补齐头部用户下拉功能
Some checks failed
Release pipeline / publish (push) Has been cancelled
Release pipeline / Always run job (push) Has been cancelled

This commit is contained in:
sheng
2026-06-25 10:25:42 +08:00
parent 46ef776d74
commit 659f000bf6
5 changed files with 385 additions and 10 deletions

View File

@@ -31,3 +31,38 @@ export function logoutAdminUser () {
}
})
}
export function getAuthTime (data) {
return request({
url: 'auth/get/time',
method: 'post',
data: {
method: 'get.auth.time',
platform: 'admin',
...data
}
})
}
export function getAuthNearTime (data) {
return request({
url: 'auth/get/nearTime',
method: 'post',
data: {
method: 'get.auth.nearTime',
platform: 'admin',
...data
}
})
}
export function getAuthTimeByUpload (data) {
return request({
url: 'auth/get/timeByUpload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data
})
}

View File

@@ -97,3 +97,15 @@ export function resetUserPwd (data) {
}
})
}
export function updateUserPwd (data) {
return request({
url: BASE + 'update_pwd',
method: 'put',
data: {
method: 'system_settings_user_management_user_update_pwd',
platform: 'background',
...data
}
})
}

View File

@@ -1,31 +1,295 @@
<template>
<el-dropdown size="small" class="d2-mr">
<span class="btn-text">{{info.name ? $t('page.layout.user.greeting', { name: info.name }) : $t('page.layout.user.not_logged_in')}}</span>
<span class="btn-text">
{{ info.name ? $t('page.layout.user.greeting', { name: info.name }) : $t('page.layout.user.not_logged_in') }}
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="logOff">
<d2-icon name="power-off" class="d2-mr-5"/>
<el-dropdown-item v-if="isAdmin" icon="el-icon-setting" @click.native="openApiDebug">
{{ $t('page.layout.user.api_debug') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-key" @click.native="openPasswordDialog">
{{ $t('page.layout.user.change_password') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-refresh" @click.native="reloadMenu">
{{ $t('page.layout.user.reload_menu') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-document-checked" @click.native="openAuthDialog">
{{ $t('page.layout.user.product_authorization') }}
</el-dropdown-item>
<el-dropdown-item divided @click.native="logOff">
<d2-icon name="power-off" class="d2-mr-5" />
{{ $t('page.layout.user.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
<el-dialog
:title="$t('page.layout.user.change_password')"
width="600px"
:visible.sync="passwordDialogVisible"
:append-to-body="true"
:close-on-click-modal="false"
@close="resetPasswordForm"
>
<el-form ref="passwordForm" :model="passwordForm" :rules="passwordRules" label-width="110px" size="small">
<el-form-item :label="$t('page.layout.user.original_password')" prop="password_old">
<el-input v-model.trim="passwordForm.password_old" type="password" :placeholder="$t('page.layout.user.placeholder_original_password')" clearable show-password />
</el-form-item>
<el-form-item :label="$t('page.layout.user.new_password')" prop="password">
<el-input v-model.trim="passwordForm.password" type="password" :placeholder="$t('page.layout.user.placeholder_new_password')" clearable show-password />
</el-form-item>
<el-form-item :label="$t('page.layout.user.confirm_password')" prop="password_confirm">
<el-input v-model.trim="passwordForm.password_confirm" type="password" :placeholder="$t('page.layout.user.placeholder_confirm_password')" clearable show-password />
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="small" @click="passwordDialogVisible = false">{{ $t('page.layout.user.cancel') }}</el-button>
<el-button size="small" type="primary" :loading="passwordSubmitting" @click="submitPassword">
{{ $t('page.layout.user.confirm') }}
</el-button>
</div>
</el-dialog>
<el-dialog
:title="$t('page.layout.user.product_authorization')"
width="600px"
:visible.sync="authDialogVisible"
:append-to-body="true"
:close-on-click-modal="false"
@opened="clearAuthFiles"
>
<el-descriptions :column="1" border>
<el-descriptions-item :label="$t('page.layout.user.authorization_validity_period')">
{{ authTime || '-' }}
</el-descriptions-item>
</el-descriptions>
<el-upload
ref="authUpload"
class="auth-upload"
drag
action=""
:limit="1"
:show-file-list="true"
:before-upload="beforeAuthUpload"
:http-request="uploadAuthFile"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
{{ $t('page.layout.user.drag_upload_hint') }}<em>{{ $t('page.layout.user.click_upload') }}</em>
</div>
<div slot="tip" class="el-upload__tip">{{ $t('page.layout.user.upload_file_tip') }}</div>
</el-upload>
</el-dialog>
</el-dropdown>
</template>
<script>
import { mapState, mapActions } from 'vuex'
import util from '@/libs/util'
import { mapActions, mapState } from 'vuex'
import { updateUserPwd } from '@/api/system-administration/user'
import { getAuthNearTime, getAuthTime, getAuthTimeByUpload } from '@/api/auth'
export default {
data () {
return {
passwordDialogVisible: false,
passwordSubmitting: false,
authDialogVisible: false,
authTime: '',
authNoticeTimer: null,
passwordForm: this.emptyPasswordForm(),
passwordRules: {
password_old: [
{ required: true, message: this.$t('page.layout.user.old_password_required'), trigger: 'blur' },
{ min: 6, max: 64, message: this.$t('page.layout.user.length_6_to_64'), trigger: 'blur' }
],
password: [
{ required: true, message: this.$t('page.layout.user.new_password_required'), trigger: 'blur' },
{ min: 6, max: 64, message: this.$t('page.layout.user.length_6_to_64'), trigger: 'blur' }
],
password_confirm: [
{ required: true, message: this.$t('page.layout.user.confirm_password_required'), trigger: 'blur' },
{ min: 6, max: 64, message: this.$t('page.layout.user.length_6_to_64'), trigger: 'blur' }
]
}
}
},
computed: {
...mapState('d2admin/user', [
'info'
])
]),
isAdmin () {
return String(this.info && this.info.admin && this.info.admin.role_id) === '1'
}
},
mounted () {
this.loadAuthTime()
},
beforeDestroy () {
this.clearAuthNoticeTimer()
},
methods: {
...mapActions('d2admin/account', [
'logout'
]),
/**
* @description 登出
*/
emptyPasswordForm () {
return {
password_old: '',
password: '',
password_confirm: ''
}
},
normalizeAuthResponse (res) {
const source = res && res.data ? res.data : res
return {
status: Number(source && source.status),
msg: source && source.msg ? source.msg : ''
}
},
openApiDebug () {
const routeData = this.$router.resolve({
path: '/api',
query: { token: this.info && this.info.token }
})
window.open(routeData.href, '_blank')
},
openPasswordDialog () {
this.passwordForm = this.emptyPasswordForm()
this.passwordDialogVisible = true
this.$nextTick(() => {
this.$refs.passwordForm && this.$refs.passwordForm.clearValidate()
})
},
resetPasswordForm () {
this.passwordForm = this.emptyPasswordForm()
this.$nextTick(() => {
this.$refs.passwordForm && this.$refs.passwordForm.clearValidate()
})
},
submitPassword () {
this.$refs.passwordForm.validate(async valid => {
if (!valid) return
if (this.passwordForm.password_old === this.passwordForm.password) {
this.$message.error(this.$t('page.layout.user.password_same_error'))
return
}
if (this.passwordForm.password !== this.passwordForm.password_confirm) {
this.$message.error(this.$t('page.layout.user.password_not_match'))
return
}
this.passwordSubmitting = true
try {
await updateUserPwd({
user_id: this.info && this.info.admin && this.info.admin.user_id,
...this.passwordForm
})
this.passwordDialogVisible = false
this.$message.success(this.$t('page.layout.user.password_change_success'))
util.cookies.remove('token')
util.cookies.remove('uuid')
util.cookies.remove('block')
localStorage.removeItem('user_id')
this.logout({ confirm: false })
} finally {
this.passwordSubmitting = false
}
})
},
async reloadMenu () {
await this.$store.dispatch('d2admin/menu/menuReload')
this.$store.commit('d2admin/page/keepAliveClean')
await this.$router.replace('/refresh')
this.$message.success(this.$t('page.layout.user.menu_permission_reloaded'))
},
openAuthDialog () {
this.authDialogVisible = true
this.loadAuthTime()
},
clearAuthFiles () {
this.$nextTick(() => {
this.$refs.authUpload && this.$refs.authUpload.clearFiles()
})
},
beforeAuthUpload (file) {
const ext = String(file.name || '').replace(/.+\./, '').toLowerCase()
if (ext !== 'license') {
this.$message.error(this.$t('page.layout.user.please_upload_license'))
return false
}
return true
},
async uploadAuthFile ({ file }) {
const formData = new FormData()
formData.append('method', 'get.auth.timeByUpload')
formData.append('platform', 'admin')
formData.append('file', file)
try {
const res = this.normalizeAuthResponse(await getAuthTimeByUpload(formData))
if (res.status !== 0) {
this.$message.error(res.msg)
return
}
this.authTime = res.msg
this.$message.success(this.$t('page.layout.user.authorization_update_success', { time: res.msg }))
} finally {
this.clearAuthFiles()
}
},
notifyAuthExpired (message) {
if (process.env.NODE_ENV === 'development') return
this.$notify.error({
title: this.$t('page.layout.user.prompt'),
message,
duration: 4500,
position: 'bottom-right',
showClose: false
})
},
notifyAuthNearTime (message) {
if (process.env.NODE_ENV === 'development') return
this.$notify.warning({
title: this.$t('page.layout.user.prompt'),
message,
duration: 4500,
position: 'top-right',
showClose: false
})
},
clearAuthNoticeTimer () {
if (this.authNoticeTimer) {
clearInterval(this.authNoticeTimer)
this.authNoticeTimer = null
}
},
startAuthNoticeTimer (type, message) {
if (process.env.NODE_ENV === 'development') return
this.clearAuthNoticeTimer()
const notify = type === 'expired' ? this.notifyAuthExpired : this.notifyAuthNearTime
const interval = type === 'expired' ? 5000 : 30000
this.authNoticeTimer = setInterval(() => notify(message), interval)
},
async loadAuthTime () {
try {
const auth = this.normalizeAuthResponse(await getAuthTime())
if (auth.msg) this.authTime = auth.msg
if (auth.status === 1) {
const message = this.$t('page.layout.user.authorization_expired_message', { time: auth.msg })
this.notifyAuthExpired(message)
this.startAuthNoticeTimer('expired', message)
}
const near = this.normalizeAuthResponse(await getAuthNearTime())
if (near.status === 1) {
const message = this.$t('page.layout.user.authorization_near_message', { message: near.msg })
this.notifyAuthNearTime(message)
this.startAuthNoticeTimer('near', message)
}
} catch (error) {
this.clearAuthNoticeTimer()
}
},
logOff () {
localStorage.removeItem('user_id')
this.logout({
confirm: true
})
@@ -33,3 +297,9 @@ export default {
}
}
</script>
<style scoped>
.auth-upload {
margin-top: 16px;
}
</style>

View File

@@ -2685,7 +2685,36 @@
"user": {
"greeting": "Hello {name}",
"not_logged_in": "Not logged in",
"logout": "Logout"
"logout": "Logout",
"api_debug": "API Debug",
"change_password": "Change Password",
"reload_menu": "Reload Menu",
"product_authorization": "Product Authorization",
"original_password": "Old Password",
"new_password": "New Password",
"confirm_password": "Confirm Password",
"placeholder_original_password": "Please enter old password",
"placeholder_new_password": "Please enter new password",
"placeholder_confirm_password": "Please confirm new password",
"old_password_required": "Please enter old password",
"new_password_required": "Please enter new password",
"confirm_password_required": "Please confirm new password",
"length_6_to_64": "Length should be 6 to 64 characters",
"password_same_error": "Old password cannot be the same as new password",
"password_not_match": "The two new passwords do not match",
"password_change_success": "Password changed successfully. Please log in again",
"menu_permission_reloaded": "Menu permissions reloaded",
"authorization_validity_period": "Authorization Validity",
"drag_upload_hint": "Drop the license file here, or ",
"click_upload": "click to upload",
"upload_file_tip": "Only .license authorization files are allowed",
"please_upload_license": "Please upload a license file",
"authorization_update_success": "Authorization validity updated: {time}",
"authorization_expired_message": "Product authorization time: {time}. Authorization has expired and APIs will be unavailable. Please verify the license again.",
"authorization_near_message": "Product authorization is about to expire. APIs will be unavailable after expiration. {message}",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm"
},
"fullscreen": {
"enter": "Fullscreen",

View File

@@ -2685,7 +2685,36 @@
"user": {
"greeting": "你好 {name}",
"not_logged_in": "未登录",
"logout": "注销"
"logout": "注销",
"api_debug": "API调试",
"change_password": "修改密码",
"reload_menu": "重新加载菜单",
"product_authorization": "产品授权",
"original_password": "原密码",
"new_password": "新密码",
"confirm_password": "确认密码",
"placeholder_original_password": "请输入原密码",
"placeholder_new_password": "请输入新密码",
"placeholder_confirm_password": "请输入确认密码",
"old_password_required": "请输入原密码",
"new_password_required": "请输入新密码",
"confirm_password_required": "请输入确认密码",
"length_6_to_64": "长度在 6 到 64 个字符",
"password_same_error": "原密码不能与新密码相同",
"password_not_match": "两次输入的新密码不一致",
"password_change_success": "密码修改成功,请重新登录",
"menu_permission_reloaded": "菜单权限已重新载入",
"authorization_validity_period": "授权有效期",
"drag_upload_hint": "将 license 文件拖到此处,或",
"click_upload": "点击上传",
"upload_file_tip": "只能上传 .license 授权文件",
"please_upload_license": "请上传 license 文件",
"authorization_update_success": "授权有效期更新成功:{time}",
"authorization_expired_message": "产品授权时间:{time},授权已过期,接口将无法使用。请重新验证授权序列号!",
"authorization_near_message": "产品授权即将到期,授权过期时,数据接口将无法使用。{message}",
"prompt": "提示",
"cancel": "取消",
"confirm": "确定"
},
"fullscreen": {
"enter": "全屏",