2026-08-11 00:49:41 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 功能:定义标准资源在新建、详情和编辑页面中的字段能力与动态编辑限制。
|
|
|
|
|
|
* 版本:v1.0.0
|
|
|
|
|
|
*/
|
|
|
|
|
|
import type { ResourceField, ResourceUiDefinition } from './resources';
|
|
|
|
|
|
|
|
|
|
|
|
export type RecordPageMode = 'create' | 'detail' | 'edit';
|
|
|
|
|
|
export type ResourceRow = Record<string, unknown>;
|
|
|
|
|
|
|
|
|
|
|
|
export type ResourcePageContext = {
|
|
|
|
|
|
role: string;
|
|
|
|
|
|
accountIdentity: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type ResourcePageRule = {
|
|
|
|
|
|
createHiddenKeys?: string[];
|
|
|
|
|
|
editKeys: string[];
|
|
|
|
|
|
editOptionalKeys?: string[];
|
|
|
|
|
|
editSubmitOnlyKeys?: string[];
|
|
|
|
|
|
accountSummary?: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 每个可编辑资源都显式声明更新字段,禁止把创建字段直接提交给更新接口。 */
|
|
|
|
|
|
const pageRules: Record<string, ResourcePageRule> = {
|
|
|
|
|
|
gas_basic: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'name',
|
|
|
|
|
|
'credit_code',
|
|
|
|
|
|
'principal',
|
|
|
|
|
|
'address',
|
|
|
|
|
|
'longitude',
|
|
|
|
|
|
'latitude',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
gas_account: {
|
2026-08-11 02:01:00 +08:00
|
|
|
|
editKeys: ['display_name', 'gas_basic_identity'],
|
2026-08-11 00:49:41 +08:00
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
delivery_basic: {
|
|
|
|
|
|
editKeys: ['name', 'gas_basic_identity', 'principal', 'address'],
|
|
|
|
|
|
},
|
|
|
|
|
|
delivery_account: {
|
|
|
|
|
|
editKeys: ['display_name', 'delivery_basic_identity'],
|
|
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
staff_account: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'name',
|
|
|
|
|
|
'phone',
|
|
|
|
|
|
'avatar',
|
|
|
|
|
|
'role_code',
|
|
|
|
|
|
'gas_basic_identity',
|
|
|
|
|
|
'delivery_basic_identity',
|
|
|
|
|
|
'work_status',
|
|
|
|
|
|
],
|
|
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
staff_credential: {
|
|
|
|
|
|
editKeys: ['credential_type', 'credential_no', 'expired_at'],
|
|
|
|
|
|
editSubmitOnlyKeys: ['staff_account_identity'],
|
|
|
|
|
|
},
|
|
|
|
|
|
user_account: {
|
|
|
|
|
|
editKeys: ['name', 'phone', 'avatar', 'real_name'],
|
|
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
user_address: {
|
|
|
|
|
|
editKeys: ['address', 'longitude', 'latitude', 'is_default'],
|
|
|
|
|
|
editSubmitOnlyKeys: ['user_account_identity'],
|
|
|
|
|
|
},
|
|
|
|
|
|
user_service_relation: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'gas_basic_identity',
|
|
|
|
|
|
'delivery_basic_identity',
|
|
|
|
|
|
'staff_account_identity',
|
|
|
|
|
|
],
|
|
|
|
|
|
editSubmitOnlyKeys: ['user_account_identity'],
|
|
|
|
|
|
},
|
|
|
|
|
|
producer_account: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'name',
|
|
|
|
|
|
'credit_code',
|
|
|
|
|
|
'principal',
|
|
|
|
|
|
'phone',
|
|
|
|
|
|
'address',
|
|
|
|
|
|
'password',
|
|
|
|
|
|
'display_name',
|
|
|
|
|
|
'remark',
|
|
|
|
|
|
],
|
|
|
|
|
|
editOptionalKeys: ['password'],
|
|
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
product_type: { editKeys: ['code', 'name'] },
|
|
|
|
|
|
product_warehouse: {
|
|
|
|
|
|
editKeys: ['code', 'name', 'address', 'manager', 'phone'],
|
|
|
|
|
|
},
|
|
|
|
|
|
product_info: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'name',
|
|
|
|
|
|
'producer_account_identity',
|
|
|
|
|
|
'product_type_identity',
|
|
|
|
|
|
'params',
|
|
|
|
|
|
'produced_at',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
product_repair: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'repair_no',
|
|
|
|
|
|
'repair_type',
|
|
|
|
|
|
'started_at',
|
|
|
|
|
|
'completed_at',
|
|
|
|
|
|
'result',
|
|
|
|
|
|
'target_product_status',
|
|
|
|
|
|
'content',
|
|
|
|
|
|
'operator',
|
|
|
|
|
|
'remark',
|
|
|
|
|
|
],
|
|
|
|
|
|
editSubmitOnlyKeys: ['product_info_identity'],
|
|
|
|
|
|
},
|
|
|
|
|
|
gasorder_contract: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'delivery_basic_identity',
|
|
|
|
|
|
'title',
|
|
|
|
|
|
'terms',
|
|
|
|
|
|
'file_uri',
|
|
|
|
|
|
'default_delivery_fee',
|
|
|
|
|
|
'signed_at',
|
|
|
|
|
|
'effective_at',
|
|
|
|
|
|
'expired_at',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
ec_product: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'ec_category_identity',
|
|
|
|
|
|
'product_code',
|
|
|
|
|
|
'name',
|
|
|
|
|
|
'price_amount',
|
|
|
|
|
|
'stock_quantity',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
ec_product_attribute: {
|
|
|
|
|
|
editKeys: ['ec_product_identity', 'name', 'value', 'sort_no'],
|
|
|
|
|
|
},
|
|
|
|
|
|
ec_product_image: {
|
|
|
|
|
|
editKeys: ['ec_product_identity', 'image_uri', 'sort_no', 'is_cover'],
|
|
|
|
|
|
},
|
|
|
|
|
|
fin_settlement: {
|
|
|
|
|
|
editKeys: [
|
|
|
|
|
|
'settlement_no',
|
|
|
|
|
|
'subject_type',
|
|
|
|
|
|
'subject_identity',
|
|
|
|
|
|
'period_start',
|
|
|
|
|
|
'period_end',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
cms_content: {
|
|
|
|
|
|
editKeys: ['content_type', 'title', 'body', 'version_no', 'publish_status'],
|
|
|
|
|
|
},
|
|
|
|
|
|
cs_ticket: {
|
|
|
|
|
|
editKeys: ['user_account_identity', 'ticket_no', 'category', 'priority'],
|
|
|
|
|
|
},
|
|
|
|
|
|
platform_account: {
|
|
|
|
|
|
editKeys: ['display_name', 'avatar', 'platform_role_code', 'phone'],
|
|
|
|
|
|
accountSummary: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
platform_role: { editKeys: ['name', 'location_scope'] },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const ownerKeys: Record<string, string[]> = {
|
|
|
|
|
|
staff_credential: ['staff_account_identity'],
|
|
|
|
|
|
user_address: ['user_account_identity'],
|
|
|
|
|
|
user_service_relation: ['user_account_identity'],
|
|
|
|
|
|
product_info: [
|
|
|
|
|
|
'warehouse_identity',
|
|
|
|
|
|
'gas_basic_identity',
|
|
|
|
|
|
'delivery_basic_identity',
|
|
|
|
|
|
'user_account_identity',
|
|
|
|
|
|
],
|
|
|
|
|
|
product_repair: ['product_info_identity'],
|
|
|
|
|
|
gasorder_contract: ['user_account_identity', 'gas_basic_identity'],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回当前模式需要展示的字段;编辑页会保留不可变字段但将其设为只读。 */
|
|
|
|
|
|
export function pageFields(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
mode: Exclude<RecordPageMode, 'detail'>,
|
|
|
|
|
|
row: ResourceRow,
|
|
|
|
|
|
context: ResourcePageContext,
|
|
|
|
|
|
) {
|
|
|
|
|
|
const rule = pageRules[definition.name];
|
|
|
|
|
|
if (mode === 'create') {
|
|
|
|
|
|
const hidden = new Set(rule?.createHiddenKeys ?? []);
|
|
|
|
|
|
return definition.fields.filter((field) => !hidden.has(field.key));
|
|
|
|
|
|
}
|
|
|
|
|
|
let editableKeys =
|
|
|
|
|
|
rule?.editKeys ?? definition.fields.map((field) => field.key);
|
|
|
|
|
|
if (definition.name === 'product_repair' && row.result !== 'pending') {
|
|
|
|
|
|
editableKeys = ['remark'];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (definition.name === 'platform_account' && context.role !== 'root') {
|
|
|
|
|
|
editableKeys = editableKeys.filter((key) => key !== 'platform_role_code');
|
|
|
|
|
|
}
|
|
|
|
|
|
const visibleKeys = new Set([
|
|
|
|
|
|
...editableKeys,
|
|
|
|
|
|
...(ownerKeys[definition.name] ?? []),
|
|
|
|
|
|
...definition.fields
|
|
|
|
|
|
.filter((field) =>
|
|
|
|
|
|
[
|
|
|
|
|
|
'username',
|
|
|
|
|
|
'code',
|
|
|
|
|
|
'delivery_code',
|
|
|
|
|
|
'producer_code',
|
|
|
|
|
|
'contract_no',
|
|
|
|
|
|
'role_code',
|
|
|
|
|
|
].includes(field.key),
|
|
|
|
|
|
)
|
|
|
|
|
|
.map((field) => field.key),
|
|
|
|
|
|
]);
|
|
|
|
|
|
return definition.fields.filter(
|
|
|
|
|
|
(field) =>
|
|
|
|
|
|
visibleKeys.has(field.key) &&
|
|
|
|
|
|
(field.type !== 'password' || editableKeys.includes(field.key)),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回编辑请求允许提交的字段。 */
|
|
|
|
|
|
export function editableFields(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
row: ResourceRow,
|
|
|
|
|
|
context: ResourcePageContext,
|
|
|
|
|
|
): ResourceField[] {
|
|
|
|
|
|
const rule = pageRules[definition.name];
|
|
|
|
|
|
let keys = rule?.editKeys ?? definition.fields.map((field) => field.key);
|
|
|
|
|
|
if (definition.name === 'product_repair' && row.result !== 'pending')
|
|
|
|
|
|
keys = ['remark'];
|
|
|
|
|
|
if (definition.name === 'platform_account' && context.role !== 'root') {
|
|
|
|
|
|
keys = keys.filter((key) => key !== 'platform_role_code');
|
|
|
|
|
|
}
|
|
|
|
|
|
const allowed = new Set(keys);
|
|
|
|
|
|
return definition.fields.filter((field) => allowed.has(field.key));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回更新协议需要提交的字段,其中只读归属字段仅原样回传。 */
|
|
|
|
|
|
export function updatePayloadFields(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
row: ResourceRow,
|
|
|
|
|
|
context: ResourcePageContext,
|
|
|
|
|
|
) {
|
|
|
|
|
|
const keys = new Set([
|
|
|
|
|
|
...editableFields(definition, row, context).map((field) => field.key),
|
|
|
|
|
|
...(pageRules[definition.name]?.editSubmitOnlyKeys ?? []),
|
|
|
|
|
|
]);
|
|
|
|
|
|
return definition.fields.filter((field) => keys.has(field.key));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 判断编辑字段在当前模式是否必填。 */
|
|
|
|
|
|
export function fieldRequired(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
field: ResourceField,
|
|
|
|
|
|
mode: Exclude<RecordPageMode, 'detail'>,
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (mode === 'create') return Boolean(field.required);
|
|
|
|
|
|
const optional = new Set(pageRules[definition.name]?.editOptionalKeys ?? []);
|
|
|
|
|
|
return Boolean(field.required && !optional.has(field.key));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 判断资源是否使用账户头像与账号摘要。 */
|
|
|
|
|
|
export function usesAccountSummary(definition: ResourceUiDefinition) {
|
|
|
|
|
|
return Boolean(pageRules[definition.name]?.accountSummary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回直达新建页时的阻止原因,空字符串代表允许新建。 */
|
|
|
|
|
|
export function createBlockedReason(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
context: ResourcePageContext,
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (!definition.canCreate) return '当前资源不支持新建';
|
|
|
|
|
|
if (
|
|
|
|
|
|
['platform_account', 'platform_role'].includes(definition.name) &&
|
|
|
|
|
|
context.role !== 'root'
|
|
|
|
|
|
) {
|
|
|
|
|
|
return '只有 root 可以新建平台账户或平台角色';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回直达编辑页时的阻止原因,空字符串代表允许编辑。 */
|
|
|
|
|
|
export function editBlockedReason(
|
|
|
|
|
|
definition: ResourceUiDefinition,
|
|
|
|
|
|
row: ResourceRow,
|
|
|
|
|
|
context: ResourcePageContext,
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (!definition.canEdit) return '当前资源不支持编辑';
|
2026-08-12 22:11:52 +08:00
|
|
|
|
if (definition.name === 'gasorder_contract') {
|
|
|
|
|
|
if (Number(row.contract_status) !== 0)
|
|
|
|
|
|
return '只有草稿状态的合同可以编辑';
|
|
|
|
|
|
if (![0, 1].includes(Number(row.status)))
|
|
|
|
|
|
return '停用、已归档或已冻结的合同不可编辑';
|
2026-08-11 00:49:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (definition.name === 'platform_role') {
|
|
|
|
|
|
if (context.role !== 'root') return '只有 root 可以编辑平台角色';
|
|
|
|
|
|
if (row.is_system === true) return '系统角色不允许编辑';
|
|
|
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
|
|
|
definition.name === 'platform_account' &&
|
|
|
|
|
|
context.role !== 'root' &&
|
|
|
|
|
|
String(row.identity ?? '') !== context.accountIdentity
|
|
|
|
|
|
) {
|
|
|
|
|
|
return '普通平台管理员只能编辑自己的账户资料';
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 校验每个列表型可编辑资源都有显式更新字段规则。 */
|
|
|
|
|
|
export function assertResourcePageRules(definitions: ResourceUiDefinition[]) {
|
|
|
|
|
|
const missing = definitions
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
(definition) => definition.pageKind === 'list' && definition.canEdit,
|
|
|
|
|
|
)
|
|
|
|
|
|
.filter((definition) => !pageRules[definition.name])
|
|
|
|
|
|
.map((definition) => definition.name);
|
|
|
|
|
|
if (missing.length)
|
|
|
|
|
|
throw new Error(`资源缺少编辑字段规则:${missing.join('、')}`);
|
|
|
|
|
|
}
|