From a4d213231c6ea3ef73f522401a4031819b316752 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Thu, 12 Nov 2026 20:39:48 +0800 Subject: [PATCH] feat(web-admin): split recharge summary by fuel/ETC and fix pnpm workspace Show fuel-only cards on fuel routes and ETC-only cards on ETC routes with a 5-column layout, and declare packages in pnpm-workspace.yaml so pnpm 9+ can build. Co-authored-by: Cursor --- ...11-12-pl-etc-fuel-recharge-split-design.md | 2 +- web-admin/pnpm-workspace.yaml | 4 ++ .../business/pl-recharge-summary/index.vue | 36 ++++++++++++--- .../pl-recharge-summary/recharge-summary.scss | 8 +++- .../core/layouts/art-page-content/index.vue | 39 +++++++++++++--- .../src/utils/pl-recharge-summary.test.ts | 45 ++++++++++++++++--- web-admin/src/utils/pl-recharge-summary.ts | 24 ++++++++-- 7 files changed, 132 insertions(+), 26 deletions(-) diff --git a/docs/superpowers/specs/2026-11-12-pl-etc-fuel-recharge-split-design.md b/docs/superpowers/specs/2026-11-12-pl-etc-fuel-recharge-split-design.md index 2c63ce08f..94c8e1039 100644 --- a/docs/superpowers/specs/2026-11-12-pl-etc-fuel-recharge-split-design.md +++ b/docs/superpowers/specs/2026-11-12-pl-etc-fuel-recharge-split-design.md @@ -13,7 +13,7 @@ Web【车辆管理】下现有【ETC/油卡充值管理】(路由前缀 `/vehi | 产品结构 | 并列【ETC充值管理】与【油卡充值管理】(原名改名) | | ETC 子菜单 | 申请、审批、发放管理、审批人设置 | | 油卡侧 | 界面保持现状;列表等不再展示 `rechargeType=etc` | -| ETC 顶部统计 | 不展示(`PlRechargeSummary` 不对 ETC 路由出现) | +| ETC 顶部统计 | ETC 分组展示 `PlRechargeSummary`(仅 ETC 信息);油卡分组仅展示加油卡信息(验收调整) | | 前端实现 | 复制现有 4 个页面目录再改,便于后续各自演进 | | 后端实现 | 不拆服务/表;列表与写入靠类型过滤/约束;小改即可 | | 审批流程 / 审批人 | 保持现状,仍一套 `vehicle_recharge`;两边菜单都进同一配置(方案 A) | diff --git a/web-admin/pnpm-workspace.yaml b/web-admin/pnpm-workspace.yaml index 5a7620a6a..96220d2d7 100644 --- a/web-admin/pnpm-workspace.yaml +++ b/web-admin/pnpm-workspace.yaml @@ -1,3 +1,7 @@ +# 单包工程也须声明 packages,否则 pnpm 9+ 报 “packages field missing or empty” +packages: + - '.' + allowBuilds: '@parcel/watcher': true '@tailwindcss/oxide': true diff --git a/web-admin/src/components/business/pl-recharge-summary/index.vue b/web-admin/src/components/business/pl-recharge-summary/index.vue index 96d0634b5..2e71f2d5e 100644 --- a/web-admin/src/components/business/pl-recharge-summary/index.vue +++ b/web-admin/src/components/business/pl-recharge-summary/index.vue @@ -9,15 +9,15 @@
消息通知
最新
-
+
{{ notice.actorName }}{{ notice.roleName ? `(${notice.roleName})` : '' }}{{ notice.action }}{{ notice.licensePlate - }}{{ notice.rechargeType === 'etc' ? 'ETC' : '加油卡' }}充值;
-
暂无消息
+
暂无消息
{{ statusLabels[vehicle.vehicleStatus] || vehicle.vehicleStatus || '—' }} -
+

{{ kind.label }}

@@ -94,10 +94,17 @@ } from '@/api/pl/recharge-summary' import { pickBxData } from '@/utils/http/bx' import { formatPlVehiclePlateDisplay } from '@/utils/pl-display' - import { rechargeComparison, rechargeSummaryQuery } from '@/utils/pl-recharge-summary' + import { + rechargeComparison, + rechargeSummaryQuery, + type RechargeSummaryMode + } from '@/utils/pl-recharge-summary' import mittBus from '@/utils/sys/mittBus' - const props = defineProps<{ applyPath: string }>() + const props = withDefaults( + defineProps<{ applyPath: string; mode?: RechargeSummaryMode }>(), + { mode: 'fuel' } + ) const router = useRouter() const route = useRoute() const summary = ref(null) @@ -107,12 +114,22 @@ vehicle.vehicleType === 'self' && !['油桶', '叉车'].includes(vehicle.licensePlate.trim()) ) ) + const displayedNotices = computed(() => { + const notices = summary.value?.notices || [] + if (props.mode === 'etc') return notices.filter((n) => n.rechargeType === 'etc') + return notices.filter((n) => n.rechargeType !== 'etc') + }) const loading = ref(false) const error = ref(false) - const kinds = [ + const kindDefs = [ { value: 'etc', label: 'ETC' }, { value: 'fuel', label: '加油卡' } ] as const + const kinds = computed(() => + kindDefs.filter((kind) => + props.mode === 'etc' ? kind.value === 'etc' : kind.value === 'fuel' + ) + ) const statusLabels: Record = { available: '可用', maintaining: '维修中', @@ -122,6 +139,11 @@ Number(value).toLocaleString('zh-CN', { maximumFractionDigits: 0 }) let requestVersion = 0 + function noticeTypeLabel(rechargeType: string) { + if (rechargeType === 'etc') return 'ETC' + return '加油卡' + } + function metrics(amounts: RechargeAmounts) { return [ { diff --git a/web-admin/src/components/business/pl-recharge-summary/recharge-summary.scss b/web-admin/src/components/business/pl-recharge-summary/recharge-summary.scss index 00f3b21c2..d770753c2 100644 --- a/web-admin/src/components/business/pl-recharge-summary/recharge-summary.scss +++ b/web-admin/src/components/business/pl-recharge-summary/recharge-summary.scss @@ -186,11 +186,13 @@ } .recharge-grid { - grid-template-columns: repeat(3, 1fr); + /* 单栏(油卡/ETC 分开展示):固定一行 5 张 */ + grid-template-columns: repeat(5, 1fr); } .recharge-card { cursor: default; + padding: 12px 14px; } .recharge-split { @@ -199,6 +201,10 @@ gap: 10px; } +.recharge-split.is-single { + grid-template-columns: 1fr; +} + .recharge-pane { min-width: 0; padding: 10px 12px; diff --git a/web-admin/src/components/core/layouts/art-page-content/index.vue b/web-admin/src/components/core/layouts/art-page-content/index.vue index 79693ed5f..90d54ee65 100644 --- a/web-admin/src/components/core/layouts/art-page-content/index.vue +++ b/web-admin/src/components/core/layouts/art-page-content/index.vue @@ -14,7 +14,11 @@
- + @@ -71,7 +75,11 @@ import { layoutOutletCacheKey, resolveLeafGroup } from '@/utils/router/leaf-group' import ArtGroupLeafTabs from '@/components/core/layouts/art-group-leaf-tabs/index.vue' import PlRechargeSummary from '@/components/business/pl-recharge-summary/index.vue' - import { isRechargeSummaryRoute } from '@/utils/pl-recharge-summary' + import { + isRechargeSummaryRoute, + rechargeSummaryModeFromPath, + type RechargeSummaryMode + } from '@/utils/pl-recharge-summary' defineOptions({ name: 'ArtPageContent' }) @@ -93,12 +101,29 @@ const showGroupLeafTabs = computed(() => (leafGroup.value?.leaves.length || 0) > 1) const leafGroupId = computed(() => leafGroup.value?.groupId || '') - const rechargeApplyPath = computed(() => - leafGroup.value?.leaves.find((leaf) => leaf.meta.menuCode === 'pl:vehicle-recharge-apply:list')?.path || '' + const rechargeSummaryMode = computed(() => { + const fromRoute = rechargeSummaryModeFromPath(route.path) + if (fromRoute) return fromRoute + for (const leaf of leafGroup.value?.leaves || []) { + const mode = rechargeSummaryModeFromPath(leaf.path) + if (mode) return mode + } + return null + }) + const rechargeApplyMenuCode = computed(() => + rechargeSummaryMode.value === 'etc' + ? 'pl:etc-vehicle-recharge-apply:list' + : 'pl:vehicle-recharge-apply:list' ) - const showRechargeSummary = computed(() => - isRechargeSummaryRoute(route.path) || - !!leafGroup.value?.leaves.some((leaf) => isRechargeSummaryRoute(leaf.path)) + const rechargeApplyPath = computed( + () => + leafGroup.value?.leaves.find((leaf) => leaf.meta.menuCode === rechargeApplyMenuCode.value) + ?.path || '' + ) + const showRechargeSummary = computed( + () => + isRechargeSummaryRoute(route.path) || + !!leafGroup.value?.leaves.some((leaf) => isRechargeSummaryRoute(leaf.path)) ) const useGroupOutletCache = computed( diff --git a/web-admin/src/utils/pl-recharge-summary.test.ts b/web-admin/src/utils/pl-recharge-summary.test.ts index e3a9c1602..1818c9858 100644 --- a/web-admin/src/utils/pl-recharge-summary.test.ts +++ b/web-admin/src/utils/pl-recharge-summary.test.ts @@ -3,7 +3,10 @@ import { describe, it } from 'node:test' import { rechargeComparison, rechargeSummaryQuery, - isRechargeSummaryRoute + isRechargeSummaryRoute, + isFuelRechargeSummaryRoute, + isEtcRechargeSummaryRoute, + rechargeSummaryModeFromPath } from './pl-recharge-summary' describe('充值统计卡片', () => { @@ -30,32 +33,60 @@ describe('充值统计卡片', () => { }) }) - it('只对油卡充值业务子页显示,ETC 与其它车辆页不显示', () => { + it('油卡分组子页识别为 fuel 模式', () => { assert.equal( - isRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-apply'), + isFuelRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-apply'), true ) assert.equal( - isRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-record'), + isFuelRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-record'), true ) assert.equal( - isRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-approval'), + isFuelRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-approval'), true ) assert.equal( - isRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-workflow'), + isFuelRechargeSummaryRoute('/vehicle-mgmt/fuel-recharge/vehicle-recharge-workflow'), + true + ) + assert.equal(rechargeSummaryModeFromPath('/vehicle-mgmt/fuel-recharge/vehicle-recharge-apply'), 'fuel') + }) + + it('ETC 分组子页识别为 etc 模式并展示顶部统计', () => { + assert.equal( + isEtcRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-apply'), + true + ) + assert.equal( + isEtcRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-approval'), + true + ) + assert.equal( + isEtcRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-record'), + true + ) + assert.equal( + isEtcRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-workflow'), true ) assert.equal( isRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-apply'), - false + true ) + assert.equal( + rechargeSummaryModeFromPath('/vehicle-mgmt/etc-recharge/etc-vehicle-recharge-apply'), + 'etc' + ) + }) + + it('旧路径与其它车辆页不显示充值统计', () => { assert.equal( isRechargeSummaryRoute('/vehicle-mgmt/etc-recharge/vehicle-recharge-apply'), false ) assert.equal(isRechargeSummaryRoute('/vehicle-mgmt/vehicle-fuel-card'), false) assert.equal(isRechargeSummaryRoute('/vehicle-mgmt/vehicle'), false) + assert.equal(rechargeSummaryModeFromPath('/vehicle-mgmt/vehicle'), null) }) }) diff --git a/web-admin/src/utils/pl-recharge-summary.ts b/web-admin/src/utils/pl-recharge-summary.ts index ffaab3a1e..415de04ea 100644 --- a/web-admin/src/utils/pl-recharge-summary.ts +++ b/web-admin/src/utils/pl-recharge-summary.ts @@ -13,12 +13,30 @@ export function rechargeComparison( } } +export type RechargeSummaryMode = 'fuel' | 'etc' + export function rechargeSummaryQuery(plate: string, kind: 'etc' | 'fuel') { return { rechargePlate: plate, rechargeKind: kind } } +/** 油卡充值分组下的申请/审批/发放/审批人设置 */ +export function isFuelRechargeSummaryRoute(path: string) { + return /\/fuel-recharge\/vehicle-recharge-(apply|approval|record|workflow)\/?$/.test(path) +} + +/** ETC 充值分组下的申请/审批/发放/审批人设置 */ +export function isEtcRechargeSummaryRoute(path: string) { + return /\/etc-recharge\/etc-vehicle-recharge-(apply|approval|record|workflow)\/?$/.test(path) +} + +/** 是否展示顶部充值统计(油卡或 ETC 业务子页) */ export function isRechargeSummaryRoute(path: string) { - return /\/fuel-recharge\/vehicle-recharge-(apply|approval|record|workflow)\/?$/.test( - path - ) + return isFuelRechargeSummaryRoute(path) || isEtcRechargeSummaryRoute(path) +} + +/** 当前路由对应的统计模式;非充值业务页返回 null */ +export function rechargeSummaryModeFromPath(path: string): RechargeSummaryMode | null { + if (isEtcRechargeSummaryRoute(path)) return 'etc' + if (isFuelRechargeSummaryRoute(path)) return 'fuel' + return null }