feat: complete platform administration pages
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Task 6 report
|
||||
|
||||
## RED baseline
|
||||
|
||||
`pnpm audit:platform` initially exited with code 1. The audit reported missing API declarations, route entries, and view files for the resource contract catalogue.
|
||||
|
||||
## Implementation
|
||||
|
||||
- Added `src/api/resources.ts` as the single front-end catalogue for all 46 backend resource contracts, including path, title, mode, page kind, and fields.
|
||||
- Added route modules and declarative list/tree pages for each resource.
|
||||
- Added reusable read-only and tree pages; read-only definitions do not render create, edit, status, or archive controls.
|
||||
- Updated the shared CRUD page with server-side keyword filters, identity-based details, typed form data, archive confirmation, and API error messages.
|
||||
- Removed platform UI use of internal numeric identifiers in favour of `identity` and `parent_identity`.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm type:check` — pass
|
||||
- `pnpm audit:platform` — pass
|
||||
- `pnpm build` — pass
|
||||
|
||||
## Notes
|
||||
|
||||
The tree page consumes `parent_identity`; the corresponding menu API must expose that identity relation rather than an internal numeric parent key.
|
||||
@@ -3,7 +3,7 @@ import { resourceApi } from './resource';
|
||||
|
||||
/** 平台角色、菜单、账号和工作台接口。 */
|
||||
export type PlatformRole = { identity: string; role_code: string; name: string; data_scope: string; is_system: boolean; status: string };
|
||||
export type PlatformMenu = { id: number; identity: string; parent_id: number; menu_code: string; name: string; icon: string; path: string; sort_no: number };
|
||||
export type PlatformMenu = { identity: string; parent_identity?: string; menu_code: string; name: string; icon: string; path: string; sort_no: number };
|
||||
|
||||
export const platformApi = {
|
||||
overview: () => request<Record<string, number>>('/dashboard/overview'),
|
||||
|
||||
@@ -2,7 +2,10 @@ import { request, type PageResult } from './http';
|
||||
|
||||
/** 所有标准 CRUD 资源共享的调用方法。 */
|
||||
export const resourceApi = {
|
||||
list: <T>(resource: string, page = 1, size = 20) => request<PageResult<T>>(`${resource}?page=${page}&size=${size}`),
|
||||
list: <T>(resource: string, page = 1, size = 20, filters: Record<string, string> = {}) => {
|
||||
const query = new URLSearchParams({ page: String(page), size: String(size), ...filters });
|
||||
return request<PageResult<T>>(`${resource}?${query.toString()}`);
|
||||
},
|
||||
detail: <T>(resource: string, identity: string) => request<T>(`${resource}/${identity}`),
|
||||
create: <T>(resource: string, data: Record<string, unknown>) => request<T>(resource, { method: 'POST', body: JSON.stringify(data) }),
|
||||
update: <T>(resource: string, identity: string, data: Record<string, unknown>) => request<T>(`${resource}/${identity}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
|
||||
39
frontend/platform_admin/src/api/resources.ts
Normal file
39
frontend/platform_admin/src/api/resources.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export type ResourceMode = 'writable' | 'readonly' | 'append_only';
|
||||
export type ResourcePageKind = 'list' | 'tree' | 'dashboard';
|
||||
export type ResourceField = { key: string; label: string; required?: boolean };
|
||||
export type ResourceUiDefinition = {
|
||||
key: string;
|
||||
resource: string;
|
||||
title: string;
|
||||
mode: ResourceMode;
|
||||
pageKind: ResourcePageKind;
|
||||
fields: ResourceField[];
|
||||
};
|
||||
|
||||
const fields: ResourceField[] = [{ key: 'name', label: '名称' }, { key: 'status', label: '状态' }];
|
||||
const resource = (domain: string, name: string, title: string, mode: ResourceMode = 'writable', pageKind: ResourcePageKind = 'list'): ResourceUiDefinition => ({
|
||||
key: name.replace(/_/g, '-'), resource: `/${domain}/${name}`, title, mode, pageKind, fields,
|
||||
});
|
||||
|
||||
/** The single UI catalogue mirrors the backend ExpectedResources contract. */
|
||||
export const resources: ResourceUiDefinition[] = [
|
||||
resource('gas', 'gas_basic', '气站管理'), resource('gas', 'gas_account', '气站账户'),
|
||||
resource('delivery', 'delivery_basic', '配送点管理'), resource('delivery', 'delivery_account', '配送账户'), resource('delivery', 'delivery_task', '配送任务'), resource('delivery', 'delivery_track', '配送轨迹'), resource('delivery', 'delivery_track_point', '轨迹点'),
|
||||
resource('staff', 'staff_account', '服务人员'), resource('staff', 'staff_credential', '人员资质'),
|
||||
resource('user', 'user_account', '用户账户'), resource('user', 'user_address', '用户地址'), resource('user', 'user_service_relation', '用户服务关系'),
|
||||
resource('device', 'dev_smart_cylinder_valve', '智能钢瓶阀'), resource('device', 'dev_device_binding', '设备绑定'), resource('device', 'dev_telemetry', '设备遥测', 'readonly'), resource('device', 'saf_rule', '安全规则'), resource('device', 'saf_event', '安全事件'), resource('device', 'saf_event_disposal', '事件处置记录', 'append_only'), resource('device', 'saf_inspection', '安全检查'),
|
||||
resource('commerce', 'ec_category', '商品分类'), resource('commerce', 'ec_product', '商品'), resource('commerce', 'ec_product_attribute', '商品属性'), resource('commerce', 'ec_product_image', '商品图片'), resource('commerce', 'ec_cart', '购物车'), resource('commerce', 'ec_order', '订单'), resource('commerce', 'ec_review', '评价'),
|
||||
resource('finance', 'fin_payment', '支付记录'), resource('finance', 'fin_settlement', '结算记录'), resource('finance', 'fin_reconciliation', '对账记录'),
|
||||
resource('content', 'cnt_content', '内容管理'), resource('notification', 'ntf_template', '通知模板'), resource('customer_service', 'cs_ticket', '客服工单'),
|
||||
resource('platform', 'platfrom_account', '平台账户'), resource('platform', 'platform_role', '平台角色'), resource('platform', 'platform_menu', '平台菜单', 'writable', 'tree'),
|
||||
resource('wallet', 'wallet', '钱包', 'readonly'), resource('wallet', 'wallet_ledger', '钱包流水', 'readonly'), resource('wallet', 'wallet_recharge', '钱包充值', 'readonly'), resource('wallet', 'wallet_withdrawal', '钱包提现', 'readonly'),
|
||||
resource('report', 'report', '报表', 'readonly'), resource('report', 'report_item', '报表项目', 'readonly'), resource('report', 'report_metric_snapshot', '指标快照', 'readonly'),
|
||||
resource('audit', 'aud_operation_log', '操作审计', 'readonly'), resource('audit', 'aud_export_log', '导出审计', 'readonly'), resource('audit', 'aud_approval', '审批审计', 'readonly'),
|
||||
];
|
||||
|
||||
export const resourceByName = Object.fromEntries(resources.map((definition) => [definition.resource, definition])) as Record<string, ResourceUiDefinition>;
|
||||
export function getResource(resourcePath: string): ResourceUiDefinition {
|
||||
const definition = resourceByName[resourcePath];
|
||||
if (!definition) throw new Error(`Unknown resource: ${resourcePath}`);
|
||||
return definition;
|
||||
}
|
||||
@@ -2,4 +2,4 @@ import { resourceApi } from './resource';
|
||||
|
||||
/** 服务人员管理接口。 */
|
||||
export type Staff = { identity: string; name: string; phone: string; role_code: string; work_status: string; status: string };
|
||||
export const staffApi = { list: () => resourceApi.list<Staff>('/staff/staff'), create: (data: Record<string, unknown>) => resourceApi.create<Staff>('/staff/staff', data) };
|
||||
export const staffApi = { list: () => resourceApi.list<Staff>('/staff/staff_account'), create: (data: Record<string, unknown>) => resourceApi.create<Staff>('/staff/staff_account', data) };
|
||||
|
||||
@@ -2,4 +2,4 @@ import { resourceApi } from './resource';
|
||||
|
||||
/** 业主客户管理接口。 */
|
||||
export type User = { identity: string; name: string; phone: string; real_name: string; status: string };
|
||||
export const userApi = { list: () => resourceApi.list<User>('/user/user'), create: (data: Record<string, unknown>) => resourceApi.create<User>('/user/user', data) };
|
||||
export const userApi = { list: () => resourceApi.list<User>('/user/user_account'), create: (data: Record<string, unknown>) => resourceApi.create<User>('/user/user_account', data) };
|
||||
|
||||
12
frontend/platform_admin/src/router/routes/modules/audit.ts
Normal file
12
frontend/platform_admin/src/router/routes/modules/audit.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/audit', name: 'audit', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.audit', requiresAuth: true, icon: 'icon-apps', order: 22 },
|
||||
children: [
|
||||
{ path: 'aud-operation-log', name: 'audit-aud-operation-log', component: () => import('@/views/audit/aud_operation_log/ListPage.vue'), meta: { locale: 'menu.platform.audit.aud_operation_log', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'aud-export-log', name: 'audit-aud-export-log', component: () => import('@/views/audit/aud_export_log/ListPage.vue'), meta: { locale: 'menu.platform.audit.aud_export_log', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'aud-approval', name: 'audit-aud-approval', component: () => import('@/views/audit/aud_approval/ListPage.vue'), meta: { locale: 'menu.platform.audit.aud_approval', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/commerce', name: 'commerce', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.commerce', requiresAuth: true, icon: 'icon-apps', order: 15 },
|
||||
children: [
|
||||
{ path: 'ec-category', name: 'commerce-ec-category', component: () => import('@/views/commerce/ec_category/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_category', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-product', name: 'commerce-ec-product', component: () => import('@/views/commerce/ec_product/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_product', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-product-attribute', name: 'commerce-ec-product-attribute', component: () => import('@/views/commerce/ec_product_attribute/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_product_attribute', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-product-image', name: 'commerce-ec-product-image', component: () => import('@/views/commerce/ec_product_image/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_product_image', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-cart', name: 'commerce-ec-cart', component: () => import('@/views/commerce/ec_cart/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_cart', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-order', name: 'commerce-ec-order', component: () => import('@/views/commerce/ec_order/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_order', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'ec-review', name: 'commerce-ec-review', component: () => import('@/views/commerce/ec_review/ListPage.vue'), meta: { locale: 'menu.platform.commerce.ec_review', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
10
frontend/platform_admin/src/router/routes/modules/content.ts
Normal file
10
frontend/platform_admin/src/router/routes/modules/content.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/content', name: 'content', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.content', requiresAuth: true, icon: 'icon-apps', order: 17 },
|
||||
children: [
|
||||
{ path: 'cnt-content', name: 'content-cnt-content', component: () => import('@/views/content/cnt_content/ListPage.vue'), meta: { locale: 'menu.platform.content.cnt_content', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/customer_service', name: 'customer_service', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.customer_service', requiresAuth: true, icon: 'icon-apps', order: 19 },
|
||||
children: [
|
||||
{ path: 'cs-ticket', name: 'customer_service-cs-ticket', component: () => import('@/views/customer_service/cs_ticket/ListPage.vue'), meta: { locale: 'menu.platform.customer_service.cs_ticket', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/delivery', name: 'delivery', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.delivery', requiresAuth: true, icon: 'icon-apps', order: 11 },
|
||||
children: [
|
||||
{ path: 'delivery-basic', name: 'delivery-delivery-basic', component: () => import('@/views/delivery/delivery_basic/ListPage.vue'), meta: { locale: 'menu.platform.delivery.delivery_basic', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'delivery-account', name: 'delivery-delivery-account', component: () => import('@/views/delivery/delivery_account/ListPage.vue'), meta: { locale: 'menu.platform.delivery.delivery_account', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'delivery-task', name: 'delivery-delivery-task', component: () => import('@/views/delivery/delivery_task/ListPage.vue'), meta: { locale: 'menu.platform.delivery.delivery_task', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'delivery-track', name: 'delivery-delivery-track', component: () => import('@/views/delivery/delivery_track/ListPage.vue'), meta: { locale: 'menu.platform.delivery.delivery_track', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'delivery-track-point', name: 'delivery-delivery-track-point', component: () => import('@/views/delivery/delivery_track_point/ListPage.vue'), meta: { locale: 'menu.platform.delivery.delivery_track_point', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
16
frontend/platform_admin/src/router/routes/modules/device.ts
Normal file
16
frontend/platform_admin/src/router/routes/modules/device.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/device', name: 'device', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.device', requiresAuth: true, icon: 'icon-apps', order: 14 },
|
||||
children: [
|
||||
{ path: 'dev-smart-cylinder-valve', name: 'device-dev-smart-cylinder-valve', component: () => import('@/views/device/dev_smart_cylinder_valve/ListPage.vue'), meta: { locale: 'menu.platform.device.dev_smart_cylinder_valve', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'dev-device-binding', name: 'device-dev-device-binding', component: () => import('@/views/device/dev_device_binding/ListPage.vue'), meta: { locale: 'menu.platform.device.dev_device_binding', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'dev-telemetry', name: 'device-dev-telemetry', component: () => import('@/views/device/dev_telemetry/ListPage.vue'), meta: { locale: 'menu.platform.device.dev_telemetry', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'saf-rule', name: 'device-saf-rule', component: () => import('@/views/device/saf_rule/ListPage.vue'), meta: { locale: 'menu.platform.device.saf_rule', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'saf-event', name: 'device-saf-event', component: () => import('@/views/device/saf_event/ListPage.vue'), meta: { locale: 'menu.platform.device.saf_event', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'saf-event-disposal', name: 'device-saf-event-disposal', component: () => import('@/views/device/saf_event_disposal/ListPage.vue'), meta: { locale: 'menu.platform.device.saf_event_disposal', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'saf-inspection', name: 'device-saf-inspection', component: () => import('@/views/device/saf_inspection/ListPage.vue'), meta: { locale: 'menu.platform.device.saf_inspection', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
12
frontend/platform_admin/src/router/routes/modules/finance.ts
Normal file
12
frontend/platform_admin/src/router/routes/modules/finance.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/finance', name: 'finance', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.finance', requiresAuth: true, icon: 'icon-apps', order: 16 },
|
||||
children: [
|
||||
{ path: 'fin-payment', name: 'finance-fin-payment', component: () => import('@/views/finance/fin_payment/ListPage.vue'), meta: { locale: 'menu.platform.finance.fin_payment', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'fin-settlement', name: 'finance-fin-settlement', component: () => import('@/views/finance/fin_settlement/ListPage.vue'), meta: { locale: 'menu.platform.finance.fin_settlement', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'fin-reconciliation', name: 'finance-fin-reconciliation', component: () => import('@/views/finance/fin_reconciliation/ListPage.vue'), meta: { locale: 'menu.platform.finance.fin_reconciliation', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
11
frontend/platform_admin/src/router/routes/modules/gas.ts
Normal file
11
frontend/platform_admin/src/router/routes/modules/gas.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/gas', name: 'gas', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.gas', requiresAuth: true, icon: 'icon-apps', order: 10 },
|
||||
children: [
|
||||
{ path: 'gas-basic', name: 'gas-gas-basic', component: () => import('@/views/gas/gas_basic/ListPage.vue'), meta: { locale: 'menu.platform.gas.gas_basic', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'gas-account', name: 'gas-gas-account', component: () => import('@/views/gas/gas_account/ListPage.vue'), meta: { locale: 'menu.platform.gas.gas_account', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/notification', name: 'notification', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.notification', requiresAuth: true, icon: 'icon-apps', order: 18 },
|
||||
children: [
|
||||
{ path: 'ntf-template', name: 'notification-ntf-template', component: () => import('@/views/notification/ntf_template/ListPage.vue'), meta: { locale: 'menu.platform.notification.ntf_template', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -1,13 +1,24 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
|
||||
const PLATFORM: AppRouteRecordRaw[] = [
|
||||
import gasRoutes from './gas';
|
||||
import deliveryRoutes from './delivery';
|
||||
import staffRoutes from './staff';
|
||||
import userRoutes from './user';
|
||||
import deviceRoutes from './device';
|
||||
import commerceRoutes from './commerce';
|
||||
import financeRoutes from './finance';
|
||||
import contentRoutes from './content';
|
||||
import notificationRoutes from './notification';
|
||||
import customerServiceRoutes from './customer_service';
|
||||
import walletRoutes from './wallet';
|
||||
import reportRoutes from './report';
|
||||
import auditRoutes from './audit';
|
||||
const platformRoutes: AppRouteRecordRaw[] = [
|
||||
{ path: '/dashboard', name: 'dashboard', component: DEFAULT_LAYOUT, meta: { locale: 'menu.dashboard', requiresAuth: true, icon: 'icon-dashboard', order: 0 }, children: [{ path: 'overview', name: 'DashboardOverview', component: () => import('@/views/dashboard/DashboardPage.vue'), meta: { locale: 'menu.platform.dashboard', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/gas', name: 'gas', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.gas', requiresAuth: true, icon: 'icon-fire', order: 10 }, children: [{ path: 'basic', name: 'GasBasic', component: () => import('@/views/gas/basic/ListPage.vue'), meta: { locale: 'menu.platform.gas.basic', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/delivery', name: 'delivery', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.delivery', requiresAuth: true, icon: 'icon-car', order: 11 }, children: [{ path: 'basic', name: 'DeliveryBasic', component: () => import('@/views/delivery/basic/ListPage.vue'), meta: { locale: 'menu.platform.delivery.basic', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/staff', name: 'staff', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.staff', requiresAuth: true, icon: 'icon-user', order: 12 }, children: [{ path: 'list', name: 'Staff', component: () => import('@/views/staff/list/ListPage.vue'), meta: { locale: 'menu.platform.staff.list', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/user', name: 'user', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.user', requiresAuth: true, icon: 'icon-user-group', order: 13 }, children: [{ path: 'list', name: 'User', component: () => import('@/views/user/list/ListPage.vue'), meta: { locale: 'menu.platform.user.list', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/platform', name: 'platform', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.config', requiresAuth: true, icon: 'icon-settings', order: 14 }, children: [{ path: 'account', name: 'PlatformAccount', component: () => import('@/views/platform/account/ListPage.vue'), meta: { locale: 'menu.platform.config.account', requiresAuth: true, roles: ['*'] } }, { path: 'role', name: 'PlatformRole', component: () => import('@/views/platform/role/ListPage.vue'), meta: { locale: 'menu.platform.config.role', requiresAuth: true, roles: ['*'] } }, { path: 'menu', name: 'PlatformMenu', component: () => import('@/views/platform/menu/TreePage.vue'), meta: { locale: 'menu.platform.config.menu', requiresAuth: true, roles: ['*'] } }] },
|
||||
{ path: '/platform', name: 'platform', component: DEFAULT_LAYOUT, meta: { locale: 'menu.platform.config', requiresAuth: true, icon: 'icon-settings', order: 30 }, children: [
|
||||
{ path: 'platfrom-account', name: 'platform-platfrom-account', component: () => import('@/views/platform/platfrom_account/ListPage.vue'), meta: { locale: 'menu.platform.platform.platfrom_account', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'platform-role', name: 'platform-platform-role', component: () => import('@/views/platform/platform_role/ListPage.vue'), meta: { locale: 'menu.platform.platform.platform_role', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'platform-menu', name: 'platform-platform-menu', component: () => import('@/views/platform/platform_menu/TreePage.vue'), meta: { locale: 'menu.platform.platform.platform_menu', requiresAuth: true, roles: ['*'] } },
|
||||
] },
|
||||
];
|
||||
|
||||
export default PLATFORM;
|
||||
export default [...platformRoutes, ...gasRoutes, ...deliveryRoutes, ...staffRoutes, ...userRoutes, ...deviceRoutes, ...commerceRoutes, ...financeRoutes, ...contentRoutes, ...notificationRoutes, ...customerServiceRoutes, ...walletRoutes, ...reportRoutes, ...auditRoutes];
|
||||
|
||||
12
frontend/platform_admin/src/router/routes/modules/report.ts
Normal file
12
frontend/platform_admin/src/router/routes/modules/report.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/report', name: 'report', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.report', requiresAuth: true, icon: 'icon-apps', order: 21 },
|
||||
children: [
|
||||
{ path: 'report', name: 'report-report', component: () => import('@/views/report/report/ListPage.vue'), meta: { locale: 'menu.platform.report.report', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'report-item', name: 'report-report-item', component: () => import('@/views/report/report_item/ListPage.vue'), meta: { locale: 'menu.platform.report.report_item', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'report-metric-snapshot', name: 'report-report-metric-snapshot', component: () => import('@/views/report/report_metric_snapshot/ListPage.vue'), meta: { locale: 'menu.platform.report.report_metric_snapshot', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
11
frontend/platform_admin/src/router/routes/modules/staff.ts
Normal file
11
frontend/platform_admin/src/router/routes/modules/staff.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/staff', name: 'staff', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.staff', requiresAuth: true, icon: 'icon-apps', order: 12 },
|
||||
children: [
|
||||
{ path: 'staff-account', name: 'staff-staff-account', component: () => import('@/views/staff/staff_account/ListPage.vue'), meta: { locale: 'menu.platform.staff.staff_account', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'staff-credential', name: 'staff-staff-credential', component: () => import('@/views/staff/staff_credential/ListPage.vue'), meta: { locale: 'menu.platform.staff.staff_credential', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
12
frontend/platform_admin/src/router/routes/modules/user.ts
Normal file
12
frontend/platform_admin/src/router/routes/modules/user.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/user', name: 'user', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.user', requiresAuth: true, icon: 'icon-apps', order: 13 },
|
||||
children: [
|
||||
{ path: 'user-account', name: 'user-user-account', component: () => import('@/views/user/user_account/ListPage.vue'), meta: { locale: 'menu.platform.user.user_account', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'user-address', name: 'user-user-address', component: () => import('@/views/user/user_address/ListPage.vue'), meta: { locale: 'menu.platform.user.user_address', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'user-service-relation', name: 'user-user-service-relation', component: () => import('@/views/user/user_service_relation/ListPage.vue'), meta: { locale: 'menu.platform.user.user_service_relation', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
13
frontend/platform_admin/src/router/routes/modules/wallet.ts
Normal file
13
frontend/platform_admin/src/router/routes/modules/wallet.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
const routes: AppRouteRecordRaw[] = [{
|
||||
path: '/wallet', name: 'wallet', component: DEFAULT_LAYOUT,
|
||||
meta: { locale: 'menu.platform.wallet', requiresAuth: true, icon: 'icon-apps', order: 20 },
|
||||
children: [
|
||||
{ path: 'wallet', name: 'wallet-wallet', component: () => import('@/views/wallet/wallet/ListPage.vue'), meta: { locale: 'menu.platform.wallet.wallet', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'wallet-ledger', name: 'wallet-wallet-ledger', component: () => import('@/views/wallet/wallet_ledger/ListPage.vue'), meta: { locale: 'menu.platform.wallet.wallet_ledger', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'wallet-recharge', name: 'wallet-wallet-recharge', component: () => import('@/views/wallet/wallet_recharge/ListPage.vue'), meta: { locale: 'menu.platform.wallet.wallet_recharge', requiresAuth: true, roles: ['*'] } },
|
||||
{ path: 'wallet-withdrawal', name: 'wallet-wallet-withdrawal', component: () => import('@/views/wallet/wallet_withdrawal/ListPage.vue'), meta: { locale: 'menu.platform.wallet.wallet_withdrawal', requiresAuth: true, roles: ['*'] } }
|
||||
],
|
||||
}];
|
||||
export default routes;
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/audit/aud_approval');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/audit/aud_export_log');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/audit/aud_operation_log');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_cart');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_category');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_order');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_product');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_product_attribute');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_product_image');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/commerce/ec_review');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/content/cnt_content');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/customer_service/cs_ticket');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/delivery/delivery_account');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/delivery/delivery_basic');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/delivery/delivery_task');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/delivery/delivery_track');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/delivery/delivery_track_point');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/dev_device_binding');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/dev_smart_cylinder_valve');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/dev_telemetry');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/saf_event');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/saf_event_disposal');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/saf_inspection');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/device/saf_rule');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/finance/fin_payment');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/finance/fin_reconciliation');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/finance/fin_settlement');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/gas/gas_account');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/gas/gas_basic');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/notification/ntf_template');
|
||||
</script>
|
||||
@@ -1 +1,6 @@
|
||||
<template><a-card title="菜单管理" :bordered="false"><a-tree :data="tree" :field-names="{ key: 'identity', title: 'name', children: 'children' }" /></a-card></template><script setup lang="ts">import { Message } from '@arco-design/web-vue'; import { computed, onMounted, ref } from 'vue'; import { platformApi, type PlatformMenu } from '@/api/platform'; type Node = PlatformMenu & { children: Node[] }; const list = ref<PlatformMenu[]>([]); const tree = computed<any[]>(() => { const byId = new Map<number, Node>(); const roots: Node[] = []; list.value.forEach((item) => byId.set(item.id, { ...item, children: [] })); byId.forEach((item) => { if (item.parent_id && byId.has(item.parent_id)) byId.get(item.parent_id)?.children.push(item); else roots.push(item); }); return roots; }); onMounted(async () => { try { list.value = (await platformApi.listMenu()).list; } catch (error) { Message.error((error as Error).message); } });</script>
|
||||
<template><TreePage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import TreePage from '@/views/shared/TreePage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/platform/platform_menu');
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><TreePage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import TreePage from '@/views/shared/TreePage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/platform/platform_menu');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/platform/platform_role');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/platform/platfrom_account');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/report/report');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/report/report_item');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/report/report_metric_snapshot');
|
||||
</script>
|
||||
@@ -1,40 +1,49 @@
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra><a-space><a-button @click="load">刷新</a-button><a-button type="primary" @click="openCreate">新建</a-button></a-space></template>
|
||||
<a-card :title="definition.title" :bordered="false">
|
||||
<template #extra>
|
||||
<a-space><a-button @click="load">刷新</a-button><a-button v-if="canCreate" type="primary" @click="openCreate">新建</a-button></a-space>
|
||||
</template>
|
||||
<a-form :model="filters" layout="inline" class="filters" @submit.prevent="load">
|
||||
<a-form-item label="关键字"><a-input v-model="filters.keyword" allow-clear placeholder="服务端筛选" /></a-form-item>
|
||||
<a-button type="primary" html-type="submit">查询</a-button>
|
||||
</a-form>
|
||||
<a-table :data="list" :loading="loading" :pagination="false" row-key="identity">
|
||||
<template #columns>
|
||||
<a-table-column title="业务标识" data-index="identity" :width="220" ellipsis tooltip />
|
||||
<a-table-column v-for="field in fields" :key="field.key" :title="field.label" :data-index="field.key" ellipsis tooltip />
|
||||
<a-table-column title="状态" data-index="status" :width="100"><template #cell="{ record }"><a-tag :color="record.status === 'enabled' ? 'green' : 'orange'">{{ record.status }}</a-tag></template></a-table-column>
|
||||
<a-table-column title="操作" :width="210" fixed="right"><template #cell="{ record }"><a-space><a-button size="mini" @click="openDetail(record)">详情</a-button><a-button size="mini" @click="openEdit(record)">编辑</a-button><a-button size="mini" @click="changeStatus(record)">{{ record.status === 'enabled' ? '停用' : '启用' }}</a-button></a-space></template></a-table-column>
|
||||
<a-table-column v-for="field in displayFields" :key="field.key" :title="field.label" :data-index="field.key" ellipsis tooltip />
|
||||
<a-table-column title="操作" :width="190" fixed="right"><template #cell="{ record }"><a-space><a-button size="mini" @click="openDetail(record)">详情</a-button><a-button v-if="canEdit" size="mini" @click="openEdit(record)">编辑</a-button><a-button v-if="canArchive" size="mini" status="danger" @click="confirmArchive(record)">归档</a-button></a-space></template></a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="pagination"><a-pagination :total="total" :current="page" :page-size="pageSize" show-total @change="changePage" /></div>
|
||||
</a-card>
|
||||
<a-drawer :visible="formVisible" :title="editingIdentity ? `编辑${title}` : `新建${title}`" :width="480" @cancel="formVisible = false" @ok="save">
|
||||
<a-form :model="form" layout="vertical"><a-form-item v-for="field in fields" :key="field.key" :label="field.label" :required="field.required"><a-input v-model="form[field.key]" :placeholder="`请输入${field.label}`" /></a-form-item></a-form>
|
||||
<a-drawer :visible="formVisible" :title="editingIdentity ? `编辑${definition.title}` : `新建${definition.title}`" :width="480" @cancel="formVisible = false" @ok="save">
|
||||
<a-form :model="form" layout="vertical"><a-form-item v-for="field in definition.fields" :key="field.key" :label="field.label" :required="field.required"><a-input v-model="form[field.key]" :placeholder="`请输入${field.label}`" /></a-form-item></a-form>
|
||||
</a-drawer>
|
||||
<a-drawer :visible="detailVisible" title="详情" :width="540" @cancel="detailVisible = false"><a-descriptions :column="1" bordered><a-descriptions-item v-for="(value, key) in detail" :key="String(key)" :label="String(key)">{{ value ?? '-' }}</a-descriptions-item></a-descriptions></a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Message, Modal } from '@arco-design/web-vue';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { resourceApi } from '@/api/resource';
|
||||
import type { ResourceUiDefinition } from '@/api/resources';
|
||||
|
||||
type Field = { key: string; label: string; required?: boolean };
|
||||
type Row = Record<string, any>;
|
||||
const props = defineProps<{ title: string; resource: string; fields: Field[] }>();
|
||||
const loading = ref(false); const page = ref(1); const pageSize = 20; const total = ref(0); const list = ref<Row[]>([]);
|
||||
const formVisible = ref(false); const detailVisible = ref(false); const editingIdentity = ref(''); const form = reactive<Row>({}); const detail = ref<Row>({});
|
||||
function resetForm(data?: Row) { props.fields.forEach((field) => { form[field.key] = data?.[field.key] ?? ''; }); }
|
||||
async function load() { loading.value = true; try { const result = await resourceApi.list<Row>(props.resource, page.value, pageSize); list.value = result.list; total.value = result.total; } catch (error) { Message.error((error as Error).message); } finally { loading.value = false; } }
|
||||
type Row = Record<string, unknown>;
|
||||
const props = defineProps<{ definition: ResourceUiDefinition }>();
|
||||
const loading = ref(false); const page = ref(1); const pageSize = 20; const total = ref(0); const list = ref<Row[]>([]); const filters = reactive({ keyword: '' });
|
||||
const formVisible = ref(false); const detailVisible = ref(false); const editingIdentity = ref(''); const form = reactive<Record<string, string>>({}); const detail = ref<Row>({});
|
||||
const canCreate = computed(() => props.definition.mode !== 'readonly');
|
||||
const canEdit = computed(() => props.definition.mode === 'writable');
|
||||
const canArchive = computed(() => props.definition.mode === 'writable');
|
||||
const displayFields = computed(() => props.definition.fields.filter((field) => field.key !== 'status'));
|
||||
function resetForm(data?: Row) { props.definition.fields.forEach((field) => { form[field.key] = data?.[field.key] == null ? '' : String(data[field.key]); }); }
|
||||
async function load() { loading.value = true; try { const result = await resourceApi.list<Row>(props.definition.resource, page.value, pageSize, filters.keyword ? { keyword: filters.keyword } : {}); list.value = result.list; total.value = result.total; } catch (error) { Message.error((error as Error).message); } finally { loading.value = false; } }
|
||||
function openCreate() { editingIdentity.value = ''; resetForm(); formVisible.value = true; }
|
||||
function openEdit(row: Row) { editingIdentity.value = row.identity; resetForm(row); formVisible.value = true; }
|
||||
function openDetail(row: Row) { detail.value = row; detailVisible.value = true; }
|
||||
async function save() { if (props.fields.some((field) => field.required && !form[field.key])) { Message.warning('请填写必填字段'); return; } try { if (editingIdentity.value) await resourceApi.update(props.resource, editingIdentity.value, form); else await resourceApi.create(props.resource, form); Message.success('保存成功'); formVisible.value = false; await load(); } catch (error) { Message.error((error as Error).message); } }
|
||||
async function changeStatus(row: Row) { try { await resourceApi.updateStatus(props.resource, row.identity, row.status === 'enabled' ? 'disabled' : 'enabled'); await load(); } catch (error) { Message.error((error as Error).message); } }
|
||||
function openEdit(row: Row) { editingIdentity.value = String(row.identity ?? ''); resetForm(row); formVisible.value = true; }
|
||||
async function openDetail(row: Row) { try { detail.value = await resourceApi.detail<Row>(props.definition.resource, String(row.identity)); detailVisible.value = true; } catch (error) { Message.error((error as Error).message); } }
|
||||
async function save() { if (props.definition.fields.some((field) => field.required && !form[field.key])) { Message.warning('请填写必填字段'); return; } try { if (editingIdentity.value) await resourceApi.update(props.definition.resource, editingIdentity.value, form); else await resourceApi.create(props.definition.resource, form); Message.success('保存成功'); formVisible.value = false; await load(); } catch (error) { Message.error((error as Error).message); } }
|
||||
function confirmArchive(row: Row) { Modal.warning({ title: '确认归档', content: '归档后该记录将不再参与日常业务。', onOk: async () => { try { await resourceApi.archive(props.definition.resource, String(row.identity)); Message.success('已归档'); await load(); } catch (error) { Message.error((error as Error).message); } } }); }
|
||||
async function changePage(next: number) { page.value = next; await load(); }
|
||||
onMounted(load);
|
||||
</script>
|
||||
<style scoped lang="less">.pagination { display: flex; justify-content: flex-end; margin-top: 16px; }</style>
|
||||
<style scoped lang="less">.filters { margin-bottom: 16px; } .pagination { display: flex; justify-content: flex-end; margin-top: 16px; }</style>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from './CrudListPage.vue';
|
||||
import type { ResourceUiDefinition } from '@/api/resources';
|
||||
defineProps<{ definition: ResourceUiDefinition }>();
|
||||
</script>
|
||||
24
frontend/platform_admin/src/views/shared/TreePage.vue
Normal file
24
frontend/platform_admin/src/views/shared/TreePage.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<a-card :title="definition.title" :bordered="false">
|
||||
<template #extra><a-button @click="load">刷新</a-button></template>
|
||||
<a-tree :data="tree" :loading="loading" :field-names="{ key: 'identity', title: 'name', children: 'children' }" />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { resourceApi } from '@/api/resource';
|
||||
import type { ResourceUiDefinition } from '@/api/resources';
|
||||
|
||||
type Node = Record<string, unknown> & { identity: string; parent_identity?: string; children: Node[] };
|
||||
const props = defineProps<{ definition: ResourceUiDefinition }>();
|
||||
const loading = ref(false); const list = ref<Node[]>([]);
|
||||
const tree = computed(() => {
|
||||
const nodes = new Map<string, Node>(); const roots: Node[] = [];
|
||||
list.value.forEach((item) => nodes.set(item.identity, { ...item, children: [] }));
|
||||
nodes.forEach((item) => { const parent = item.parent_identity && nodes.get(item.parent_identity); if (parent) parent.children.push(item); else roots.push(item); });
|
||||
return roots;
|
||||
});
|
||||
async function load() { loading.value = true; try { list.value = (await resourceApi.list<Node>(props.definition.resource, 1, 500)).list; } catch (error) { Message.error((error as Error).message); } finally { loading.value = false; } }
|
||||
onMounted(load);
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/staff/staff_account');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/staff/staff_credential');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/user/user_account');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/user/user_address');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><CrudListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import CrudListPage from '@/views/shared/CrudListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/user/user_service_relation');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/wallet/wallet');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/wallet/wallet_ledger');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/wallet/wallet_recharge');
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template><ReadOnlyListPage :definition="definition" /></template>
|
||||
<script setup lang="ts">
|
||||
import ReadOnlyListPage from '@/views/shared/ReadOnlyListPage.vue';
|
||||
import { getResource } from '@/api/resources';
|
||||
const definition = getResource('/wallet/wallet_withdrawal');
|
||||
</script>
|
||||
Reference in New Issue
Block a user