diff --git a/docs/操作日志_配送点独立资源页面_20260822.md b/docs/操作日志_配送点独立资源页面_20260822.md new file mode 100644 index 0000000..d93b0ac --- /dev/null +++ b/docs/操作日志_配送点独立资源页面_20260822.md @@ -0,0 +1,56 @@ +# 操作日志:配送点独立资源页面 + +操作时间:2026-08-22 20:18:39 +操作类型:扩展、修改 +影响模块:配送点管理系统标准资源列表、独立记录页、前端路由与资源契约 + +## 操作前状态 + +配送点标准资源的新建、详情和编辑均在 `CrudListPage.vue` 抽屉中完成,没有稳定的记录 URL;支付和退款前端资源名与配送点后端实际路径不一致。 + +## 具体操作 + +1. 新增基于资源能力的路由生成器,为非单例资源生成详情、新建和编辑独立路由。 +2. 新增共享记录页、详情内容、字段表单、业务动作对话框和未保存离开保护。 +3. 新增标准列表页,将新建、详情和编辑入口改为路由跳转,并移除已停用的抽屉页面。 +4. 为可编辑资源增加后端更新字段白名单,保留受控合同编辑限制和危险操作二次确认。 +5. 将配送端支付、退款资源统一为 `payment_order`、`payment_refund`,同步后端契约快照。 +6. 增加页面能力矩阵自动检查,并更新项目文档。 +7. 依据 5173 页面重新对齐灰色页面底、面包屑、分区卡片、详情信息网格、双列表单和底部操作区。 + +## 操作后状态 + +17 类资源具备独立详情页,9 类资源具备独立新建页,5 类资源具备独立编辑页。列表查询来源可以恢复,新建和编辑保存后进入详情,未保存离开会提示确认。配送点资料仍使用专用只读卡片。 + +## 代码变更 + +- 新增:`src/views/resource/` 下 5 个记录页组件与组合函数。 +- 新增:`src/views/shared/ResourceListPage.vue`。 +- 新增:`src/router/routes/modules/resource-route-builder.ts`。 +- 新增:`src/api/resource-display.ts`、`resource-navigation.ts`、`resource-record-form.ts`。 +- 修改:`platform.ts`、`ResourcePage.vue`、`resources.ts`、路由类型、资源契约和包脚本。 +- 删除:`src/views/shared/CrudListPage.vue`(已无活动引用,可由 Git 恢复)。 + +## 行为变化 + +- 变更前:列表抽屉承载记录操作,浏览器地址不随记录变化。 +- 变更后:每个允许的操作拥有独立 URL,详情页集中承载业务动作。 +- 兼容性:现有列表菜单地址、后端接口和配送点数据范围保持不变。 + +## 验证结果 + +- `resource-pages:check`:通过,详情 17、新建 9、编辑 5。 +- `contract:check`:通过,18 个资源与后端契约一致。 +- `profile:check`:通过,资料专用只读页未回退。 +- `type:check`:通过。 +- `build`:通过,2622 个模块完成生产构建。 +- `go test ./internal/logic/delivery`:通过。 +- `lint`:通过;仅报告项目既有警告,未产生失败项。 +- 浏览器回归:工作人员列表、详情、编辑、正式新建地址、返回链路、未保存保护、配送订单新建页、支付列表均通过。 +- 布局回归:工作人员详情、编辑和配送订单新建页已在应用内浏览器逐页截图检查,与 5173 的页面结构和响应式断点一致。 + +## 风险评估 + +- 风险:后端新增可编辑字段后,前端白名单可能未同步。缓解:维护指南要求同步白名单并执行页面契约检查。 +- 风险:旧书签若直接指向抽屉状态无法恢复。缓解:原列表 URL 保持不变,新的记录 URL 可稳定收藏。 +- 风险:支付和退款名称修正可能影响依赖旧错误名称的自定义代码。缓解:仅配送端定义改用后端正式名称,菜单地址不变,并由契约检查持续保护。 diff --git a/docs/项目文档_配送点独立资源页面_v1.0.md b/docs/项目文档_配送点独立资源页面_v1.0.md new file mode 100644 index 0000000..74216a0 --- /dev/null +++ b/docs/项目文档_配送点独立资源页面_v1.0.md @@ -0,0 +1,76 @@ +# 项目文档:配送点独立资源页面 v1.0 + +## 1. 项目概述 + +本次将配送点管理系统中标准资源的新建、详情和编辑操作从列表抽屉迁移为独立 URL 页面。页面框架参考平台总后台的共享记录页模式,同时严格使用配送点 JWT 数据范围和配送点后端现有接口。 + +技术栈为 Vue 3、TypeScript、Vue Router、Arco Design Vue、Vite。运行环境要求 Node.js 20.19.0 及以上,前端默认访问地址为 `http://localhost:5176`。 + +## 2. 页面能力范围 + +- 17 类非单例资源提供独立详情页。 +- 9 类具有后端 POST 接口的资源提供独立新建页。 +- 5 类具有后端 PUT 接口的资源提供独立编辑页。 +- 配送点资料继续使用专用只读卡片,不生成记录子路由。 +- `/staff/add` 和 `/gasorder/create` 保留为正式新建地址,其余资源使用列表路径下的 `/new`。 +- 只读资源仅生成详情页;追加型资源生成新建和详情页,不生成编辑页。 + +## 3. 目录结构 + +```text +frontend/delivery_admin/ +├── scripts/ +│ └── check-resource-pages.mjs # 校验页面矩阵与后端 CRUD 契约 +└── src/ + ├── api/ + │ ├── resource-display.ts # 中文字段、状态和详情值展示 + │ ├── resource-navigation.ts # 独立页面路由和安全返回地址 + │ └── resource-record-form.ts # 表单初始化、字段白名单和校验 + ├── router/routes/modules/ + │ └── resource-route-builder.ts # 根据资源能力生成独立路由 + ├── views/resource/ + │ ├── ResourceRecordPage.vue # 新建、详情、编辑三态容器 + │ ├── ResourceDetailContent.vue # 主记录和集合详情展示 + │ ├── ResourceFieldForm.vue # 通用资源字段表单 + │ ├── ResourceActionDialog.vue # 详情业务动作与危险确认 + │ └── use-unsaved-record.ts # 未保存离开保护 + └── views/shared/ + └── ResourceListPage.vue # 跳转独立页面的标准列表 +``` + +## 4. 核心实现 + +`resource-route-builder.ts` 根据资源定义中的 `canCreate`、`canEdit` 和页面类型生成隐藏子路由。每条记录拥有稳定 URL,列表查询参数通过 `return_to` 保存,详情返回后可恢复来源列表状态。 + +`ResourceRecordPage.vue` 通过路由元数据 `recordMode` 在新建、详情、编辑三态间切换。新建成功进入详情,编辑成功返回详情;未保存表单离开时弹出确认。合同等受控资源的业务动作位于详情页,危险操作继续二次确认。 + +页面布局与平台总后台保持一致:使用灰色页面背景、顶部面包屑和操作栏;详情由“基本信息”“关联记录”“业务操作”分区卡片组成;新建和编辑使用双列响应式表单,并将保存、取消操作固定在表单内容底部。 + +`resource-record-form.ts` 为五类可编辑资源声明后端更新字段白名单,防止用户名、合同编号等只读字段出现在编辑页或被无效提交。 + +支付与退款资源统一使用后端正式名称 `payment_order`、`payment_refund`,菜单地址仍保持 `/finance/payments`、`/finance/refunds`,避免接口路径不一致导致 404。 + +## 5. 维护指南 + +新增标准资源时,先同步后端契约,再在 `resources.ts` 中配置资源字段和能力。若后端支持编辑,必须同步维护 `resource-record-form.ts` 的更新字段白名单。完成后运行: + +```powershell +npm.cmd run contract:sync +npm.cmd run resource-pages:check +npm.cmd run type:check +npm.cmd run build +``` + +不要为只读或追加型资源手工开放编辑路由。单例资料、树形页面或强业务定制页面应继续使用专用组件,不纳入通用记录页生成器。 + +## 6. 变更记录 + +- 新增共享独立记录页、标准列表页、路由生成器和未保存保护。 +- 删除已停用的抽屉式 `CrudListPage.vue`。 +- 修复支付、退款资源与后端契约名称不一致的问题。 +- 新增 17/9/5 页面能力矩阵自动检查。 +- 保持配送点资料专用只读页面和现有公共后端接口不变。 + +## 7. 已知边界 + +独立页面只使用配送端已存在的关系查询和 CRUD 能力,不引入平台端头像上传、合同附件上传、平台角色或跨组织账户摘要接口。普通头像和附件 URI 字段仍按原后端文本契约展示或填写。 diff --git a/frontend/delivery_admin/package.json b/frontend/delivery_admin/package.json index 7ee3b66..92bfc75 100644 --- a/frontend/delivery_admin/package.json +++ b/frontend/delivery_admin/package.json @@ -14,6 +14,7 @@ "contract:sync": "node scripts/sync-backend-contract.mjs", "contract:check": "node scripts/check-backend-contract.mjs", "profile:check": "node scripts/check-delivery-profile-page.mjs", + "resource-pages:check": "node scripts/check-resource-pages.mjs", "audit:platform": "node scripts/check-backend-contract.mjs", "lint": "biome lint .", "lint:fix": "biome lint --write .", diff --git a/frontend/delivery_admin/scripts/check-resource-pages.mjs b/frontend/delivery_admin/scripts/check-resource-pages.mjs new file mode 100644 index 0000000..27c1d11 --- /dev/null +++ b/frontend/delivery_admin/scripts/check-resource-pages.mjs @@ -0,0 +1,64 @@ +/** + * 功能描述:校验配送点资源的独立详情、新建、编辑页面与后端契约保持一致。 + * 版本:v1.0.0。 + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const contract = JSON.parse(fs.readFileSync( + path.join(projectRoot, 'src/contracts/delivery-resources.json'), + 'utf8', +)); +const read = (relativePath) => fs.readFileSync(path.join(projectRoot, relativePath), 'utf8'); +const routeKeys = new Set(contract.routes.map((route) => `${route.method} ${route.path}`)); +const listResources = contract.resources.filter( + (resource) => resource.pageKind === 'list' && resource.name !== 'delivery_profile', +); +const creatable = new Set([ + 'staff_account', 'staff_credential', 'user_account', 'user_address', + 'gasorder_contract', 'gasorder_contract_product', 'gasorder_basic', + 'wallet_recharge', 'wallet_apply_cash', +]); +const editable = new Set([ + 'staff_account', 'staff_credential', 'user_account', 'user_address', 'gasorder_contract', +]); + +/** 在条件不成立时中止检查,并给出可直接定位的原因。 */ +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +for (const resource of listResources) { + assert( + routeKeys.has(`GET ${resource.path}/:identity`), + `${resource.name} 缺少详情接口`, + ); + if (creatable.has(resource.name)) { + assert(routeKeys.has(`POST ${resource.path}`), `${resource.name} 缺少新建接口`); + } + if (editable.has(resource.name)) { + assert(routeKeys.has(`PUT ${resource.path}/:identity`), `${resource.name} 缺少编辑接口`); + } +} + +assert(listResources.length === 17, `详情页面资源应为 17 个,当前为 ${listResources.length}`); +assert(creatable.size === 9, `新建页面资源应为 9 个,当前为 ${creatable.size}`); +assert(editable.size === 5, `编辑页面资源应为 5 个,当前为 ${editable.size}`); + +const builder = read('src/router/routes/modules/resource-route-builder.ts'); +const platform = read('src/router/routes/modules/platform.ts'); +const resourcePage = read('src/views/shared/ResourcePage.vue'); +const definitions = read('src/api/resources.ts'); +assert(builder.includes("recordRoute('detail', ':identity'"), '路由生成器未创建详情页'); +assert(builder.includes("recordRoute('create', 'new'"), '路由生成器未创建新建页'); +assert(builder.includes("recordRoute('edit', ':identity/edit'"), '路由生成器未创建编辑页'); +assert(platform.includes("createChild('add'"), '工作人员正式新建路由必须保留 /staff/add'); +assert(platform.includes("createChild('create'"), '配送订单正式新建路由必须保留 /gasorder/create'); +assert(resourcePage.includes('ResourceListPage'), '标准资源列表尚未切换到独立页面导航'); +assert(!resourcePage.includes('CrudListPage'), '活动列表仍依赖抽屉式 CrudListPage'); +assert(definitions.includes("define('payment_order'"), '支付资源必须使用后端名称 payment_order'); +assert(definitions.includes("define('payment_refund'"), '退款资源必须使用后端名称 payment_refund'); + +console.log(`独立资源页面契约通过:详情 ${listResources.length},新建 ${creatable.size},编辑 ${editable.size}`); diff --git a/frontend/delivery_admin/src/api/resource-display.ts b/frontend/delivery_admin/src/api/resource-display.ts new file mode 100644 index 0000000..899f8d4 --- /dev/null +++ b/frontend/delivery_admin/src/api/resource-display.ts @@ -0,0 +1,86 @@ +/** + * 功能描述:统一配送点资源独立页面的字段名称和值展示。 + * 版本:v1.0.0。 + */ +import dayjs from 'dayjs'; +import { resourceFieldLabel, type ResourceUiDefinition } from './resources'; +import type { ResourceRow } from './resource-record-form'; + +const aliases: Record = { + id: 'ID', + identity: '系统唯一标识', + created_at: '创建时间', + updated_at: '更新时间', + items: '订单明细', + assignments: '分配记录', + statuses: '状态记录', + tracks: '运行轨迹', + confirmations: '确认记录', + payments: '支付记录', + revisions: '修订记录', + products: '合同气瓶', +}; + +const moneyKeys = new Set([ + 'amount', 'unit_price', 'balance', 'withdrawal_balance', 'delivery_fee', + 'discount_amount', 'total_amount', 'default_delivery_fee', 'fee', +]); + +/** 返回聚合详情中的主记录。 */ +export function primaryRecord(detail: ResourceRow): ResourceRow { + for (const key of ['order', 'contract', 'track', 'user']) { + if (detail[key] && typeof detail[key] === 'object') return detail[key] as ResourceRow; + } + return detail; +} + +/** 返回字段中文名称。 */ +export function detailFieldLabel(key: string) { + return aliases[key] ?? resourceFieldLabel(key); +} + +/** 返回通用记录状态名称。 */ +export function recordStatusLabel(status: number) { + return { 0: '待审核', 1: '启用', 2: '停用', 3: '已归档', 4: '已冻结' }[status] ?? '未知状态'; +} + +/** 返回通用记录状态颜色。 */ +export function recordStatusColor(status: number) { + return { 0: 'orange', 1: 'green', 2: 'red', 3: 'gray', 4: 'purple' }[status] ?? 'gray'; +} + +/** 将接口字段值转换为中文可读内容。 */ +export function displayResourceValue( + definition: ResourceUiDefinition, + key: string, + value: unknown, +) { + if (value == null || value === '') return '-'; + const field = definition.fields.find((item) => item.key === key); + const option = field?.options?.find((item) => String(item.value) === String(value)); + if (option) return option.label; + if (key === 'role_code') { + return { delivery: '配送人员', installer: '安装人员', operations: '运维人员' }[String(value)] + ?? String(value); + } + if (typeof value === 'boolean') return value ? '是' : '否'; + if (key === 'status') return recordStatusLabel(Number(value)); + if (moneyKeys.has(key) || key.endsWith('_amount') || key.endsWith('_balance_after')) { + const amount = Number(value); + return Number.isFinite(amount) ? `¥${(amount / 100).toFixed(2)}` : String(value); + } + if (key.endsWith('_at') || key === 'created_at' || key === 'updated_at') { + const date = dayjs(String(value)); + return date.isValid() ? date.format('YYYY-MM-DD HH:mm:ss') : String(value); + } + if (typeof value === 'object') return JSON.stringify(value, null, 2); + return String(value); +} + +/** 返回合同编辑阻止原因。 */ +export function recordEditReason(definition: ResourceUiDefinition, row: ResourceRow) { + if (definition.name !== 'gasorder_contract') return ''; + if (Number(row.contract_status) !== 0) return '只有草稿状态的合同可以编辑'; + if (![0, 1].includes(Number(row.status))) return '停用、已归档或已冻结的合同不可编辑'; + return ''; +} diff --git a/frontend/delivery_admin/src/api/resource-navigation.ts b/frontend/delivery_admin/src/api/resource-navigation.ts new file mode 100644 index 0000000..3baaa12 --- /dev/null +++ b/frontend/delivery_admin/src/api/resource-navigation.ts @@ -0,0 +1,39 @@ +/** + * 功能描述:构造配送点资源列表与独立记录页之间的安全导航地址。 + * 版本:v1.0.0。 + */ +import type { RouteLocationRaw, Router } from 'vue-router'; + +export type RecordNavigationMode = 'create' | 'detail' | 'edit'; + +/** 只接受站内绝对路径,防止 return_to 触发外部跳转。 */ +export function safeReturnPath(value: unknown) { + if ( + typeof value !== 'string' || + !value.startsWith('/') || + value.startsWith('//') || + value.includes('\\') || + /[\r\n]/.test(value) + ) + return ''; + return value; +} + +/** 返回指定资源记录模式对应的命名路由。 */ +export function recordRouteLocation( + listRouteName: string, + mode: RecordNavigationMode, + identity: string, + returnTo: string, +): RouteLocationRaw { + return { + name: `${listRouteName}-${mode}`, + ...(mode === 'create' ? {} : { params: { identity } }), + query: returnTo ? { return_to: returnTo } : {}, + }; +} + +/** 返回来源列表;没有安全来源时回退到列表命名路由。 */ +export function returnToList(router: Router, returnPath: string, listRouteName: string) { + return router.push(returnPath || { name: listRouteName }); +} diff --git a/frontend/delivery_admin/src/api/resource-record-form.ts b/frontend/delivery_admin/src/api/resource-record-form.ts new file mode 100644 index 0000000..db67e48 --- /dev/null +++ b/frontend/delivery_admin/src/api/resource-record-form.ts @@ -0,0 +1,72 @@ +/** + * 功能描述:初始化与校验配送点资源独立页面表单。 + * 版本:v1.0.0。 + */ +import { isMissingField } from './resource-form'; +import type { ResourceField } from './resources'; + +export type ResourceRow = Record; + +const editableFieldKeys: Record> = { + staff_account: new Set(['name', 'phone', 'avatar', 'work_status']), + staff_credential: new Set(['staff_account_identity', 'credential_type', 'credential_no', 'expired_at']), + user_account: new Set(['name', 'phone', 'avatar', 'real_name']), + user_address: new Set(['user_account_identity', 'address', 'longitude', 'latitude', 'is_default']), + gasorder_contract: new Set([ + 'title', 'terms', 'file_uri', 'default_delivery_fee', + 'signed_at', 'effective_at', 'expired_at', + ]), +}; + +/** 按后端更新契约返回当前页面允许提交的字段,避免展示无效的只读字段。 */ +export function recordFormFields( + resourceName: string, + fields: ResourceField[], + mode: 'create' | 'edit', +) { + const available = fields.filter((field) => field.key !== 'identity'); + if (mode === 'create') return available; + const editable = editableFieldKeys[resourceName]; + return editable + ? available.filter((field) => editable.has(field.key)) + : available.filter((field) => field.type !== 'password'); +} + +/** 使用详情快照初始化表单,并应用来源关系预填。 */ +export function resetResourceRecordForm( + form: Record, + fields: ResourceField[], + row: ResourceRow, + prefill: { relationKey?: string; ownerIdentity?: string }, +) { + for (const key of Object.keys(form)) delete form[key]; + for (const field of fields) { + const value = row[field.key]; + form[field.key] = + value == null + ? field.defaultValue + : field.type === 'money' + ? Number(value) / 100 + : value; + } + if (prefill.relationKey && prefill.ownerIdentity) + form[prefill.relationKey] = prefill.ownerIdentity; +} + +/** 返回表单首个校验错误;空字符串表示可以提交。 */ +export function validateResourceRecordForm( + form: Record, + fields: ResourceField[], + mode: 'create' | 'edit', +) { + const required = fields.filter( + (field) => field.required && !(mode === 'edit' && field.type === 'password'), + ); + if (required.some((field) => isMissingField(form[field.key]))) return '请填写必填字段'; + const password = fields.find( + (field) => field.type === 'password' && !isMissingField(form[field.key]), + ); + if (password && Array.from(String(form[password.key])).length < 6) + return '密码长度不能少于 6 个字符'; + return ''; +} diff --git a/frontend/delivery_admin/src/api/resources.ts b/frontend/delivery_admin/src/api/resources.ts index 6503b3d..50f8ffd 100644 --- a/frontend/delivery_admin/src/api/resources.ts +++ b/frontend/delivery_admin/src/api/resources.ts @@ -1,3 +1,7 @@ +/** + * 功能描述:定义配送点后台资源能力、字段契约和受控业务动作。 + * 版本:v1.1.0。 + */ export type ResourceMode = | 'writable' | 'readonly' @@ -234,6 +238,12 @@ const fieldLabels: Record = { withdrawable: '计入可提现余额', }; +/** 返回资源字段的中文名称,未知字段保留原键用于排障。 */ +export function resourceFieldLabel(key: string) { + const normalized = key.endsWith('_masked') ? key.slice(0, -7) : key; + return fieldLabels[normalized] ?? normalized; +} + const numbers = new Set([ 'sort_no', 'stock_quantity', 'quantity', 'score', 'version_no', 'attempt_no', 'target_product_status', @@ -525,9 +535,9 @@ const deliveryOverrides: ResourceUiDefinition[] = [ ]), define('wallet_basic', '钱包', 'readonly', []), define('wallet_bank', '银行卡', 'readonly', []), - define('wallet_payment', '支付记录', 'readonly', []), + define('payment_order', '支付记录', 'readonly', []), define('wallet_record', '钱包流水', 'readonly', []), - define('wallet_refund', '退款记录', 'readonly', []), + define('payment_refund', '退款记录', 'readonly', []), define('wallet_recharge', '钱包充值', 'append_only', [f('request_no', { required: true }), f('amount', { required: true }), ...reason, f('remark')]), define('wallet_apply_cash', '提现申请', 'append_only', [relation('wallet_bank_identity', '/wallet_bank'), f('request_no', { required: true }), f('amount', { required: true }), withdrawalChannelField, f('remark')]), define('fin_settlement', '结算结果', 'readonly', []), diff --git a/frontend/delivery_admin/src/contracts/delivery-resources.json b/frontend/delivery_admin/src/contracts/delivery-resources.json index bce36f2..a0c2b01 100644 --- a/frontend/delivery_admin/src/contracts/delivery-resources.json +++ b/frontend/delivery_admin/src/contracts/delivery-resources.json @@ -1 +1 @@ -{"resources":[{"domain":"profile","name":"delivery_profile","path":"/delivery_profile","pageKind":"list","mode":"readonly"},{"domain":"staff","name":"staff_account","path":"/staff_account","pageKind":"list","mode":"writable"},{"domain":"staff","name":"staff_credential","path":"/staff_credential","pageKind":"list","mode":"writable"},{"domain":"user","name":"user_account","path":"/user_account","pageKind":"list","mode":"writable"},{"domain":"user","name":"user_address","path":"/user_address","pageKind":"list","mode":"writable"},{"domain":"contract","name":"gasorder_contract","path":"/gasorder_contract","pageKind":"list","mode":"managed"},{"domain":"contract","name":"gasorder_contract_product","path":"/gasorder_contract_product","pageKind":"list","mode":"append_only"},{"domain":"contract","name":"gasorder_contract_revision","path":"/gasorder_contract_revision","pageKind":"list","mode":"readonly"},{"domain":"contract","name":"product_info","path":"/product_info","pageKind":"list","mode":"readonly"},{"domain":"gasorder","name":"gasorder_basic","path":"/gasorder_basic","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"wallet_basic","path":"/wallet_basic","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_bank","path":"/wallet_bank","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_payment","path":"/wallet_payment","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_record","path":"/wallet_record","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_refund","path":"/wallet_refund","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_recharge","path":"/wallet_recharge","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"wallet_apply_cash","path":"/wallet_apply_cash","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"fin_settlement","path":"/fin_settlement","pageKind":"list","mode":"readonly"}],"routes":[{"method":"POST","path":"/gasorder_basic"},{"method":"POST","path":"/gasorder_basic/:identity/assign"},{"method":"POST","path":"/gasorder_basic/:identity/adjust-amount"},{"method":"POST","path":"/gasorder_basic/:identity/reclaim"},{"method":"POST","path":"/gasorder_basic/:identity/recover"},{"method":"POST","path":"/gasorder_basic/:identity/exception"},{"method":"POST","path":"/gasorder_basic/:identity/cancel"},{"method":"POST","path":"/gasorder_contract"},{"method":"POST","path":"/gasorder_contract/:identity/activate"},{"method":"POST","path":"/gasorder_contract/:identity/renew"},{"method":"POST","path":"/gasorder_contract/:identity/terminate"},{"method":"POST","path":"/gasorder_contract_product"},{"method":"POST","path":"/gasorder_contract_product/:identity/unbind"},{"method":"POST","path":"/wallet_basic/recharge"},{"method":"POST","path":"/wallet_recharge"},{"method":"POST","path":"/wallet_apply_cash"},{"method":"POST","path":"/staff_account"},{"method":"POST","path":"/staff_credential"},{"method":"POST","path":"/user_account"},{"method":"POST","path":"/user_address"},{"method":"POST","path":"/auth/login"},{"method":"GET","path":"/wallet_recharge"},{"method":"GET","path":"/wallet_recharge/:identity"},{"method":"GET","path":"/wallet_record"},{"method":"GET","path":"/wallet_record/:identity"},{"method":"GET","path":"/wallet_refund"},{"method":"GET","path":"/wallet_refund/:identity"},{"method":"GET","path":"/wallet_basic"},{"method":"GET","path":"/wallet_basic/:identity"},{"method":"GET","path":"/wallet_bank"},{"method":"GET","path":"/wallet_bank/:identity"},{"method":"GET","path":"/wallet_payment"},{"method":"GET","path":"/wallet_payment/:identity"},{"method":"GET","path":"/wallet_apply_cash"},{"method":"GET","path":"/wallet_apply_cash/:identity"},{"method":"GET","path":"/gasorder_contract"},{"method":"GET","path":"/gasorder_contract_product"},{"method":"GET","path":"/gasorder_contract_product/:identity"},{"method":"GET","path":"/gasorder_contract_revision"},{"method":"GET","path":"/gasorder_contract_revision/:identity"},{"method":"GET","path":"/gasorder_contract/:identity"},{"method":"GET","path":"/gasorder_basic"},{"method":"GET","path":"/gasorder_basic/:identity"},{"method":"GET","path":"/dashboard/overview"},{"method":"GET","path":"/dashboard/reports"},{"method":"GET","path":"/delivery_menu"},{"method":"GET","path":"/delivery_profile"},{"method":"GET","path":"/staff_account"},{"method":"GET","path":"/staff_account/:identity"},{"method":"GET","path":"/staff_credential"},{"method":"GET","path":"/staff_credential/:identity"},{"method":"GET","path":"/user_account"},{"method":"GET","path":"/user_account/:identity"},{"method":"GET","path":"/user_address"},{"method":"GET","path":"/user_address/:identity"},{"method":"GET","path":"/product_info"},{"method":"GET","path":"/product_info/:identity"},{"method":"GET","path":"/fin_settlement"},{"method":"GET","path":"/fin_settlement/:identity"},{"method":"GET","path":"/auth/profile"},{"method":"GET","path":"/invitation/qrcode"},{"method":"PUT","path":"/staff_account/:identity"},{"method":"PUT","path":"/staff_account/:identity/password"},{"method":"PUT","path":"/staff_credential/:identity"},{"method":"PUT","path":"/user_account/:identity"},{"method":"PUT","path":"/user_account/:identity/password"},{"method":"PUT","path":"/user_address/:identity"},{"method":"PUT","path":"/auth/password"},{"method":"PUT","path":"/gasorder_contract/:identity"},{"method":"PATCH","path":"/staff_account/:identity/status"},{"method":"PATCH","path":"/staff_credential/:identity/status"},{"method":"PATCH","path":"/user_account/:identity/status"},{"method":"PATCH","path":"/user_address/:identity/status"},{"method":"DELETE","path":"/staff_account/:identity"},{"method":"DELETE","path":"/staff_credential/:identity"},{"method":"DELETE","path":"/user_account/:identity"},{"method":"DELETE","path":"/user_address/:identity"}]} +{"resources":[{"domain":"profile","name":"delivery_profile","path":"/delivery_profile","pageKind":"list","mode":"readonly"},{"domain":"staff","name":"staff_account","path":"/staff_account","pageKind":"list","mode":"writable"},{"domain":"staff","name":"staff_credential","path":"/staff_credential","pageKind":"list","mode":"writable"},{"domain":"user","name":"user_account","path":"/user_account","pageKind":"list","mode":"writable"},{"domain":"user","name":"user_address","path":"/user_address","pageKind":"list","mode":"writable"},{"domain":"contract","name":"gasorder_contract","path":"/gasorder_contract","pageKind":"list","mode":"managed"},{"domain":"contract","name":"gasorder_contract_product","path":"/gasorder_contract_product","pageKind":"list","mode":"append_only"},{"domain":"contract","name":"gasorder_contract_revision","path":"/gasorder_contract_revision","pageKind":"list","mode":"readonly"},{"domain":"contract","name":"product_info","path":"/product_info","pageKind":"list","mode":"readonly"},{"domain":"gasorder","name":"gasorder_basic","path":"/gasorder_basic","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"wallet_basic","path":"/wallet_basic","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_bank","path":"/wallet_bank","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"payment_order","path":"/payment_order","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_record","path":"/wallet_record","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"payment_refund","path":"/payment_refund","pageKind":"list","mode":"readonly"},{"domain":"finance","name":"wallet_recharge","path":"/wallet_recharge","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"wallet_apply_cash","path":"/wallet_apply_cash","pageKind":"list","mode":"append_only"},{"domain":"finance","name":"fin_settlement","path":"/fin_settlement","pageKind":"list","mode":"readonly"}],"routes":[{"method":"POST","path":"/gasorder_basic"},{"method":"POST","path":"/gasorder_basic/:identity/assign"},{"method":"POST","path":"/gasorder_basic/:identity/adjust-amount"},{"method":"POST","path":"/gasorder_basic/:identity/reclaim"},{"method":"POST","path":"/gasorder_basic/:identity/recover"},{"method":"POST","path":"/gasorder_basic/:identity/exception"},{"method":"POST","path":"/gasorder_basic/:identity/cancel"},{"method":"POST","path":"/gasorder_contract"},{"method":"POST","path":"/gasorder_contract/:identity/activate"},{"method":"POST","path":"/gasorder_contract/:identity/renew"},{"method":"POST","path":"/gasorder_contract/:identity/terminate"},{"method":"POST","path":"/gasorder_contract_product"},{"method":"POST","path":"/gasorder_contract_product/:identity/unbind"},{"method":"POST","path":"/wallet_basic/recharge"},{"method":"POST","path":"/wallet_recharge"},{"method":"POST","path":"/wallet_apply_cash"},{"method":"POST","path":"/staff_account"},{"method":"POST","path":"/staff_credential"},{"method":"POST","path":"/user_account"},{"method":"POST","path":"/user_address"},{"method":"POST","path":"/auth/login"},{"method":"GET","path":"/wallet_basic"},{"method":"GET","path":"/wallet_basic/:identity"},{"method":"GET","path":"/wallet_bank"},{"method":"GET","path":"/wallet_bank/:identity"},{"method":"GET","path":"/wallet_recharge"},{"method":"GET","path":"/wallet_recharge/:identity"},{"method":"GET","path":"/wallet_record"},{"method":"GET","path":"/wallet_record/:identity"},{"method":"GET","path":"/wallet_apply_cash"},{"method":"GET","path":"/wallet_apply_cash/:identity"},{"method":"GET","path":"/gasorder_contract"},{"method":"GET","path":"/gasorder_contract_product"},{"method":"GET","path":"/gasorder_contract_product/:identity"},{"method":"GET","path":"/gasorder_contract_revision"},{"method":"GET","path":"/gasorder_contract_revision/:identity"},{"method":"GET","path":"/gasorder_contract/:identity"},{"method":"GET","path":"/gasorder_basic"},{"method":"GET","path":"/gasorder_basic/:identity"},{"method":"GET","path":"/payment_order"},{"method":"GET","path":"/payment_order/:identity"},{"method":"GET","path":"/payment_refund"},{"method":"GET","path":"/payment_refund/:identity"},{"method":"GET","path":"/product_info"},{"method":"GET","path":"/product_info/:identity"},{"method":"GET","path":"/dashboard/overview"},{"method":"GET","path":"/dashboard/reports"},{"method":"GET","path":"/delivery_menu"},{"method":"GET","path":"/delivery_profile"},{"method":"GET","path":"/staff_account"},{"method":"GET","path":"/staff_account/:identity"},{"method":"GET","path":"/staff_credential"},{"method":"GET","path":"/staff_credential/:identity"},{"method":"GET","path":"/user_account"},{"method":"GET","path":"/user_account/:identity"},{"method":"GET","path":"/user_address"},{"method":"GET","path":"/user_address/:identity"},{"method":"GET","path":"/fin_settlement"},{"method":"GET","path":"/fin_settlement/:identity"},{"method":"GET","path":"/auth/profile"},{"method":"GET","path":"/invitation/qrcode"},{"method":"PUT","path":"/staff_account/:identity"},{"method":"PUT","path":"/staff_account/:identity/password"},{"method":"PUT","path":"/staff_credential/:identity"},{"method":"PUT","path":"/user_account/:identity"},{"method":"PUT","path":"/user_account/:identity/password"},{"method":"PUT","path":"/user_address/:identity"},{"method":"PUT","path":"/auth/password"},{"method":"PUT","path":"/gasorder_contract/:identity"},{"method":"PATCH","path":"/staff_account/:identity/status"},{"method":"PATCH","path":"/staff_credential/:identity/status"},{"method":"PATCH","path":"/user_account/:identity/status"},{"method":"PATCH","path":"/user_address/:identity/status"},{"method":"DELETE","path":"/staff_account/:identity"},{"method":"DELETE","path":"/staff_credential/:identity"},{"method":"DELETE","path":"/user_account/:identity"},{"method":"DELETE","path":"/user_address/:identity"}]} diff --git a/frontend/delivery_admin/src/router/routes/modules/platform.ts b/frontend/delivery_admin/src/router/routes/modules/platform.ts index 3a499d0..25a61df 100644 --- a/frontend/delivery_admin/src/router/routes/modules/platform.ts +++ b/frontend/delivery_admin/src/router/routes/modules/platform.ts @@ -1,15 +1,10 @@ +/** + * 功能描述:配置配送点后台菜单、列表与独立记录页面路由。 + * 版本:v1.1.0。 + */ import { DEFAULT_LAYOUT } from '../base'; import type { AppRouteRecordRaw } from '../types'; - -const resourcePage = () => import('@/views/shared/ResourcePage.vue'); -const child = (domain: string, path: string, title: string, resource: string, menuCode: string, meta: Record = {}): AppRouteRecordRaw => ({ - path, name: `${domain}-${path}`, component: resourcePage, - meta: { title, resource, requiresAuth: true, menuCode, ...meta }, -}); -const group = (path: string, name: string, title: string, icon: string, order: number, children: AppRouteRecordRaw[]): AppRouteRecordRaw => ({ - path: `/${path}`, name, component: DEFAULT_LAYOUT, redirect: `/${path}/${children[0].path}`, - meta: { title, requiresAuth: true, icon, order, menuCode: name }, children, -}); +import { child, createChild, group } from './resource-route-builder'; const routes: AppRouteRecordRaw[] = [ { path: '/dashboard', name: 'dashboard', component: DEFAULT_LAYOUT, redirect: '/dashboard/overview', meta: { title: '数据概览', requiresAuth: true, icon: 'icon-dashboard', order: 10, menuCode: 'dashboard', hideInMenu: true }, children: [ @@ -20,7 +15,7 @@ const routes: AppRouteRecordRaw[] = [ child('profile', 'basic', '本点资料', '/delivery_profile', 'delivery_profile'), ]), group('staff', 'staff', '配送人员管理', 'icon-user-group', 30, [ - child('staff', 'add', '新增配送人员', '/staff_account', 'staff_add', { createMode: true }), + createChild('add', '新增配送人员', '/staff_account', 'staff_add', 'staff-delivery'), child('staff', 'delivery', '配送人员列表', '/staff_account', 'staff_delivery'), child('staff', 'credentials', '人员资质', '/staff_credential', 'staff', { hideInMenu: true, activeMenu: 'staff-delivery' }), ]), @@ -35,15 +30,15 @@ const routes: AppRouteRecordRaw[] = [ child('contract', 'candidates', '合同可选气瓶', '/product_info', 'gasorder_contract', { hideInMenu: true, activeMenu: 'contract-contracts' }), ]), group('gasorder', 'gasorder', '配送订单', 'icon-list', 60, [ - child('gasorder', 'create', '创建订单', '/gasorder_basic', 'gasorder_create', { createMode: true }), + createChild('create', '创建订单', '/gasorder_basic', 'gasorder_create', 'gasorder-orders'), child('gasorder', 'orders', '订单列表', '/gasorder_basic', 'gasorder_basic'), ]), group('finance', 'finance', '财务管理', 'icon-bar-chart', 70, [ child('finance', 'wallet', '钱包', '/wallet_basic', 'wallet_basic'), child('finance', 'banks', '银行卡', '/wallet_bank', 'wallet_bank'), - child('finance', 'payments', '支付记录', '/wallet_payment', 'wallet_payment'), + child('finance', 'payments', '支付记录', '/payment_order', 'payment_order'), child('finance', 'records', '钱包流水', '/wallet_record', 'wallet_record'), - child('finance', 'refunds', '退款记录', '/wallet_refund', 'wallet_refund'), + child('finance', 'refunds', '退款记录', '/payment_refund', 'payment_refund'), child('finance', 'recharge', '钱包充值', '/wallet_recharge', 'wallet_recharge'), child('finance', 'withdrawals', '提现申请', '/wallet_apply_cash', 'wallet_apply_cash'), child('finance', 'settlements', '结算结果', '/fin_settlement', 'fin_settlement'), diff --git a/frontend/delivery_admin/src/router/routes/modules/resource-route-builder.ts b/frontend/delivery_admin/src/router/routes/modules/resource-route-builder.ts new file mode 100644 index 0000000..d5ae93a --- /dev/null +++ b/frontend/delivery_admin/src/router/routes/modules/resource-route-builder.ts @@ -0,0 +1,112 @@ +/** + * 功能描述:为配送点标准资源构造列表菜单与新建、详情、编辑独立路由。 + * 版本:v1.0.0。 + */ +import { getResource } from '@/api/resources'; +import { DEFAULT_LAYOUT } from '../base'; +import type { AppRouteRecordRaw } from '../types'; + +const resourcePage = () => import('@/views/shared/ResourcePage.vue'); +const resourceRecordPage = () => import('@/views/resource/ResourceRecordPage.vue'); + +/** 创建标准资源列表路由。 */ +export function child( + domain: string, + path: string, + title: string, + resource: string, + menuCode: string, + meta: Record = {}, +): AppRouteRecordRaw { + return { + path, + name: `${domain}-${path}`, + component: resourcePage, + meta: { title, resource, requiresAuth: true, menuCode, ...meta }, + }; +} + +/** 创建具有既有菜单地址的正式新建路由。 */ +export function createChild( + path: string, + title: string, + resource: string, + menuCode: string, + listRouteName: string, +): AppRouteRecordRaw { + return { + path, + name: `${listRouteName}-create`, + component: resourceRecordPage, + meta: { + title, + resource, + requiresAuth: true, + menuCode, + recordMode: 'create', + listRouteName, + canonicalCreate: true, + }, + }; +} + +/** 创建菜单分组并展开各资源的独立记录路由。 */ +export function group( + path: string, + name: string, + title: string, + icon: string, + order: number, + children: AppRouteRecordRaw[], +): AppRouteRecordRaw { + return { + path: `/${path}`, + name, + component: DEFAULT_LAYOUT, + redirect: `/${path}/${children[0].path}`, + meta: { title, requiresAuth: true, icon, order, menuCode: name }, + children: expandResourceChildren(children), + }; +} + +/** 为列表资源生成缺失的新建页以及全部详情、编辑页。 */ +function expandResourceChildren(children: AppRouteRecordRaw[]) { + const canonicalCreateResources = new Set( + children.filter((route) => route.meta?.canonicalCreate).map((route) => String(route.meta?.resource)), + ); + return children.flatMap((route) => { + if (route.meta?.recordMode === 'create') return [route]; + const resource = String(route.meta?.resource ?? ''); + if (!resource || resource === '/delivery_profile') return [route]; + const definition = getResource(resource); + if (definition.pageKind !== 'list') return [route]; + const listRouteName = String(route.name); + const activeMenu = String(route.meta?.activeMenu ?? route.name); + const recordRoute = ( + mode: 'create' | 'detail' | 'edit', + suffix: string, + titlePrefix: string, + ): AppRouteRecordRaw => ({ + path: `${route.path}/${suffix}`, + name: `${listRouteName}-${mode}`, + component: resourceRecordPage, + meta: { + ...route.meta, + requiresAuth: true, + title: `${titlePrefix}${definition.title}`, + hideInMenu: true, + activeMenu, + listRouteName, + recordMode: mode, + }, + }); + return [ + route, + ...(definition.canCreate && !canonicalCreateResources.has(resource) + ? [recordRoute('create', 'new', '新建')] + : []), + recordRoute('detail', ':identity', ''), + ...(definition.canEdit ? [recordRoute('edit', ':identity/edit', '编辑')] : []), + ]; + }); +} diff --git a/frontend/delivery_admin/src/router/typings.d.ts b/frontend/delivery_admin/src/router/typings.d.ts index 1f85192..7814cbf 100644 --- a/frontend/delivery_admin/src/router/typings.d.ts +++ b/frontend/delivery_admin/src/router/typings.d.ts @@ -1,3 +1,4 @@ +/** 功能描述:扩展配送点后台路由元数据类型。版本:v1.1.0。 */ import 'vue-router'; declare module 'vue-router' { @@ -6,6 +7,10 @@ declare module 'vue-router' { menuCode?: string; // Server-assigned menu domain required by this route staffType?: 'installer' | 'delivery' | 'operations'; createMode?: boolean; + resource?: string; + recordMode?: 'create' | 'detail' | 'edit'; + listRouteName?: string; + canonicalCreate?: boolean; requiresAuth: boolean; // Whether login is required to access the current page (every route must declare) icon?: string; // The icon show in the side menu locale?: string; // The locale name show in side menu and breadcrumb diff --git a/frontend/delivery_admin/src/views/resource/ResourceActionDialog.vue b/frontend/delivery_admin/src/views/resource/ResourceActionDialog.vue new file mode 100644 index 0000000..d94ea7e --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/ResourceActionDialog.vue @@ -0,0 +1,104 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/resource/ResourceDetailContent.vue b/frontend/delivery_admin/src/views/resource/ResourceDetailContent.vue new file mode 100644 index 0000000..2b20c6d --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/ResourceDetailContent.vue @@ -0,0 +1,178 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/resource/ResourceFieldForm.vue b/frontend/delivery_admin/src/views/resource/ResourceFieldForm.vue new file mode 100644 index 0000000..99bc96a --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/ResourceFieldForm.vue @@ -0,0 +1,138 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/resource/ResourceRecordPage.less b/frontend/delivery_admin/src/views/resource/ResourceRecordPage.less new file mode 100644 index 0000000..1e28e31 --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/ResourceRecordPage.less @@ -0,0 +1,68 @@ +/* 功能描述:对齐平台总后台的资源独立页面布局。版本:v1.0.0。 */ +.record-page { + display: grid; + align-content: start; + width: 100%; + min-width: 0; + max-width: 100%; + gap: 12px; + min-height: 100%; + padding: 16px 20px 20px; + background: var(--color-fill-2); +} +.page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 24px; + padding: 4px; +} +.state-card { + display: grid; + min-height: 260px; + place-items: center; + border-radius: 10px; +} +.section-card, +.form-card { + min-width: 0; + max-width: 100%; + border-radius: 10px; +} +.section-card :deep(.arco-card-header) { + height: 48px; + padding: 0 24px; +} +.section-card :deep(.arco-card-body) { + padding: 18px 24px; +} +.form-card { + width: 100%; + margin: 0; +} +.form-card :deep(.arco-card-header) { + padding: 0 32px; +} +.form-card :deep(.arco-card-body) { + padding: 24px 32px 28px; +} +.form-shell { + width: 100%; + max-width: 1440px; + margin: 0 auto; +} +.workflow-alert { margin-bottom: 18px; } +.form-actions { + display: flex; + gap: 12px; + margin-top: 20px; + padding-top: 16px; +} +@media (max-width: 760px) { + .record-page { padding: 12px; } + .page-header { flex-direction: column; } + .form-card :deep(.arco-card-header) { padding: 0 16px; } + .form-card :deep(.arco-card-body) { padding: 20px 16px 24px; } + .section-card :deep(.arco-card-header), + .section-card :deep(.arco-card-body) { padding-right: 16px; padding-left: 16px; } +} diff --git a/frontend/delivery_admin/src/views/resource/ResourceRecordPage.vue b/frontend/delivery_admin/src/views/resource/ResourceRecordPage.vue new file mode 100644 index 0000000..3f2c70a --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/ResourceRecordPage.vue @@ -0,0 +1,290 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/resource/use-unsaved-record.ts b/frontend/delivery_admin/src/views/resource/use-unsaved-record.ts new file mode 100644 index 0000000..793e75b --- /dev/null +++ b/frontend/delivery_admin/src/views/resource/use-unsaved-record.ts @@ -0,0 +1,59 @@ +/** + * 功能描述:统一资源新建和编辑页面的未保存离开保护。 + * 版本:v1.0.0。 + */ +import { Modal } from '@arco-design/web-vue'; +import { computed, onBeforeUnmount, onMounted, ref } from 'vue'; +import { onBeforeRouteLeave } from 'vue-router'; + +/** 创建基于表单快照的离开保护。 */ +export function useUnsavedRecord(snapshot: () => string, active: () => boolean) { + const initial = ref(''); + const bypass = ref(false); + const dirty = computed(() => active() && initial.value !== '' && snapshot() !== initial.value); + + function markInitialized() { + initial.value = snapshot(); + } + + function allowNextNavigation() { + bypass.value = true; + } + + /** 对页面内返回动作进行一次明确确认。 */ + function confirmDiscard(action: () => unknown) { + if (!dirty.value) return action(); + Modal.warning({ + title: '放弃未保存的修改?', + content: '当前页面内容尚未保存,离开后将无法恢复。', + hideCancel: false, + onOk: () => { + allowNextNavigation(); + action(); + }, + }); + } + + function beforeUnload(event: BeforeUnloadEvent) { + if (!dirty.value) return; + event.preventDefault(); + event.returnValue = ''; + } + + onBeforeRouteLeave(() => { + if (bypass.value || !dirty.value) return true; + return new Promise((resolve) => + Modal.warning({ + title: '内容尚未保存', + content: '确定离开当前页面并放弃修改吗?', + hideCancel: false, + onOk: () => resolve(true), + onCancel: () => resolve(false), + }), + ); + }); + onMounted(() => window.addEventListener('beforeunload', beforeUnload)); + onBeforeUnmount(() => window.removeEventListener('beforeunload', beforeUnload)); + + return { dirty, markInitialized, allowNextNavigation, confirmDiscard }; +} diff --git a/frontend/delivery_admin/src/views/shared/CrudListPage.vue b/frontend/delivery_admin/src/views/shared/CrudListPage.vue deleted file mode 100644 index edf53c1..0000000 --- a/frontend/delivery_admin/src/views/shared/CrudListPage.vue +++ /dev/null @@ -1,1266 +0,0 @@ - - - - - diff --git a/frontend/delivery_admin/src/views/shared/ResourceListPage.vue b/frontend/delivery_admin/src/views/shared/ResourceListPage.vue new file mode 100644 index 0000000..4f83ac1 --- /dev/null +++ b/frontend/delivery_admin/src/views/shared/ResourceListPage.vue @@ -0,0 +1,222 @@ + + + + + + diff --git a/frontend/delivery_admin/src/views/shared/ResourcePage.vue b/frontend/delivery_admin/src/views/shared/ResourcePage.vue index 9812f8a..7fa61f8 100644 --- a/frontend/delivery_admin/src/views/shared/ResourcePage.vue +++ b/frontend/delivery_admin/src/views/shared/ResourcePage.vue @@ -9,7 +9,7 @@ :key="String(route.name)" :definition="definition" /> -