style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -23,7 +23,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import InlineHelp from '@/shared/components/InlineHelp.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
@@ -98,7 +98,7 @@ const canEdit = computed(() => access.can('platform:user:edit'))
|
||||
const canGrantRoles = computed(() => access.can('platform:user:grant-role'))
|
||||
const canChangeStatus = computed(() => access.can('platform:user:status'))
|
||||
const canResetPassword = computed(() => access.can('platform:user:reset-password'))
|
||||
const editorLabel = computed(() => editingUser.value ? `编辑用户 - ${editingUser.value.displayName}` : '新增用户')
|
||||
const editorLabel = computed(() => editingUser.value ? `编辑用户 · ${editingUser.value.displayName?.trim() || editingUser.value.username?.trim() || '—'}` : '新增用户')
|
||||
|
||||
useUnsavedWorkRegistration({
|
||||
key: 'platform-user-editor',
|
||||
@@ -120,6 +120,9 @@ const domesticPdaRoleIds = computed(() => new Set(
|
||||
availableRoles.value.filter((role) => role.roleCode === 'CN_FIRSTLEG_PDA_OPERATOR').map((role) => String(role.id)),
|
||||
))
|
||||
const hasDomesticPdaRole = computed(() => form.roleIds.some((roleId) => domesticPdaRoleIds.value.has(roleId)))
|
||||
const selectedRoleNames = computed(() => form.roleIds.map((roleId) => availableRoles.value.find((role) => String(role.id) === roleId)?.name || '—').join('、') || '—')
|
||||
const selectedOverseasWarehouse = computed(() => overseasWarehouses.value.find((warehouse) => warehouse.id === form.overseasWarehouseId)?.warehouseName || '—')
|
||||
const selectedDomesticWarehouse = computed(() => domesticWarehouses.value.find((warehouse) => String(warehouse.id) === form.domesticWarehouseId)?.warehouseName || '—')
|
||||
|
||||
const formRules = computed<FormRules>(() => ({
|
||||
username: editingUser.value ? [] : [
|
||||
@@ -331,7 +334,7 @@ async function toggleStatus(row: PlatformManagedUser) {
|
||||
const targetStatus = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
|
||||
const targetLabel = targetStatus === 'ACTIVE' ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认${targetLabel}用户“${row.displayName}”吗?`, `${targetLabel}用户`, { customClass: 'admin-density-confirm',
|
||||
await ElMessageBox.confirm(`确认${targetLabel}用户“${row.displayName}”吗?`, `${targetLabel}用户 · ${row.displayName?.trim() || row.username?.trim() || '—'}`, { customClass: 'admin-density-confirm',
|
||||
confirmButtonText: `确认${targetLabel}`,
|
||||
cancelButtonText: '取消',
|
||||
type: targetStatus === 'DISABLED' ? 'warning' : 'info',
|
||||
@@ -429,93 +432,47 @@ onMounted(loadInitialData)
|
||||
</DataTable></section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDialog v-model="editorOpen" :title="editorLabel" width="min(650px, calc(100vw - 28px))" class="internal-user-editor-dialog admin-density-dialog iam-density" destroy-on-close :confirm-loading="saving" :confirm-text="editingUser ? '保存修改' : '确认添加'" @confirm="saveUser">
|
||||
<el-form class="density-form" ref="formRef" :model="form" :rules="formRules" label-position="top">
|
||||
<FormSection class="density-form-section" title="用户资料" description="维护登录身份、联系方式与平台角色。">
|
||||
<div class="form-grid two-cols">
|
||||
<el-form-item label="用户名" prop="username" required>
|
||||
<el-input v-model="form.username" :disabled="Boolean(editingUser)" placeholder="登录用户名" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="displayName" required>
|
||||
<el-input v-model="form.displayName" :disabled="Boolean(editingUser) && !canEdit" placeholder="真实姓名" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone" required>
|
||||
<el-input v-model="form.phone" :disabled="Boolean(editingUser) && !canEdit" placeholder="手机号码" inputmode="tel" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" :disabled="Boolean(editingUser) && !canEdit" placeholder="邮箱地址" inputmode="email" maxlength="128" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" prop="roleIds" required>
|
||||
<el-select v-model="form.roleIds" multiple collapse-tags collapse-tags-tooltip
|
||||
:disabled="Boolean(editingUser) && (!canEdit || !canGrantRoles || editingUser?.protectedAccount)" placeholder="选择角色">
|
||||
<el-option v-for="role in availableRoles" :key="role.id" :label="role.name" :value="String(role.id)" :disabled="role.status !== 'ACTIVE'" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" :disabled="Boolean(editingUser) && (!canChangeStatus || cannotDisable(editingUser))">
|
||||
<el-option label="正常" value="ACTIVE" />
|
||||
<el-option label="禁用" value="DISABLED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasOverseasPdaRole" label="实际海外收货仓" prop="overseasWarehouseId" required>
|
||||
<el-select v-model="form.overseasWarehouseId" filterable
|
||||
:loading="overseasWarehousesLoading" :disabled="Boolean(editingUser) && !canEdit"
|
||||
placeholder="请选择平台共享海外收货仓">
|
||||
<el-option v-for="warehouse in overseasWarehouses" :key="warehouse.id"
|
||||
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode} · ${warehouse.countryCode})`"
|
||||
:value="warehouse.id" />
|
||||
</el-select>
|
||||
<p v-if="overseasWarehousesError" class="field-error">
|
||||
{{ overseasWarehousesError.message }}
|
||||
<el-button link type="primary" :loading="overseasWarehousesLoading" @click="loadOverseasWarehouses">重新加载</el-button>
|
||||
</p>
|
||||
<p v-else class="field-help">仓库将写入登录 Token,海外 PDA 不提供仓库切换。</p>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasDomesticPdaRole" label="国内作业仓" prop="domesticWarehouseId" required>
|
||||
<el-select v-model="form.domesticWarehouseId" filterable
|
||||
:loading="domesticWarehousesLoading" :disabled="Boolean(editingUser) && !canEdit"
|
||||
placeholder="请选择国内作业仓">
|
||||
<el-option v-for="warehouse in domesticWarehouses" :key="String(warehouse.id)"
|
||||
:label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`"
|
||||
:value="String(warehouse.id)" />
|
||||
</el-select>
|
||||
<p v-if="domesticWarehousesError" class="field-error">
|
||||
{{ domesticWarehousesError.message }}
|
||||
<el-button link type="primary" :loading="domesticWarehousesLoading" @click="loadDomesticWarehouses">重新加载</el-button>
|
||||
</p>
|
||||
<p v-else class="field-help">该人员只会出现在此仓库的国内头程 PDA 任务候选中。</p>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="editingUser" label="最后登录">
|
||||
<el-input :model-value="formatLogin(editingUser.lastLoginAt)" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
<FormSection class="density-form-section" v-if="!editingUser || canResetPassword" title="登录密码" description="密码为 8-64 位,并同时包含字母和数字。">
|
||||
<el-checkbox v-if="editingUser" v-model="form.resetPassword">同时重置密码</el-checkbox>
|
||||
<div v-if="!editingUser || form.resetPassword" class="form-grid two-cols password-fields">
|
||||
<el-form-item :label="editingUser ? '新密码' : '初始密码'" prop="password" required>
|
||||
<el-input v-model="form.password" type="password" show-password autocomplete="new-password" placeholder="8-64 位,包含字母和数字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword" required>
|
||||
<el-input v-model="form.confirmPassword" type="password" show-password autocomplete="new-password" placeholder="再次输入密码" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</FormSection>
|
||||
<AppDialog v-model="editorOpen" :title="editorLabel" width="min(650px, calc(100vw - 28px))" class="internal-user-editor-dialog admin-density-dialog iam-density" fullscreenable destroy-on-close :confirm-loading="saving" :confirm-text="editingUser ? '保存修改' : '确认添加'" @confirm="saveUser">
|
||||
<el-form class="density-form density-dialog-sections" ref="formRef" :model="form" :rules="formRules" label-position="left" label-width="100px" :disabled="saving">
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="用户基本资料">
|
||||
<h3 class="density-dialog-section__title">用户资料</h3>
|
||||
<table class="density-table density-table--editable user-editor-table"><tbody>
|
||||
<tr><td><el-form-item label="用户名" prop="username" required><span v-if="editingUser">{{form.username || '—'}}</span><el-input v-else v-model="form.username" placeholder="登录用户名" maxlength="64" /></el-form-item></td><td><el-form-item label="姓名" prop="displayName" required><span v-if="editingUser && !canEdit">{{form.displayName || '—'}}</span><el-input v-else v-model="form.displayName" placeholder="真实姓名" maxlength="64" /></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="手机号" prop="phone" required><span v-if="editingUser && !canEdit">{{form.phone || '—'}}</span><el-input v-else v-model="form.phone" placeholder="手机号码" inputmode="tel" maxlength="11" /></el-form-item></td><td><el-form-item label="邮箱" prop="email"><span v-if="editingUser && !canEdit">{{form.email || '—'}}</span><el-input v-else v-model="form.email" placeholder="邮箱地址" inputmode="email" maxlength="128" /></el-form-item></td></tr>
|
||||
<tr><td colspan="2"><el-form-item label="角色" prop="roleIds" required><span v-if="editingUser && (!canEdit || !canGrantRoles || editingUser.protectedAccount)">{{selectedRoleNames}}</span><el-select v-else v-model="form.roleIds" multiple collapse-tags collapse-tags-tooltip placeholder="选择角色"><el-option v-for="role in availableRoles" :key="role.id" :label="role.name" :value="String(role.id)" :disabled="role.status !== 'ACTIVE'" /></el-select></el-form-item></td></tr>
|
||||
<tr><td :colspan="editingUser ? 1 : 2"><el-form-item label="状态" prop="status"><span v-if="editingUser && (!canChangeStatus || cannotDisable(editingUser))">{{form.status === 'ACTIVE' ? '正常' : '禁用'}}</span><el-select v-else v-model="form.status"><el-option label="正常" value="ACTIVE" /><el-option label="禁用" value="DISABLED" /></el-select></el-form-item></td><td v-if="editingUser"><el-form-item label="最后登录"><span>{{formatLogin(editingUser.lastLoginAt)}}</span></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section v-if="hasOverseasPdaRole || hasDomesticPdaRole" class="density-dialog-section" data-tone="work" aria-label="用户作业仓库">
|
||||
<h3 class="density-dialog-section__title">作业仓库</h3>
|
||||
<table class="density-table density-table--editable"><tbody>
|
||||
<tr v-if="hasOverseasPdaRole"><td><el-form-item label="实际海外收货仓" prop="overseasWarehouseId" label-width="145px" required>
|
||||
<template #label>实际海外收货仓 <InlineHelp title="海外仓作业范围">该人员仅在绑定的海外仓作业,海外 PDA 不提供仓库切换。</InlineHelp></template>
|
||||
<span v-if="editingUser && !canEdit">{{selectedOverseasWarehouse}}</span><el-select v-else v-model="form.overseasWarehouseId" filterable :loading="overseasWarehousesLoading" placeholder="请选择平台共享海外收货仓"><el-option v-for="warehouse in overseasWarehouses" :key="warehouse.id" :label="`${warehouse.warehouseName}(${warehouse.warehouseCode} · ${warehouse.countryCode})`" :value="warehouse.id" /></el-select>
|
||||
<LoadErrorState v-if="overseasWarehousesError" compact title="共享海外收货仓加载失败" :message="overseasWarehousesError.message" :loading="overseasWarehousesLoading" @retry="loadOverseasWarehouses"><template #details><ApiErrorMeta :details="overseasWarehousesError" /></template></LoadErrorState>
|
||||
</el-form-item></td></tr>
|
||||
<tr v-if="hasDomesticPdaRole"><td><el-form-item label="国内作业仓" prop="domesticWarehouseId" label-width="145px" required>
|
||||
<template #label>国内作业仓 <InlineHelp title="国内仓作业范围">该人员只会出现在此仓库的国内头程 PDA 任务候选中。</InlineHelp></template>
|
||||
<span v-if="editingUser && !canEdit">{{selectedDomesticWarehouse}}</span><el-select v-else v-model="form.domesticWarehouseId" filterable :loading="domesticWarehousesLoading" placeholder="请选择国内作业仓"><el-option v-for="warehouse in domesticWarehouses" :key="String(warehouse.id)" :label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`" :value="String(warehouse.id)" /></el-select>
|
||||
<LoadErrorState v-if="domesticWarehousesError" compact title="国内作业仓加载失败" :message="domesticWarehousesError.message" :loading="domesticWarehousesLoading" @retry="loadDomesticWarehouses"><template #details><ApiErrorMeta :details="domesticWarehousesError" /></template></LoadErrorState>
|
||||
</el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section v-if="!editingUser || canResetPassword" class="density-dialog-section" data-tone="work" aria-label="用户登录密码">
|
||||
<h3 class="density-dialog-section__title">登录密码 <InlineHelp title="密码要求">密码为 8-64 位,并同时包含字母和数字。</InlineHelp></h3>
|
||||
<table class="density-table density-table--editable user-editor-table"><tbody>
|
||||
<tr v-if="editingUser"><td colspan="2"><el-checkbox v-model="form.resetPassword">同时重置密码</el-checkbox></td></tr>
|
||||
<tr v-if="!editingUser || form.resetPassword"><td><el-form-item :label="editingUser ? '新密码' : '初始密码'" prop="password" required><el-input v-model="form.password" type="password" show-password autocomplete="new-password" placeholder="8-64 位,包含字母和数字" /></el-form-item></td><td><el-form-item label="确认密码" prop="confirmPassword" required><el-input v-model="form.confirmPassword" type="password" show-password autocomplete="new-password" placeholder="再次输入密码" /></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.password-fields { margin-top: var(--admin-space-6); }
|
||||
.field-help, .field-error { margin: 6px 0 0; font-size: var(--admin-font-caption); line-height: 1.5; }
|
||||
.field-help { color: var(--cc-text-secondary); }
|
||||
.field-error { color: var(--el-color-danger); }
|
||||
:global(.internal-user-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; }
|
||||
:global(.internal-user-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }
|
||||
:global(.internal-user-editor-dialog .el-dialog__footer) { flex: 0 0 auto; }
|
||||
@media (max-width: 720px) {
|
||||
.form-grid.two-cols { grid-template-columns: 1fr; }
|
||||
}
|
||||
.user-editor-table { table-layout: fixed; }
|
||||
.user-editor-table td:not([colspan="2"]) { width: 50%; }
|
||||
:global(.internal-user-editor-dialog.app-dialog.admin-density-dialog.el-dialog:not(.is-fullscreen)) { max-height: min(760px, calc(100vh - 32px)); }
|
||||
@media (max-width: 720px) { .user-editor-table tr { display: grid; } .user-editor-table td:not([colspan="2"]) { width: auto; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user