feat(admin): account governance and user sync pages

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-20 20:26:53 +08:00
parent 3681999667
commit 618d1d796c
21 changed files with 2261 additions and 1 deletions
@@ -0,0 +1,129 @@
<!-- 编辑用户治理属性对话框:role/localLocked/displayName(sourceStatus 只读展示)。
自我保护:自身行禁用角色与锁定编辑(服务端 400 双保险——http 层已本地化)。 -->
<template>
<ElDialog
v-model="visible"
:title="$t('usersManage.edit.title', { account: user?.account ?? '' })"
width="520px"
destroy-on-close
data-test="user-edit-dialog"
>
<ElForm :model="form" label-width="120px">
<ElFormItem :label="$t('usersManage.list.colAccount')">
<span class="text-gray-500">{{ user?.account }}</span>
</ElFormItem>
<ElFormItem :label="$t('usersManage.list.colRole')">
<ElSelect
v-model="form.role"
:disabled="isSelf"
style="width: 200px"
data-test="user-edit-role"
>
<ElOption value="staff" :label="$t('usersManage.role.staff')" />
<ElOption value="admin" :label="$t('usersManage.role.admin')" />
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('usersManage.list.colDisplayName')" :error="fieldErrors.displayName">
<ElInput
v-model="form.displayName"
:placeholder="$t('usersManage.edit.displayNamePlaceholder')"
data-test="user-edit-name"
/>
</ElFormItem>
<ElFormItem :label="$t('usersManage.list.colLocalLocked')">
<div class="flex flex-col gap-1">
<ElSwitch v-model="form.localLocked" :disabled="isSelf" data-test="user-edit-locked" />
<span class="text-gray-400 text-xs">{{ $t('usersManage.edit.lockedHint') }}</span>
</div>
</ElFormItem>
</ElForm>
<ElAlert
v-if="isSelf"
type="info"
:closable="false"
:title="$t('usersManage.edit.selfGuardHint')"
data-test="user-edit-self-hint"
/>
<template #footer>
<ElButton data-test="user-edit-cancel" @click="visible = false">
{{ $t('common.cancel') }}
</ElButton>
<ElButton type="primary" :loading="submitting" data-test="user-edit-submit" @click="submit">
{{ $t('usersManage.edit.submit') }}
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { fetchUpdateAdminUser } from '@/api/adminUsers'
import { buildUserUpdatePayload } from '@/utils/system/userAdmin'
import { useUserStore } from '@/store/modules/user'
const props = defineProps<{
/** 编辑目标(null 时对话框不应打开) */
user: Api.AdminUser.Item | null
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'saved', user: Api.AdminUser.Item): void
}>()
const visible = defineModel<boolean>({ default: false })
const { t } = useI18n()
const userStore = useUserStore()
const form = reactive({
role: 'staff' as Api.AdminUser.Role,
displayName: '',
localLocked: false
})
const fieldErrors = reactive({ displayName: '' })
const submitting = ref(false)
/** 自身行:角色/锁定禁改(服务端同口径 400;displayName 自改放行) */
const isSelf = computed(
() => props.user !== null && props.user.account === (userStore.info?.userName ?? '')
)
watch(visible, (open) => {
if (!open || !props.user) return
form.role = props.user.role
form.displayName = props.user.displayName
form.localLocked = props.user.localLocked
fieldErrors.displayName = ''
})
async function submit(): Promise<void> {
if (!props.user) return
fieldErrors.displayName = ''
const payload = buildUserUpdatePayload(props.user, {
role: form.role,
displayName: form.displayName,
localLocked: form.localLocked
})
if (Object.keys(payload).length === 0) {
// 无变更:不制造服务端 no-op(幂等返回也不落审计,前端直接收口)
ElMessage.info(t('usersManage.edit.noChanges'))
visible.value = false
return
}
submitting.value = true
try {
const updated = await fetchUpdateAdminUser(props.user.id, payload)
ElMessage.success(t('usersManage.edit.success'))
emit('saved', updated)
visible.value = false
} catch {
// 错误已由 http 层统一弹出(ElMessage),此处仅吸收避免未处理拒绝
} finally {
submitting.value = false
}
}
</script>