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 <cursoragent@cursor.com>
This commit is contained in:
1 parent
7ebbaa2784
commit
a4d213231c
7 files changed
+132
-26
No files matched your search
@@ -13,7 +13,7 @@ Web【车辆管理】下现有【ETC/油卡充值管理】(路由前缀 `/vehi
|
||||
| 产品结构 | 并列【ETC充值管理】与【油卡充值管理】(原名改名) |
|
||||
| ETC 子菜单 | 申请、审批、发放管理、审批人设置 |
|
||||
| 油卡侧 | 界面保持现状;列表等不再展示 `rechargeType=etc` |
|
||||
| ETC 顶部统计 | 不展示(`PlRechargeSummary` 不对 ETC 路由出现) |
|
||||
| ETC 顶部统计 | ETC 分组展示 `PlRechargeSummary`(仅 ETC 信息);油卡分组仅展示加油卡信息(验收调整) |
|
||||
| 前端实现 | 复制现有 4 个页面目录再改,便于后续各自演进 |
|
||||
| 后端实现 | 不拆服务/表;列表与写入靠类型过滤/约束;小改即可 |
|
||||
| 审批流程 / 审批人 | 保持现状,仍一套 `vehicle_recharge`;两边菜单都进同一配置(方案 A) |
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# 单包工程也须声明 packages,否则 pnpm 9+ 报 “packages field missing or empty”
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
allowBuilds:
|
||||
'@parcel/watcher': true
|
||||
'@tailwindcss/oxide': true
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
<article class="fuel-plate-card recharge-card recharge-notice">
|
||||
<div class="recharge-notice-head"> <div class="plate">消息通知</div><em>最新</em> </div>
|
||||
<div class="recharge-notice-list">
|
||||
<div v-for="notice in summary.notices" :key="notice.id" class="recharge-notice-item">
|
||||
<div v-for="notice in displayedNotices" :key="notice.id" class="recharge-notice-item">
|
||||
<time :datetime="notice.occurredAt">{{ notice.occurredAt.slice(0, 10) }}</time>
|
||||
<span
|
||||
>{{ notice.actorName }}{{ notice.roleName ? `(${notice.roleName})` : ''
|
||||
}}{{ notice.action }}{{ notice.licensePlate
|
||||
}}{{ notice.rechargeType === 'etc' ? 'ETC' : '加油卡' }}充值;</span
|
||||
}}{{ noticeTypeLabel(notice.rechargeType) }}充值;</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="!summary.notices.length" class="recharge-notice-item muted">暂无消息</div>
|
||||
<div v-if="!displayedNotices.length" class="recharge-notice-item muted">暂无消息</div>
|
||||
</div>
|
||||
</article>
|
||||
<article
|
||||
@@ -45,7 +45,7 @@
|
||||
>{{ statusLabels[vehicle.vehicleStatus] || vehicle.vehicleStatus || '—' }}</el-tag
|
||||
>
|
||||
</div>
|
||||
<div class="recharge-split">
|
||||
<div class="recharge-split" :class="{ 'is-single': kinds.length === 1 }">
|
||||
<div v-for="kind in kinds" :key="kind.value" class="recharge-pane">
|
||||
<div class="recharge-pane-h">
|
||||
<h4>{{ kind.label }}</h4>
|
||||
@@ -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<RechargeSummary | null>(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<string, string> = {
|
||||
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 [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PlRechargeSummary v-if="!isFullPage && showRechargeSummary" :apply-path="rechargeApplyPath" />
|
||||
<PlRechargeSummary
|
||||
v-if="!isFullPage && showRechargeSummary && rechargeSummaryMode"
|
||||
:apply-path="rechargeApplyPath"
|
||||
:mode="rechargeSummaryMode"
|
||||
/>
|
||||
<ArtGroupLeafTabs v-if="!isFullPage && showGroupLeafTabs" />
|
||||
|
||||
<!-- 勿强制 minHeight:calc(100vh-…),否则门户布局会叠出多余纵向滚动条 -->
|
||||
@@ -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<RechargeSummaryMode | null>(() => {
|
||||
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(
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user