feat: 统一账户角色展示并优化资源页面

This commit is contained in:
czl231
2026-08-11 02:01:00 +08:00
parent 9cf16ccb3c
commit ff345dc42a
28 changed files with 555 additions and 76 deletions

View File

@@ -33,7 +33,7 @@ export type PlatformMenu = { identity: string; parent_identity?: string; group_c
export const platformApi = {
overview: () => request<DashboardOverview>('/dashboard/overview'),
listAccount: () => request<{ total: number; list: Record<string, unknown>[] }>('/platform_account'),
listRole: () => resourceApi.list<PlatformRole>('/platform_role'),
listRole: () => resourceApi.list<PlatformRole>('/platform_role', 1, 100),
createRole: (data: Record<string, unknown>) => resourceApi.create<PlatformRole>('/platform_role', data),
listMenu: () => request<{ total: number; list: PlatformMenu[] }>('/gas_menu'),
listRoleMenuIdentities: (identity: string) =>

View File

@@ -25,6 +25,9 @@ export type ResourceField = {
required?: boolean;
relation?: string;
options?: Array<{ label: string; value: string | number }>;
defaultValue?: string | number | boolean;
readonly?: boolean;
unknownValueLabel?: string;
};
export type DetailAction = {
@@ -63,7 +66,7 @@ const fieldLabels: Record<string, string> = {
username: '用户名',
password: '密码',
display_name: '显示名称',
role_code: '角色编码',
role_code: '角色',
platform_role_code: '平台角色',
credit_code: '统一社会信用代码',
principal: '负责人',
@@ -272,6 +275,18 @@ function relation(key: string, resource: string, required = false): ResourceFiel
return f(key, { type: 'identity', relation: resource, required });
}
/** 创建固定管理员角色字段,确保页面仅展示后端支持的角色。 */
function fixedAdminRole(label: string): ResourceField {
return f('role_code', {
type: 'select',
required: true,
options: [{ label, value: 'admin' }],
defaultValue: 'admin',
readonly: true,
unknownValueLabel: '未知角色',
});
}
function define(
name: string,
title: string,
@@ -310,9 +325,9 @@ const reason = [f('reason', { required: true })];
const platformResources: ResourceUiDefinition[] = [
{ ...define('gas_basic', '气站管理', 'writable', [f('code', { required: true }), f('name', { required: true }), f('credit_code'), f('principal'), f('address'), f('longitude'), f('latitude')]), accountManagement: { resource: '/gas_account', relationKey: 'gas_basic_identity', title: '气站账户' }, walletOwnerType: 'gas' },
define('gas_account', '气站账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), f('role_code'), relation('gas_basic_identity', '/gas_basic', true)]),
define('gas_account', '气站账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), fixedAdminRole('气站管理员'), relation('gas_basic_identity', '/gas_basic', true)]),
{ ...define('delivery_basic', '配送点管理', 'writable', [f('delivery_code', { required: true }), f('name', { required: true }), relation('gas_basic_identity', '/gas_basic'), f('principal'), f('address')]), accountManagement: { resource: '/delivery_account', relationKey: 'delivery_basic_identity', title: '配送点账户' }, walletOwnerType: 'delivery' },
define('delivery_account', '配送账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), f('role_code'), relation('delivery_basic_identity', '/delivery_basic', true)]),
define('delivery_account', '配送账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), fixedAdminRole('配送点管理员'), relation('delivery_basic_identity', '/delivery_basic', true)]),
{ ...define('staff_account', '工作人员', 'writable', [f('username', { required: true }), f('password', { required: true }), f('name', { required: true }), f('phone'), f('avatar'), f('role_code', { required: true, type: 'select', options: [{ label: '安装人员', value: 'installer' }, { label: '配送人员', value: 'delivery' }, { label: '运维人员', value: 'operations' }] }), relation('gas_basic_identity', '/gas_basic'), relation('delivery_basic_identity', '/delivery_basic'), f('work_status', { type: 'select', options: [{ label: '在岗', value: 'on_duty' }, { label: '离岗', value: 'off_duty' }] })]), walletOwnerType: 'staff' },
define('staff_credential', '工作人员资质', 'writable', [relation('staff_account_identity', '/staff_account', true), f('credential_type', { required: true }), f('credential_no'), f('expired_at')]),
{ ...define('user_account', '用户账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('name', { required: true }), f('phone'), f('avatar'), f('real_name')]), walletOwnerType: 'user' },
@@ -409,7 +424,7 @@ const platformResources: ResourceUiDefinition[] = [
const gasOverrides: ResourceUiDefinition[] = [
{ ...define('delivery_basic', '配送点管理', 'writable', [f('delivery_code', { required: true }), f('name', { required: true }), f('principal'), f('address')]), accountManagement: { resource: '/delivery_account', relationKey: 'delivery_basic_identity', title: '配送点账户' } },
define('delivery_account', '配送点账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name')], 'list', [
define('delivery_account', '配送点账户', 'writable', [f('username', { required: true }), f('password', { required: true }), f('display_name'), fixedAdminRole('配送点管理员')], 'list', [
{ name: '重置密码', resource: '/delivery_account/:identity/password', method: 'PUT', fields: [f('password', { required: true })] },
]),
define('staff_account', '工作人员', 'writable', [f('username', { required: true }), f('password', { required: true }), f('name', { required: true }), f('phone'), f('avatar'), f('role_code', { required: true, type: 'select', options: [{ label: '安装人员', value: 'installer' }, { label: '配送人员', value: 'delivery' }, { label: '运维人员', value: 'operations' }] }), relation('delivery_basic_identity', '/delivery_basic'), f('work_status', { type: 'select', options: [{ label: '在岗', value: 'on_duty' }, { label: '离岗', value: 'off_duty' }] })], 'list', [

View File

@@ -102,7 +102,9 @@
<a-table-column title="唯一标识" :width="150"><template #cell="{ record }"><IdentityText :value="String(record.identity)" /></template></a-table-column>
<a-table-column title="用户名" data-index="username" :width="130" ellipsis tooltip />
<a-table-column title="显示名称" data-index="display_name" :width="130" ellipsis tooltip />
<a-table-column title="角色编码" data-index="role_code" :width="100" ellipsis tooltip />
<a-table-column title="角色" :width="130" ellipsis tooltip>
<template #cell="{ record }">{{ accountRoleLabel(definition.accountManagement?.resource, record.role_code) }}</template>
</a-table-column>
<a-table-column title="状态" :width="90">
<template #cell="{ record }">
<a-tag :color="Number(record.status) === 1 ? 'green' : 'red'">
@@ -151,8 +153,8 @@
<a-form-item label="显示名称">
<a-input v-model="accountForm.display_name" />
</a-form-item>
<a-form-item label="角色编码">
<a-input v-model="accountForm.role_code" />
<a-form-item label="角色">
<a-input :model-value="accountRoleLabel(definition.accountManagement?.resource, accountForm.role_code || 'admin')" disabled />
</a-form-item>
</a-form>
</a-modal>
@@ -166,8 +168,8 @@
<a-date-picker v-else-if="field.type === 'datetime'" v-model="form[field.key]" show-time value-format="YYYY-MM-DDTHH:mm:ssZ" />
<a-textarea v-else-if="field.type === 'textarea'" v-model="form[field.key]" :auto-size="{ minRows: 3, maxRows: 8 }" />
<a-input-password v-else-if="field.type === 'password'" v-model="form[field.key]" />
<a-select v-else-if="field.type === 'select'" v-model="form[field.key]" allow-clear>
<a-option v-for="option in field.options" :key="option.value" :value="option.value">{{ option.label }}</a-option>
<a-select v-else-if="field.type === 'select'" v-model="form[field.key]" :disabled="field.readonly" allow-clear>
<a-option v-for="option in selectFieldOptions(field)" :key="option.value" :value="option.value">{{ option.label }}</a-option>
</a-select>
<a-select v-else-if="field.type === 'identity' || field.type === 'identity-list'" v-model="form[field.key]" :multiple="field.type === 'identity-list'" allow-clear allow-search :loading="relationLoading[field.relation ?? '']" @search="(value: string) => searchRelation(field.relation, value)">
<a-option v-for="option in relationOptions[field.relation ?? ''] ?? []" :key="String(option.identity)" :value="String(option.identity)">
@@ -437,7 +439,7 @@ function resetForm(data?: Row) {
const value = data?.[field.key];
form[field.key] =
value == null
? undefined
? field.defaultValue
: field.type === 'money'
? Number(value) / 100
: value;
@@ -655,7 +657,6 @@ async function saveAccount() {
try {
const payload: Record<string, unknown> = {
display_name: String(accountForm.display_name ?? '').trim(),
role_code: String(accountForm.role_code ?? '').trim(),
[management.relationKey]: ownerIdentity,
};
if (!accountEditingIdentity.value) {
@@ -1010,6 +1011,8 @@ function displayFieldValue(field: ResourceField, row: Row) {
if (field.options) {
const option = field.options.find((item) => item.value === value);
if (option) return option.label;
if (field.unknownValueLabel && value != null && value !== '')
return `${field.unknownValueLabel}${String(value)}`;
}
if (
(field.type === 'identity' || field.type === 'identity-list') &&
@@ -1021,6 +1024,8 @@ function displayFieldValue(field: ResourceField, row: Row) {
function displayValue(key: string, value: unknown) {
if (value == null || value === '') return '-';
if (key === 'role_code')
return accountRoleLabel(props.definition.resource, value);
if (typeof value === 'boolean') return value ? '是' : '否';
if (
key === 'amount' ||
@@ -1045,6 +1050,40 @@ function displayValue(key: string, value: unknown) {
return String(value);
}
/** 将各管理端稳定角色编码转换为中文名称,未知编码保留审计线索。 */
function accountRoleLabel(resource: string | undefined, value: unknown) {
const code = String(value ?? '');
const labels: Record<string, Record<string, string>> = {
'/gas_account': { admin: '气站管理员' },
'/delivery_account': { admin: '配送点管理员' },
'/staff_account': {
installer: '安装人员',
delivery: '配送人员',
operations: '运维人员',
},
};
const label = labels[resource ?? '']?.[code];
return label ?? (code ? `未知角色(${code}` : '-');
}
/** 给只读角色下拉补充历史未知值,避免直接暴露裸编码。 */
function selectFieldOptions(field: ResourceField) {
const options = [...(field.options ?? [])];
const value = form[field.key];
if (
field.unknownValueLabel &&
value != null &&
value !== '' &&
!options.some((option) => String(option.value) === String(value))
) {
options.push({
label: `${field.unknownValueLabel}${String(value)}`,
value,
});
}
return options;
}
async function loadRelation(resource: string, keyword = '') {
relationLoading[resource] = true;
try {