From f2fc40605eebdb56810829a876901d44c0035026 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 20:34:08 +0800 Subject: [PATCH 001/642] 1 --- src/api/http.safe-integer.test.ts | 15 +++++++++++++++ src/api/http.ts | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 src/api/http.safe-integer.test.ts diff --git a/src/api/http.safe-integer.test.ts b/src/api/http.safe-integer.test.ts new file mode 100644 index 00000000..b820c4e8 --- /dev/null +++ b/src/api/http.safe-integer.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest' +import { parseApiJsonResponse, parseJsonWithSafeIntegers } from '@chongchong/shared' + +describe('safe integer API parsing', () => { + it('preserves backend long IDs as strings', () => { + expect(parseJsonWithSafeIntegers('{"id":2082674456475418625,"page":1}')).toEqual({ + id: '2082674456475418625', + page: 1, + }) + }) + + it('leaves non-JSON responses untouched', () => { + expect(parseApiJsonResponse('plain text')).toBe('plain text') + }) +}) diff --git a/src/api/http.ts b/src/api/http.ts index 87552097..81d14c6a 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -1,4 +1,5 @@ import axios, { type InternalAxiosRequestConfig } from 'axios' +import { parseApiJsonResponse } from '@chongchong/shared' export interface ApiEnvelope { code: string @@ -37,6 +38,7 @@ export interface ApiErrorDetails { export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 15_000, + transformResponse: [(data: unknown) => parseApiJsonResponse(data)], }) type RetryableRequestConfig = InternalAxiosRequestConfig & { _authRetry?: boolean } -- 2.54.0 From dee0b190149e8eda652563d22100a94dbff4d402 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 20:36:34 +0800 Subject: [PATCH 002/642] =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..82aa07b9 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,13 @@ +pipeline { + agent any + + stages { + stage('拉取 release/test 代码') { + steps { + checkout scm + sh 'echo "Jenkins 已拉取 release/test 分支代码"' + sh 'git log -1 --oneline' + } + } + } +} \ No newline at end of file -- 2.54.0 From ae81edf69a73872bd98a6d5b07ef79d76411c8ed Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 21:04:28 +0800 Subject: [PATCH 003/642] chore: vendor shared package for standalone build --- package.json | 2 +- pnpm-lock.yaml | 4 +- shared/AGENTS.md | 28 ++++++++ shared/README.md | 7 ++ shared/package.json | 11 ++++ shared/src/index.ts | 155 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 shared/AGENTS.md create mode 100644 shared/README.md create mode 100644 shared/package.json create mode 100644 shared/src/index.ts diff --git a/package.json b/package.json index 00477f9f..0613f286 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "preview": "vite preview --port 4173" }, "dependencies": { - "@chongchong/shared": "link:../../packages/shared", + "@chongchong/shared": "link:./shared", "@lucide/vue": "^1.27.0", "axios": "^1.18.1", "element-plus": "^2.14.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb5157c9..53a31cc7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@chongchong/shared': - specifier: link:../../packages/shared - version: link:../../packages/shared + specifier: link:./shared + version: link:./shared '@lucide/vue': specifier: ^1.27.0 version: 1.27.0(vue@3.5.40(typescript@5.9.3)) diff --git a/shared/AGENTS.md b/shared/AGENTS.md new file mode 100644 index 00000000..e25fc861 --- /dev/null +++ b/shared/AGENTS.md @@ -0,0 +1,28 @@ +# Shared Package Instructions + +## 项目定位 + +- 本包提供跨应用共享的 TypeScript 类型、常量、轻量工具和可复用资源。 +- 当前公共能力包括产品名称、用户模型、导航模型、API 响应模型和头程订单状态常量。 +- 只有具有明确跨项目语义,或已经被两个或以上项目稳定复用的能力才进入本包;单一应用的页面状态、UI 组件和临时业务逻辑留在对应项目。尚无消费者的契约型导出必须标记为暂定,并有对应 API 或产品决策依据。 + +## 公共 API 与依赖边界 + +- `src/index.ts` 是当前唯一公共入口。新增导出应有清晰命名和稳定语义,不让消费者依赖未导出的内部实现。 +- 公共类型保持平台无关,不依赖 DOM、Vue、微信、uni-app 或 Node 专属运行时,除非任务明确改变本包定位。 +- 不在共享类型中编码客户端鉴权结论;租户隔离、权限和数据合法性仍由 `services/api` 最终校验。 +- 避免以“复用”为由建立宽泛的万能类型。优先使用小而明确的模型,并区分请求、响应、领域状态和展示模型。 +- 新增依赖前先确认原生 TypeScript 能力或现有依赖无法满足需求;本包不应成为所有应用依赖的重量级入口。 + +## 兼容性与变更原则 + +- 修改导出名称、字段类型、枚举值或常量语义前,搜索所有消费者并评估兼容性。 +- 优先采用向后兼容的新增;删除或破坏性修改必须同步更新全部消费者,并在交付说明中明确迁移影响。 +- API 契约文档是服务端落地后的唯一权威来源;共享包只映射经确认的客户端模型,不反向定义服务端行为。API 尚未落地前,`ApiResponse` 和订单状态等无消费者导出视为暂定,不得当作已发布契约。 +- 状态码、权限码和业务常量应有唯一来源,不在多个应用中复制维护相同列表。 + +## 质量门禁 + +- 当前包未声明独立的安装、测试、类型检查或构建脚本,不得声称这些检查已在本包内通过。 +- 变更公共导出后,至少运行现有消费者的类型检查;当前直接消费者包括 `apps/admin-web` 和 `apps/client-web`。 +- 若新增本包自己的质量脚本,应同步更新 `package.json`、`README.md` 和本文件,并为公共工具及兼容性边界增加测试。 diff --git a/shared/README.md b/shared/README.md new file mode 100644 index 00000000..ec462282 --- /dev/null +++ b/shared/README.md @@ -0,0 +1,7 @@ +# 共享模块 + +用于存放跨应用共享的类型定义、常量、工具和可复用资源。 + +当前已提供产品名称、用户模型、导航模型、API 响应模型,以及经一期产品决策确认的头程订单状态与运输方式常量。 + +当前还提供平台收货仓模型、订单仓库快照模型和供 Admin、Client 静态原型共用的演示仓库数据。订单仓库快照在下单时复制仓库 ID、编码、名称及国内地址,避免仓库主数据后续改名或停用影响历史订单展示;真实仓库列表、状态、下单可用性和快照持久化最终由服务端 API 保证。 diff --git a/shared/package.json b/shared/package.json new file mode 100644 index 00000000..5e802c2d --- /dev/null +++ b/shared/package.json @@ -0,0 +1,11 @@ +{ + "name": "@chongchong/shared", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "types": "./src/index.ts" +} + diff --git a/shared/src/index.ts b/shared/src/index.ts new file mode 100644 index 00000000..5bf0aef9 --- /dev/null +++ b/shared/src/index.ts @@ -0,0 +1,155 @@ +export const PRODUCT_NAME = '虫虫出海' + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER) + +function quoteUnsafeIntegerTokens(source: string) { + let output = '' + let index = 0 + let inString = false + let escaped = false + + while (index < source.length) { + const character = source[index] + + if (inString) { + output += character + if (escaped) escaped = false + else if (character === '\\') escaped = true + else if (character === '"') inString = false + index += 1 + continue + } + + if (character === '"') { + inString = true + output += character + index += 1 + continue + } + + if (character === '-' || (character >= '0' && character <= '9')) { + const token = source.slice(index).match(/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0] + if (token) { + const isInteger = !token.includes('.') && !/[eE]/.test(token) + if (isInteger && (BigInt(token) > MAX_SAFE_INTEGER || BigInt(token) < -MAX_SAFE_INTEGER)) output += JSON.stringify(token) + else output += token + index += token.length + continue + } + } + + output += character + index += 1 + } + + return output +} + +export function parseJsonWithSafeIntegers(source: string) { + return JSON.parse(quoteUnsafeIntegerTokens(source)) as unknown +} + +export function parseApiJsonResponse(data: unknown) { + if (typeof data !== 'string') return data + const trimmed = data.trim() + if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) return data + try { + return parseJsonWithSafeIntegers(data) + } catch { + return data + } +} + +export type AppCode = 'admin' | 'client' + +export interface CurrentUser { + id: string + name: string + account: string + tenantName?: string + roles: string[] +} + +export interface NavigationItem { + path: string + title: string + permission?: string +} + +export interface ApiResponse { + code: number + message: string + data: T +} + +export const FIRST_LEG_ORDER_STATUSES = [ + { code: 'DRAFT', label: '草稿' }, + { code: 'PENDING_REVIEW', label: '待审核' }, + { code: 'ACCEPTED', label: '已受理' }, + { code: 'BOOKED', label: '已订舱' }, + { code: 'IN_TRANSIT', label: '运输中' }, + { code: 'COMPLETED', label: '已完成' }, + { code: 'CANCELLED', label: '已取消' }, +] as const + +export type FirstLegOrderStatus = (typeof FIRST_LEG_ORDER_STATUSES)[number]['code'] +export type FirstLegOrderStatusLabel = (typeof FIRST_LEG_ORDER_STATUSES)[number]['label'] + +export const FIRST_LEG_TRANSPORT_MODES = [ + { code: 'FCL', label: '海运整箱 FCL' }, + { code: 'LCL', label: '海运拼箱 LCL' }, + { code: 'AIR', label: '空运' }, + { code: 'ROAD', label: '陆运' }, +] as const + +export type FirstLegTransportMode = (typeof FIRST_LEG_TRANSPORT_MODES)[number]['code'] +export type FirstLegTransportModeLabel = (typeof FIRST_LEG_TRANSPORT_MODES)[number]['label'] + +export type PlatformWarehouseStatus = 'ACTIVE' | 'INACTIVE' + +export interface PlatformReceivingWarehouse { + id: string + code: string + name: string + province: string + city: string + address: string + contactName: string + contactPhone: string + businessHours: string + status: PlatformWarehouseStatus + remark: string + updatedAt: string +} + +export interface PlatformReceivingWarehouseSnapshot { + id: string + code: string + name: string + province: string + city: string + address: string +} + +export function createPlatformReceivingWarehouseSnapshot( + warehouse: PlatformReceivingWarehouse, +): PlatformReceivingWarehouseSnapshot { + return { + id: warehouse.id, + code: warehouse.code, + name: warehouse.name, + province: warehouse.province, + city: warehouse.city, + address: warehouse.address, + } +} + +// API 接入前供 Admin 与 Client 静态原型共用,不能作为服务端真实仓库数据。 +export const DEMO_PLATFORM_RECEIVING_WAREHOUSES: readonly PlatformReceivingWarehouse[] = [ + { id: 'WH001', code: 'CN-YW-01', name: '义乌仓', province: '浙江省', city: '义乌市', address: '北苑街道演示物流园 1 号库', contactName: '仓库客服', contactPhone: '0579-****001', businessHours: '周一至周六 08:30-18:00', status: 'ACTIVE', remark: '支持普通货物收货。', updatedAt: '2026-07-28 10:20' }, + { id: 'WH002', code: 'CN-SZ-01', name: '深圳仓', province: '广东省', city: '深圳市', address: '盐田区演示物流园 2 号库', contactName: '仓库客服', contactPhone: '0755-****002', businessHours: '周一至周六 08:30-18:00', status: 'ACTIVE', remark: '华南区域主要收货仓。', updatedAt: '2026-07-28 10:20' }, + { id: 'WH003', code: 'CN-QD-01', name: '青岛仓', province: '山东省', city: '青岛市', address: '黄岛区演示物流园 3 号库', contactName: '仓库客服', contactPhone: '0532-****003', businessHours: '周一至周五 09:00-17:30', status: 'ACTIVE', remark: '', updatedAt: '2026-07-28 10:20' }, + { id: 'WH004', code: 'CN-SH-01', name: '上海仓', province: '上海市', city: '上海市', address: '浦东新区演示物流园 4 号库', contactName: '仓库客服', contactPhone: '021-****004', businessHours: '周一至周六 08:30-18:00', status: 'ACTIVE', remark: '华东区域综合收货仓。', updatedAt: '2026-07-28 10:20' }, + { id: 'WH005', code: 'CN-NB-01', name: '宁波仓', province: '浙江省', city: '宁波市', address: '北仑区演示物流园 5 号库', contactName: '仓库客服', contactPhone: '0574-****005', businessHours: '周一至周六 08:30-18:00', status: 'ACTIVE', remark: '临近宁波港。', updatedAt: '2026-07-28 10:20' }, + { id: 'WH006', code: 'CN-XM-01', name: '厦门仓', province: '福建省', city: '厦门市', address: '湖里区演示物流园 6 号库', contactName: '仓库客服', contactPhone: '0592-****006', businessHours: '周一至周五 09:00-17:30', status: 'INACTIVE', remark: '静态演示:暂停接收新订单。', updatedAt: '2026-07-28 10:20' }, +] -- 2.54.0 From c1f6423c207f2b79fccaad86ebaf88037d8d4656 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 21:18:39 +0800 Subject: [PATCH 004/642] ci: add test environment frontend deployment --- .dockerignore | 8 ++++++++ Dockerfile | 22 ++++++++++++++++++++++ Jenkinsfile | 36 +++++++++++++++++++++++++++++++++--- docker-compose.test.yml | 13 +++++++++++++ nginx/default.conf.template | 29 +++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.test.yml create mode 100644 nginx/default.conf.template diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b59e8c23 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.playwright-cli +dist +node_modules +node_modules.broken-by-path +.env.local +npm-debug.log* +pnpm-debug.log* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..73f2bdaa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +# 构建 Vue 静态文件 +FROM node:22-alpine AS build + +WORKDIR /app + +# Corepack 随 Node 一起提供,用它固定使用项目的 pnpm。 +RUN corepack enable + +COPY package.json pnpm-lock.yaml ./ +COPY shared/package.json ./shared/package.json +RUN pnpm install --frozen-lockfile + +COPY . . +RUN pnpm test && pnpm build + +# 运行阶段:仅保留构建产物和 Nginx,不包含源码或 node_modules。 +FROM nginx:1.27-alpine + +COPY nginx/default.conf.template /etc/nginx/templates/default.conf.template +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 diff --git a/Jenkinsfile b/Jenkinsfile index 82aa07b9..64a735c5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,13 +1,43 @@ pipeline { agent any + options { + timestamps() + disableConcurrentBuilds() + } + stages { - stage('拉取 release/test 代码') { + stage('确认提交版本') { steps { - checkout scm sh 'echo "Jenkins 已拉取 release/test 分支代码"' sh 'git log -1 --oneline' } } + + stage('构建并部署测试前端') { + steps { + sh ''' + set -eu + docker compose -f docker-compose.test.yml build --pull=false + docker compose -f docker-compose.test.yml up -d --remove-orphans + ''' + } + } + + stage('部署检查') { + steps { + sh ''' + set -eu + docker compose -f docker-compose.test.yml ps + docker compose -f docker-compose.test.yml exec -T admin-web wget -qO- http://127.0.0.1/healthz + ''' + } + } } -} \ No newline at end of file + + post { + failure { + sh 'docker compose -f docker-compose.test.yml logs --tail=100 || true' + } + } +} diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 00000000..a1554047 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,13 @@ +services: + admin-web: + build: + context: . + dockerfile: Dockerfile + image: admin-web-test:local + container_name: admin-web-test + restart: unless-stopped + ports: + - "${HOST_PORT:-18080}:80" + environment: + # 当前测试后端 Gateway;以后迁移环境时只需要改这里或 Jenkins 环境变量。 + API_UPSTREAM: "${API_UPSTREAM:-http://8.163.30.128:9000}" diff --git a/nginx/default.conf.template b/nginx/default.conf.template new file mode 100644 index 00000000..2ae3e079 --- /dev/null +++ b/nginx/default.conf.template @@ -0,0 +1,29 @@ +server { + listen 80; + listen [::]:80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Docker 容器运行正常的检查地址,供 Jenkins 使用。 + location = /healthz { + default_type text/plain; + return 200 "ok\n"; + } + + # 前端代码中的 API 前缀为 /api;保持原始路径转给 Gateway。 + location /api/ { + proxy_pass ${API_UPSTREAM}; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Vue Router 的 history 路由刷新时仍返回 index.html。 + location / { + try_files $uri $uri/ /index.html; + } +} -- 2.54.0 From 0b0f86218a1b41061943000733677c45c72c1a07 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 30 Jul 2026 21:23:39 +0800 Subject: [PATCH 005/642] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/platformIam.ts | 8 +++---- src/utils/platformPermission.test.ts | 36 ++++++++++++++++++++++++++++ src/utils/platformPermission.ts | 17 +++++++++++++ src/views/CounterpartiesView.vue | 2 +- src/views/CustomersView.vue | 18 +++++++------- src/views/EnterpriseReviewsView.vue | 18 +++++++------- src/views/ExpressCompaniesView.vue | 2 +- src/views/FirstLegOrdersView.vue | 2 +- src/views/InternalUsersView.vue | 2 +- src/views/PermissionsView.vue | 21 ++++------------ src/views/RolesView.vue | 2 +- src/views/WarehousesView.vue | 6 ++--- 12 files changed, 87 insertions(+), 47 deletions(-) create mode 100644 src/utils/platformPermission.test.ts create mode 100644 src/utils/platformPermission.ts diff --git a/src/api/platformIam.ts b/src/api/platformIam.ts index 96fcaeeb..8c1f2c87 100644 --- a/src/api/platformIam.ts +++ b/src/api/platformIam.ts @@ -1,11 +1,11 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/api/http' export interface PlatformPermissionResource { - id: number - tenantId: number + id: number | string + tenantId: number | string appCode: 'PLATFORM' permissionType: 'MENU' | 'BUTTON' - parentId: number + parentId: number | string permissionCode: string permissionName: string routePath: string | null @@ -64,7 +64,7 @@ export interface CreatePlatformPermissionRequest { type: 'MENU' | 'BUTTON' code: string name: string - parentId: number + parentId: number | string routePath: string | null httpMethod: null sortNo: number diff --git a/src/utils/platformPermission.test.ts b/src/utils/platformPermission.test.ts new file mode 100644 index 00000000..381a73ee --- /dev/null +++ b/src/utils/platformPermission.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import { mapPlatformPermission } from '@/utils/platformPermission' + +describe('mapPlatformPermission', () => { + it('treats the string root parent as a root node and preserves long ids', () => { + expect(mapPlatformPermission({ + id: '2082813911786024961', + tenantId: '0', + appCode: 'PLATFORM', + permissionType: 'MENU', + parentId: '0', + permissionCode: 'platform:audit', + permissionName: '审计管理', + routePath: '/audit', + httpMethod: null, + sortNo: 50, + status: 'ACTIVE', + })).toMatchObject({ id: '2082813911786024961', parentId: null, code: 'platform:audit' }) + }) + + it('preserves a long string parent id for child resources', () => { + expect(mapPlatformPermission({ + id: '2082813911786024962', + tenantId: '0', + appCode: 'PLATFORM', + permissionType: 'BUTTON', + parentId: '2082813911786024961', + permissionCode: 'platform:audit:list', + permissionName: '操作日志', + routePath: null, + httpMethod: null, + sortNo: 10, + status: 'ACTIVE', + })).toMatchObject({ id: '2082813911786024962', parentId: '2082813911786024961', type: '按钮' }) + }) +}) diff --git a/src/utils/platformPermission.ts b/src/utils/platformPermission.ts new file mode 100644 index 00000000..05e4638c --- /dev/null +++ b/src/utils/platformPermission.ts @@ -0,0 +1,17 @@ +import type { PlatformPermissionResource } from '@/api/platformIam' +import type { Permission } from '@/types/admin' + +export function mapPlatformPermission(item: PlatformPermissionResource): Permission { + const parentId = String(item.parentId) + return { + id: String(item.id), + parentId: parentId === '0' ? null : parentId, + name: item.permissionName, + code: item.permissionCode, + type: item.permissionType === 'BUTTON' ? '按钮' : '菜单', + path: item.routePath ?? '', + method: item.httpMethod ?? '-', + status: item.status === 'ACTIVE' ? '启用' : '停用', + sort: item.sortNo, + } +} diff --git a/src/views/CounterpartiesView.vue b/src/views/CounterpartiesView.vue index 49d32e32..143024d2 100644 --- a/src/views/CounterpartiesView.vue +++ b/src/views/CounterpartiesView.vue @@ -120,12 +120,12 @@ function addressText(item: Counterparty) {

往来单位档案

平台维护完整主数据;企业客户端仅可只读选择已启用的发货单位和收货单位。

+ -
显示 {{ pagedRows.length }} 条,共 {{ filteredRows.length }} 条
diff --git a/src/views/CustomersView.vue b/src/views/CustomersView.vue index a411cbc5..a437536e 100644 --- a/src/views/CustomersView.vue +++ b/src/views/CustomersView.vue @@ -135,6 +135,15 @@ function exportCustomers() {

客户档案

共 {{ filteredRows.length }} 家企业,客户资料与注册审核统一管理。

+ + + @@ -144,15 +153,6 @@ function exportCustomers() { - - -
显示 {{ pagedRows.length }} 条,共 {{ filteredRows.length }} 条
diff --git a/src/views/EnterpriseReviewsView.vue b/src/views/EnterpriseReviewsView.vue index 7364860b..83dc6bdb 100644 --- a/src/views/EnterpriseReviewsView.vue +++ b/src/views/EnterpriseReviewsView.vue @@ -216,6 +216,15 @@ onBeforeUnmount(clearPreviewUrl)

企业资料审核

仅展示后端返回的待审核记录,提交审核时使用审核单 ID。

+ + +