2025-07-18 18:17:36 +08:00

293 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-layout-header class="basic-header" :class="cpClassNames" :style="cpStyles">
<!-- 左侧 -->
<div v-if="cpShowLeftSlot" class="basic-header__left">
<slot name="left"></slot>
</div>
<!-- 中间 -->
<div v-if="cpShowDefaultSlot" class="basic-header__center">
<slot></slot>
</div>
<!-- 右侧 -->
<div class="basic-header__right">
<a-space :size="16">
<action-button @click="handleConfig">
<setting-outlined></setting-outlined>
</action-button>
<a-dropdown :trigger="['hover']">
<action-button :style="{ height: '44px' }">
<translation-outlined />
</action-button>
<a-spin />
<template #overlay>
<a-menu v-model:selectedKeys="current">
<a-menu-item v-for="(item, key) in langData" :key="key" @click="handleLang(key)">
{{ item.icon }} {{ item.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-dropdown :trigger="['click']">
<action-button :style="{ height: '44px' }">
<a-avatar class="mr-8-1 display-inline-flex justify-content-center" :size="24"
:src="userInfo?.avatar">
</a-avatar>
<span>{{ userInfo?.name }}</span>
</action-button>
<a-spin />
<template #overlay>
<a-menu>
<a-menu-item key="edit" @click="handleOpen">
<edit-outlined />
修改密码
</a-menu-item>
<a-menu-item key="logout" @click="handleLogout">
<login-outlined></login-outlined>
{{ $t('component.RightContent.logout') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</div>
</a-layout-header>
<a-modal v-model:open="open" title="修改密码" @ok="handleOk">
<a-form ref="formRef" :model="pwdFormData" :rules="pwdFormRules">
<a-card class="mb-8-2">
<a-row :gutter="12">
<a-col :span="24">
<a-form-item :label="'原密码'" name="oldPwd">
<a-input-password :placeholder="'请输入原密码'"
v-model:value="pwdFormData.oldPwd"></a-input-password>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item :label="'新密码'" name="newPwd">
<a-input-password :placeholder="'请输入新密码'"
v-model:value="pwdFormData.newPwd"></a-input-password>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item :label="'确认密码'" name="confirmPwd">
<a-input-password :placeholder="'确认密码'"
v-model:value="pwdFormData.confirmPwd"></a-input-password>
</a-form-item>
</a-col>
</a-row>
</a-card>
</a-form>
</a-modal>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
import { computed, useSlots, ref } from 'vue'
import { useRouter } from 'vue-router'
import { LoginOutlined, SettingOutlined, EditOutlined, TranslationOutlined } from '@ant-design/icons-vue'
import { useAppStore, useUserStore } from '@/store'
import ActionButton from './ActionButton.vue'
import { theme as antTheme } from 'ant-design-vue'
import { config as conf } from '@/config'
import { useI18n } from 'vue-i18n'
import storage from '@/utils/storage'
import apis from '@/apis'
import { useForm, useModal, useSpining } from '@/hooks'
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { locale, t } = useI18n()
defineOptions({
name: 'BasicHeader',
})
/**
* @property {string} theme 主题【light=亮色dark=暗色】
*/
const props = defineProps({
theme: {
type: String,
},
})
const emit = defineEmits(['config'])
const slots = useSlots(['default', 'left', 'right'])
const formRef = ref(null)
const userStore = useUserStore()
const appStore = useAppStore()
const router = useRouter()
const { config } = storeToRefs(appStore)
const { userInfo } = storeToRefs(userStore)
const { token } = antTheme.useToken()
const pwdFormData = ref({})
const pwdFormRules = {
oldPwd: [{ required: true, message: '请输原密码' }],
newPwd: [{ required: true, message: '请输入新密码' }],
confirmPwd: [{ required: true, message: '请确认新密码' }],
}
const open = ref(false)
const cpClassNames = computed(() => ({
[`basic-header--${props.theme}`]: true,
}))
const cpStyles = computed(() => {
const styles = {
zIndex: config.value.layout === 'topBottom' ? 120 : 110,
}
if (config.value.headerTheme === 'light') {
styles.boxShadow = `0 0 0 1px ${token.value.colorSplit}`
}
return styles
})
const cpShowLeftSlot = computed(() => !!slots.left)
const cpShowDefaultSlot = computed(() => !!slots.default)
const defaultLang = storage.local.getItem(conf('storage.lang')) || 'zh-ch'
const current = ref(defaultLang)
const langData = ref({
'zh-ch': {
lang: 'zh-ch',
label: '简体中文',
icon: '🇨🇳',
title: '语言',
},
'en-us': {
lang: 'en-us',
label: 'English',
icon: '🇺🇸',
title: 'Language',
},
})
/**
* 退出登录
*/
function handleLogout() {
Modal.confirm({
title: t('component.RightContent.logout'),
okText: t('button.confirm'),
cancelText: t('button.cancel'),
onOk: () => {
userStore.logout().then(() => {
router.push({
name: 'login',
})
})
},
})
}
/**
* 修改资料
*/
function handleOpen() {
open.value = true
}
/**
* 切换语言
*/
function handleLang(lang) {
storage.local.setItem(conf('storage.lang'), lang)
locale.value = lang
current.value = lang
location.reload()
}
/**
* 配置
*/
function handleConfig() {
emit('config')
}
function handleOk() {
formRef.value.validateFields().then(async (values) => {
console.log(pwdFormData.value)
if (pwdFormData.value.newPwd !== pwdFormData.value.confirmPwd) {
return message.error('两次密码不一致')
}
try {
console.log(pwdFormData.value)
showLoading()
const params = {
new_password: pwdFormData.value.newPwd,
old_password: pwdFormData.value.oldPwd
}
const result = await apis.user.updatePassword(params)
if (conf('http.code.success') === result?.success) {
hideModal()
hideLoading()
userStore.logout().then(() => {
router.push({
name: 'login',
})
})
}
} catch (error) {
message.error({ content: error.message })
hideLoading()
}
})
}
</script>
<style lang="less" scoped>
.basic-header {
height: v-bind('config.headerHeight + "px"');
line-height: 1;
position: sticky;
top: 0;
display: flex;
align-items: center;
padding-inline: 16px;
// box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
&__left {
flex-shrink: 0;
display: flex;
align-items: center;
}
&__center {
flex: auto;
min-width: 0;
}
&__right {
flex-shrink: 0;
margin: 0 0 0 auto;
display: flex;
align-items: center;
}
:deep(.ant-menu-horizontal) {
border: none;
line-height: v-bind('config.headerHeight + "px"');
}
&--light {
background: #fff;
}
&--dark {
color: #fff;
:deep(.action-btn) {
&:hover {
background: #252a3d;
}
}
}
:deep(.basic-menu) {
.basic-menu__title {
.ant-badge {
margin-top: -2px;
}
}
}
}
</style>