diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..9df1561 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,2 @@ +# Vite 开发代理目标,仅开发服务器读取;前端请求仍使用 /api 相对地址。 +DEV_API_TARGET=http://127.0.0.1:8000 diff --git a/frontend/README.md b/frontend/README.md index 4d0dc1a..5212247 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -6,6 +6,9 @@ - 登录页与图形验证码交互 - 会话状态管理与路由守卫 +- 管理员 T+ 数据同步页:`/system/tplus`,支持读取排队、关联检查、批次/异常详情和失败重试;只读配置未就绪时禁用创建。 +- T+ 页面位于 `src/views/system/tplus/`,合同在 `src/api/tplus.ts`;创建或重试失败后保留请求键,范围改变或提交成功后才换键。任务由独立读取服务执行,页面刷新查看状态,运行命令见 T+ 本地恢复手册。 +- Vite 开发代理可通过 `DEV_API_TARGET` 指定后端,默认本地 8000;浏览器请求继续使用 `/api` 相对地址。 - 岗位角色管理页(模板绑定)、计提参数配置页(分组目录/批次提交/版本对比/导出/计算预览) - 首页 Bento 仪表盘(M1 空态设计;业务上线后接入真实统计,图表用 ECharts) - 后续:基础数据、业务数据、绩效核算、报表等业务页面 diff --git a/frontend/src/api/tplus.ts b/frontend/src/api/tplus.ts new file mode 100644 index 0000000..5e39874 --- /dev/null +++ b/frontend/src/api/tplus.ts @@ -0,0 +1,84 @@ +import { request } from './request' +import type { PageData } from './types' + +export type TaskType = 'READ' | 'AUDIT' +export type TaskStatus = 'QUEUED' | 'RUNNING' | 'SUCCEEDED' | 'PARTIAL' | 'FAILED' +export type EntityType = 'personnel' | 'products' | 'customers' | 'businesses' | 'shipments' + | 'receipts' | 'business_items' | 'shipment_items' | 'receipt_items' + +/** 仅包含系统字典和汇总,不包含客户记录或源配置。 */ +export interface AuditReport { + captured_at: string + source_version: string + relationship_status: string + compatibility_status: string + sales_links: Record | null + order_coverage: Record | null + state_enums: { id: number; code: string; name: string; enum_name: string }[] + receipt_kinds: { voucher_type_name: string; row_count: number }[] + receipt_line_kinds: { voucher_type_name: string | null; receive_flag: number; row_count: number }[] + pending_confirmations: { key: string; title: string; reason: string }[] +} + +export interface TPlusBatch { + batch_no: string + task_type: TaskType + status: TaskStatus + entity_types: EntityType[] | null + source_record_count: number + staged_record_count: number + skipped_record_count: number + failed_record_count: number + parent_batch_no: string | null + create_time: string + started_at: string | null + finished_at: string | null + can_retry: boolean + error_summary: string | null +} +export interface TPlusDetail extends TPlusBatch { audit_result: AuditReport | null } +export interface TPlusError { + id: number + entity_type: string + source_id: string | null + error_code: string + error_message: string + retry_count: number + create_time: string +} +export interface TPlusOverview { + is_configured: boolean + configuration_message: string + status_counts: Partial> + total_staged_records: number + compatibility_status: string + business_import_enabled: boolean + latest_audit: AuditReport | null +} +export interface BatchQuery { status?: TaskStatus; task_type?: TaskType; pageNum?: number; pageSize?: number } +export interface ErrorQuery { entity_type?: EntityType; pageNum?: number; pageSize?: number } +export interface CreateTask { task_type: TaskType; entity_types: EntityType[]; request_key: string } + +const base = '/v1/system/tplus' +/** 查询配置和任务状态;不会触发源库连接。 */ +export function getOverview(): Promise { return request({ url: `${base}/overview` }) } +/** 分页查询任务。 */ +export function listBatches(params: BatchQuery): Promise> { + return request({ url: `${base}/batches`, params }) +} +/** 按需查询报告。 */ +export function getBatch(number: string): Promise { + return request({ url: `${base}/batches/${encodeURIComponent(number)}` }) +} +/** 分页查询固定错误说明。 */ +export function listErrors(number: string, params: ErrorQuery): Promise> { + return request({ url: `${base}/batches/${encodeURIComponent(number)}/errors`, params }) +} +/** 以调用方保留的请求键持久化排队。 */ +export function createTask(data: CreateTask): Promise { + return request({ url: `${base}/batches`, method: 'post', data }) +} +/** 重试会创建新批次,原异常记录保留。 */ +export function retryTask(number: string, request_key: string): Promise { + return request({ url: `${base}/batches/${encodeURIComponent(number)}/retry`, method: 'post', data: { request_key } }) +} diff --git a/frontend/src/components/layout/menuIcons.ts b/frontend/src/components/layout/menuIcons.ts index adc0ab3..526d1f4 100644 --- a/frontend/src/components/layout/menuIcons.ts +++ b/frontend/src/components/layout/menuIcons.ts @@ -15,6 +15,7 @@ import { PieChart, Postcard, SetUp, + Refresh, Shop, Stamp, Tickets, @@ -47,6 +48,7 @@ const ICONS = { settings: OfficeBuilding, approvals: Stamp, logs: Tickets, + tplus: Refresh, personnel: Postcard, products: Box, customers: Shop, diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0d0bd47..ec8ec25 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -63,6 +63,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/views/system/users/index.vue'), meta: { title: '用户管理', icon: 'users', group: '系统管理', requiresAdmin: true, order: 2 }, }, + { + path: '/system/tplus', + name: 'system-tplus', + component: () => import('@/views/system/tplus/index.vue'), + meta: { title: 'T+ 数据同步', icon: 'tplus', group: '系统管理', requiresAdmin: true, order: 10 }, + }, { path: '/system/roles', name: 'system-roles', diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 89b641a..5d5fb70 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -19,8 +19,9 @@ export const MODULE_OPTIONS = [ '发货管理', '回款管理', '客户评级', + 'T+数据同步', ] as const -export const ACTION_OPTIONS = ['新增', '修改', '删除', '重置密码', '启用', '禁用'] as const +export const ACTION_OPTIONS = ['新增', '修改', '删除', '重置密码', '启用', '禁用', '创建读取任务', '重试读取任务'] as const /** 启用/禁用筛选选项(文案与 StatusTag 的展示一致) */ export const STATUS_OPTIONS = [ diff --git a/frontend/src/views/system/tplus/AuditResult.vue b/frontend/src/views/system/tplus/AuditResult.vue new file mode 100644 index 0000000..ddfd824 --- /dev/null +++ b/frontend/src/views/system/tplus/AuditResult.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/frontend/src/views/system/tplus/BatchDetailDialog.vue b/frontend/src/views/system/tplus/BatchDetailDialog.vue new file mode 100644 index 0000000..0a696b7 --- /dev/null +++ b/frontend/src/views/system/tplus/BatchDetailDialog.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/frontend/src/views/system/tplus/CreateTaskDialog.vue b/frontend/src/views/system/tplus/CreateTaskDialog.vue new file mode 100644 index 0000000..49d35dc --- /dev/null +++ b/frontend/src/views/system/tplus/CreateTaskDialog.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/views/system/tplus/index.vue b/frontend/src/views/system/tplus/index.vue new file mode 100644 index 0000000..a45503e --- /dev/null +++ b/frontend/src/views/system/tplus/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/frontend/src/views/system/tplus/labels.ts b/frontend/src/views/system/tplus/labels.ts new file mode 100644 index 0000000..af47ca7 --- /dev/null +++ b/frontend/src/views/system/tplus/labels.ts @@ -0,0 +1,17 @@ +import type { EntityType, TaskStatus, TPlusBatch } from '@/api/tplus' + +export const entities: { value: EntityType; label: string }[] = [ + { value: 'personnel', label: '人员' }, { value: 'products', label: '产品' }, + { value: 'customers', label: '客户' }, { value: 'businesses', label: '订单' }, + { value: 'business_items', label: '订单明细' }, { value: 'shipments', label: '销货单' }, + { value: 'shipment_items', label: '销货明细' }, { value: 'receipts', label: '收付款单' }, + { value: 'receipt_items', label: '收付款明细' }, +] +export const statuses: Record = { + QUEUED: '等待处理', RUNNING: '处理中', SUCCEEDED: '已完成', PARTIAL: '部分失败', FAILED: '失败', +} +/** 历史范围未记录时保持未知,不默认成全部。 */ +export function scopeLabel(row: TPlusBatch): string { + if (row.task_type === 'AUDIT') return '关联与枚举检查' + return row.entity_types?.map((key) => entities.find((item) => item.value === key)?.label ?? key).join('、') || '历史批次,范围未记录' +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 9bfed84..abaf004 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -28,7 +28,7 @@ export default defineConfig(({ mode }) => { // 开发期走代理而非后端 CORS,符合规范「跨域在网关统一处理」; // 副作用是 /api/docs(Swagger)也能从 5173 端口打开,联调期只用一个域名。 '/api': { - target: 'http://127.0.0.1:8000', + target: env.DEV_API_TARGET || 'http://127.0.0.1:8000', changeOrigin: true, }, },