/** * 功能:定义标准资源在新建、详情和编辑页面中的字段能力与动态编辑限制。 * 版本:v1.0.0 */ import type { ResourceField, ResourceUiDefinition } from './resources'; export type RecordPageMode = 'create' | 'detail' | 'edit'; export type ResourceRow = Record; export type ResourcePageContext = { role: string; accountIdentity: string; }; type ResourcePageRule = { createHiddenKeys?: string[]; editKeys: string[]; editOptionalKeys?: string[]; editSubmitOnlyKeys?: string[]; accountSummary?: boolean; }; /** 每个可编辑资源都显式声明更新字段,禁止把创建字段直接提交给更新接口。 */ const pageRules: Record = { gas_basic: { editKeys: [ 'name', 'credit_code', 'principal', 'address', 'longitude', 'latitude', ], }, gas_account: { editKeys: ['display_name', 'gas_basic_identity'], 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 = { 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, 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, ) { 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 '当前资源不支持编辑'; if (definition.name === 'gasorder_contract') { if (Number(row.contract_status) !== 0) return '只有草稿状态的合同可以编辑'; if (![0, 1].includes(Number(row.status))) return '停用、已归档或已冻结的合同不可编辑'; } 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('、')}`); }