export type ResourceMode = 'writable' | 'readonly' | 'append_only'; export type ResourcePageKind = 'list' | 'tree'; export type ResourceFieldType = | 'text' | 'password' | 'identity' | 'role-code' | 'menu-identities' | 'number' | 'boolean' | 'date' | 'datetime' | 'json' | 'textarea'; export type ResourceField = { key: string; label: string; type: ResourceFieldType; required?: boolean; }; export type DetailAction = { name: string; resource: string; fields: ResourceField[]; payload?: Record; }; export type ResourceUiDefinition = { key: string; name: string; resource: string; title: string; mode: ResourceMode; pageKind: ResourcePageKind; fields: ResourceField[]; requiredIdentities: string[]; detailActions?: DetailAction[]; }; const labels: Record = { code: '编码', name: '名称', credit_code: '统一信用代码', principal: '负责人', address: '地址', longitude: '经度', latitude: '纬度', username: '用户名', password: '密码', display_name: '显示名称', role_code: '角色编码', delivery_code: '配送编码', phone: '联系电话', avatar: '头像', work_status: '工作状态', credential_type: '资质类型', credential_no: '资质编号', expired_at: '到期时间', real_name: '实名姓名', is_default: '默认地址', device_no: '设备编号', model: '设备型号', online_status: '在线状态', effective_at: '生效时间', reported_at: '上报时间', payload: '遥测数据', rule_code: '规则编码', version_no: '版本号', threshold: '阈值', action: '处置动作', gray_scope: '灰度范围', event_code: '事件编码', level: '事件等级', title: '标题', sla_at: '处置时限', result: '检查结果', evidence_uri: '凭证地址', reason: '处置原因', sort_no: '排序号', product_code: '商品编码', price_amount: '售价', stock_quantity: '库存', value: '属性值', image_uri: '图片地址', is_cover: '封面图', quantity: '数量', selected: '是否选中', order_no: '订单号', total_amount: '订单金额', product_snapshot: '商品快照', sale_amount: '成交金额', score: '评分', channel: '渠道', amount: '金额', paid_at: '支付时间', settlement_no: '结算单号', subject_type: '结算对象类型', period_start: '结算开始时间', period_end: '结算结束时间', bill_date: '账单日期', difference_amount: '差异金额', content_type: '内容类型', body: '正文', publish_status: '发布状态', template_code: '模板编码', content: '内容', ticket_no: '工单号', category: '分类', priority: '优先级', platform_role_code: '平台角色', menu_identities: '菜单权限', data_scope: '数据范围', menu_code: '菜单编码', icon: '图标', path: '路径', balance_amount: '余额', frozen_amount: '冻结金额', balance_after: '变动后余额', report_code: '报表编码', report_type: '报表类型', stat_period: '统计周期', generated_at: '生成时间', dimension: '维度', metric_code: '指标编码', metric_value: '指标值', scope_type: '范围类型', stat_at: '统计时间', applicant_identity: '申请人标识', business_type: '业务类型', business_identity: '业务标识', handler_identity: '处理人标识', opinion: '审批意见', purpose: '用途', field_scope: '字段范围', approved_at: '批准时间', file_uri: '文件地址', operator_identity: '操作人标识', object_identity: '对象标识', resource_type: '资源类型', handled_at: '处理时间', created_at: '创建时间', status: '状态', }; const numberFields = new Set([ 'version_no', 'level', 'sort_no', 'price_amount', 'stock_quantity', 'quantity', 'total_amount', 'sale_amount', 'score', 'amount', 'difference_amount', 'balance_amount', 'frozen_amount', 'balance_after', ]); const booleanFields = new Set(['is_default', 'is_cover', 'selected']); const dateFields = new Set(['bill_date']); const datetimeFields = new Set([ 'expired_at', 'effective_at', 'reported_at', 'sla_at', 'occurred_at', 'started_at', 'completed_at', 'paid_at', 'period_start', 'period_end', 'generated_at', 'stat_at', 'approved_at', 'handled_at', 'created_at', ]); const jsonFields = new Set([ 'payload', 'threshold', 'gray_scope', 'product_snapshot', 'field_scope', ]); const textareaFields = new Set(['body', 'content', 'reason', 'opinion']); const fieldType = (key: string): ResourceFieldType => { if (key === 'platform_role_code') return 'role-code'; if (key === 'menu_identities') return 'menu-identities'; if (key.endsWith('_identity')) return 'identity'; if (key === 'password') return 'password'; if (numberFields.has(key)) return 'number'; if (booleanFields.has(key)) return 'boolean'; if (dateFields.has(key)) return 'date'; if (datetimeFields.has(key)) return 'datetime'; if (jsonFields.has(key)) return 'json'; if (textareaFields.has(key)) return 'textarea'; return 'text'; }; const field = (value: string): ResourceField => { const key = value.replace(/!$/, ''); return { key, label: labels[key] ?? (key.endsWith('_identity') ? '关联业务标识' : '业务字段'), type: fieldType(key), required: value.endsWith('!') || undefined, }; }; const titles: Record = { gas_basic: '气站管理', gas_account: '气站账户', delivery_basic: '配送点管理', delivery_account: '配送账户', delivery_task: '配送任务', delivery_track: '配送轨迹', delivery_track_point: '轨迹点', staff_account: '服务人员', staff_credential: '人员资质', user_account: '用户账户', user_address: '用户地址', user_service_relation: '用户服务关系', dev_smart_cylinder_valve: '智能钢瓶阀', dev_device_binding: '设备绑定', dev_telemetry: '设备遥测', saf_rule: '安全规则', saf_event: '安全事件', saf_inspection: '安全检查', saf_event_disposal: '事件处置', ec_category: '商品分类', ec_product: '商品管理', ec_product_attribute: '商品属性', ec_product_image: '商品图片', ec_cart: '购物车', ec_order: '订单管理', ec_order_item: '订单明细', ec_review: '商品评价', fin_payment: '支付记录', fin_settlement: '财务结算', fin_reconciliation: '财务对账', cnt_content: '内容管理', ntf_template: '通知模板', cs_ticket: '客服工单', platfrom_account: '平台账户', platform_role: '平台角色', platform_menu: '平台菜单', wallet: '钱包', wallet_ledger: '钱包流水', wallet_recharge: '钱包充值', wallet_withdrawal: '钱包提现', report: '报表', report_item: '报表项目', report_metric_snapshot: '指标快照', aud_operation_log: '操作审计', aud_export_log: '导出审计', aud_approval: '审批审计', }; const define = ( name: string, resource: string, mode: ResourceMode, pageKind: ResourcePageKind, keys: string[], detailActions?: DetailAction[], ): ResourceUiDefinition => { const fields = keys.map(field); return { key: name.replace(/_/g, '-'), name, resource, title: titles[name] ?? '业务资源', mode, pageKind, fields, requiredIdentities: fields .filter((item) => item.required && item.key.endsWith('_identity')) .map((item) => item.key), ...(detailActions ? { detailActions } : {}), }; }; const action = ( name: string, resource: string, keys: string[], payload?: Record, ): DetailAction => ({ name, resource, fields: keys.map(field), ...(payload ? { payload } : {}), }); /** Exact UI contract for every backend ExpectedResources entry. */ export const resources: ResourceUiDefinition[] = [ define('gas_basic', '/gas/gas_basic', 'writable', 'list', [ 'code!', 'name!', 'credit_code', 'principal', 'address', 'longitude', 'latitude', ]), define('gas_account', '/gas/gas_account', 'writable', 'list', [ 'username!', 'password!', 'display_name', 'role_code', 'gas_basic_identity!', ]), define('delivery_basic', '/delivery/delivery_basic', 'writable', 'list', [ 'delivery_code!', 'name!', 'gas_basic_identity', 'principal', 'address', ]), define('delivery_account', '/delivery/delivery_account', 'writable', 'list', [ 'username!', 'password!', 'display_name', 'role_code', 'delivery_basic_identity!', ]), define('delivery_task', '/delivery/delivery_task', 'writable', 'list', [ 'ec_order_identity!', 'delivery_basic_identity!', 'staff_account_identity', ]), define('delivery_track', '/delivery/delivery_track', 'writable', 'list', [ 'delivery_task_identity!', 'started_at', 'completed_at', ]), define( 'delivery_track_point', '/delivery/delivery_track_point', 'readonly', 'list', [ 'delivery_track_identity', 'point_type', 'occurred_at', 'longitude', 'latitude', ], ), define('staff_account', '/staff/account', 'writable', 'list', [ 'username!', 'password!', 'name!', 'phone', 'avatar', 'role_code', 'gas_basic_identity', 'delivery_basic_identity', 'work_status', ]), define('staff_credential', '/staff/credential', 'writable', 'list', [ 'staff_account_identity!', 'credential_type!', 'credential_no', 'expired_at', ]), define('user_account', '/user/account', 'writable', 'list', [ 'username!', 'password!', 'name!', 'phone', 'avatar', 'real_name', ]), define('user_address', '/user/address', 'writable', 'list', [ 'user_account_identity!', 'address!', 'longitude', 'latitude', 'is_default', ]), define( 'user_service_relation', '/user/service_relation', 'writable', 'list', [ 'user_account_identity!', 'gas_basic_identity', 'delivery_basic_identity', 'staff_account_identity', ], ), define( 'dev_smart_cylinder_valve', '/device/dev_smart_cylinder_valve', 'writable', 'list', ['device_no!', 'model', 'online_status', 'owner_identity'], ), define( 'dev_device_binding', '/device/dev_device_binding', 'writable', 'list', [ 'smart_cylinder_valve_identity!', 'user_account_identity!', 'effective_at', 'expired_at', ], ), define('dev_telemetry', '/device/dev_telemetry', 'readonly', 'list', [ 'smart_cylinder_valve_identity', 'reported_at', 'payload', ]), define('saf_rule', '/safety/saf_rule', 'writable', 'list', [ 'rule_code!', 'version_no', 'threshold', 'action', 'gray_scope', ]), define( 'saf_event', '/safety/saf_event', 'writable', 'list', [ 'event_code!', 'level!', 'title!', 'smart_cylinder_valve_identity!', 'sla_at', ], [ action('saf_event_disposal', '/safety/saf_event/:identity/disposals', [ 'action!', 'reason!', ]), ], ), define('saf_inspection', '/safety/saf_inspection', 'writable', 'list', [ 'user_account_identity!', 'staff_account_identity!', 'result!', 'evidence_uri', ]), define( 'saf_event_disposal', '/safety/saf_event/:identity/disposals', 'append_only', 'list', ['action!', 'reason!'], ), define('ec_category', '/ec/ec_category', 'writable', 'tree', [ 'parent_identity', 'name!', 'sort_no', ]), define('ec_product', '/ec/ec_product', 'writable', 'list', [ 'ec_category_identity!', 'product_code!', 'name!', 'price_amount!', 'stock_quantity', ]), define( 'ec_product_attribute', '/ec/ec_product_attribute', 'writable', 'list', ['ec_product_identity!', 'name!', 'value!', 'sort_no'], ), define('ec_product_image', '/ec/ec_product_image', 'writable', 'list', [ 'ec_product_identity!', 'image_uri!', 'sort_no', 'is_cover', ]), define('ec_cart', '/ec/ec_cart', 'writable', 'list', [ 'user_account_identity!', 'ec_product_identity!', 'quantity!', 'selected', ]), define('ec_order', '/ec/ec_order', 'writable', 'list', [ 'user_account_identity!', 'gas_basic_identity', 'delivery_basic_identity', 'order_no!', 'total_amount!', ]), define('ec_order_item', '/ec/ec_order_item', 'writable', 'list', [ 'ec_order_identity!', 'ec_product_identity!', 'product_snapshot!', 'quantity!', 'sale_amount!', ]), define('ec_review', '/ec/ec_review', 'writable', 'list', [ 'ec_order_identity!', 'ec_product_identity!', 'user_account_identity!', 'score!', 'content!', ]), define('fin_payment', '/finance/fin_payment', 'writable', 'list', [ 'ec_order_identity!', 'channel!', 'amount!', 'paid_at', ]), define('fin_settlement', '/finance/fin_settlement', 'writable', 'list', [ 'settlement_no!', 'subject_type!', 'subject_identity!', 'period_start!', 'period_end!', ]), define( 'fin_reconciliation', '/finance/fin_reconciliation', 'writable', 'list', ['channel!', 'bill_date!', 'difference_amount!'], ), define('cnt_content', '/content/cnt_content', 'writable', 'list', [ 'content_type!', 'title!', 'body!', 'version_no', 'publish_status', ]), define('ntf_template', '/notification/ntf_template', 'writable', 'list', [ 'template_code!', 'channel!', 'content!', ]), define('cs_ticket', '/customer_service/cs_ticket', 'writable', 'list', [ 'user_account_identity!', 'ticket_no!', 'category!', 'priority!', ]), define('platfrom_account', '/platform/platfrom_account', 'writable', 'list', [ 'username!', 'password!', 'display_name', 'avatar', 'platform_role_code!', 'phone', ]), define( 'platform_role', '/platform/platform_role', 'writable', 'list', ['role_code!', 'name!', 'data_scope!'], [ action( '分配菜单', '/platform/platform_role/:identity/menu', ['menu_identities!'], ), ], ), define('platform_menu', '/platform/platform_menu', 'writable', 'tree', [ 'parent_identity', 'menu_code!', 'name!', 'icon', 'path', 'sort_no', ]), define('wallet', '/wallet/wallet', 'readonly', 'list', [ 'owner_identity', 'balance_amount', 'frozen_amount', 'status', ]), define('wallet_ledger', '/wallet/wallet_ledger', 'readonly', 'list', [ 'wallet_identity', 'amount', 'balance_after', ]), define('wallet_recharge', '/wallet/wallet_recharge', 'readonly', 'list', [ 'wallet_identity', 'amount', 'status', ]), define('wallet_withdrawal', '/wallet/wallet_withdrawal', 'readonly', 'list', [ 'wallet_identity', 'amount', 'status', ]), define('report', '/report/report', 'readonly', 'list', [ 'report_code', 'report_type', 'stat_period', 'generated_at', 'status', ]), define('report_item', '/report/report_item', 'readonly', 'list', [ 'report_identity', 'dimension', 'metric_code', 'metric_value', ]), define( 'report_metric_snapshot', '/report/report_metric_snapshot', 'readonly', 'list', ['metric_code', 'scope_type', 'stat_at', 'metric_value'], ), define('aud_operation_log', '/audit/aud_operation_log', 'readonly', 'list', [ 'operator_identity', 'action', 'object_identity', 'created_at', ]), define('aud_export_log', '/audit/aud_export_log', 'readonly', 'list', [ 'applicant_identity', 'purpose', 'field_scope', 'approved_at', 'file_uri', ]), define( 'aud_approval', '/audit/aud_approval', 'readonly', 'list', [ 'business_type', 'business_identity', 'applicant_identity', 'opinion', 'handler_identity', 'status', 'handled_at', ], [ action('同意', '/audit/aud_approval/:identity/approve', ['opinion'], { status: 'approved', }), action('驳回', '/audit/aud_approval/:identity/approve', ['opinion!'], { status: 'rejected', }), ], ), ]; export const resourceByName = Object.fromEntries( resources.map((definition) => [definition.resource, definition]), ) as Record; export function getResource(resourcePath: string): ResourceUiDefinition { const definition = resourceByName[resourcePath]; if (!definition) throw new Error('Unknown resource: ' + resourcePath); return definition; }