diff --git a/miniprogram/api/pl.js b/miniprogram/api/pl.js
new file mode 100644
index 000000000..75342fc97
--- /dev/null
+++ b/miniprogram/api/pl.js
@@ -0,0 +1,91 @@
+/**
+ * 运输加油登记 API(复用门户 /transport/vehicle-fuel-register)
+ */
+import db from '@/utils/dbRequest'
+import { fetchVehicleOptions } from '@/api/dbOptions'
+
+function unwrapList(data) {
+ if (!data || typeof data !== 'object') {
+ return { items: [], total: 0 }
+ }
+ // 兼容门户信封 { code, data: { items, total } }
+ const payload =
+ data.data && typeof data.data === 'object' && data.items == null
+ ? data.data
+ : data
+ const items = payload.items || payload.rows || payload.list || []
+ const total = Number(payload.total || 0)
+ return { items: Array.isArray(items) ? items : [], total }
+}
+
+function unwrapEntity(data) {
+ if (!data || typeof data !== 'object') return data
+ if (data.data && typeof data.data === 'object' && data.id == null && data.registerNo == null) {
+ return data.data
+ }
+ return data
+}
+
+export const fuelRegisterApi = {
+ list: async (params = {}) => {
+ const data = await db.get('/transport/vehicle-fuel-register/', {
+ mineOnly: true,
+ ...params
+ })
+ return unwrapList(data)
+ },
+ get: async (id) => unwrapEntity(await db.get(`/transport/vehicle-fuel-register/${id}`)),
+ create: (payload) =>
+ db.post('/transport/vehicle-fuel-register/', payload, {
+ params: { mineOnly: true }
+ }),
+ update: (id, payload) =>
+ db.put(`/transport/vehicle-fuel-register/${id}`, payload, {
+ params: { mineOnly: true }
+ }),
+ cancel: (id) =>
+ db.post(
+ `/transport/vehicle-fuel-register/${id}/cancel`,
+ {},
+ { params: { mineOnly: true } }
+ )
+}
+
+function unwrapData(data) {
+ if (!data || typeof data !== 'object') return data
+ if (data.code != null && data.data !== undefined) return data.data
+ return data
+}
+
+export const sitePlanApi = {
+ history: async (params = {}) => {
+ const data = unwrapData(await db.get('/transport/site-plan/history', params))
+ return {
+ date: data?.date || params.date,
+ keyword: data?.keyword || '',
+ records: Array.isArray(data?.records) ? data.records : []
+ }
+ },
+ get: async (planId) => unwrapData(await db.get(`/transport/site-plan/${planId}`)),
+ saveFeedback: (planId, payload) =>
+ db.post(`/transport/site-plan/${planId}/feedback`, payload)
+}
+
+export const driverPlanApi = {
+ history: async (params = {}) => {
+ const data = unwrapData(await db.get('/transport/driver-plan/history', params))
+ return {
+ date: data?.date || params.date,
+ records: Array.isArray(data?.records) ? data.records : []
+ }
+ },
+ get: async (planId) => unwrapData(await db.get(`/transport/driver-plan/${planId}`)),
+ saveFeedback: (planId, payload) =>
+ db.post(`/transport/driver-plan/${planId}/feedback`, payload),
+ saveDriverFeedback: (planId, payload) =>
+ db.post(`/transport/driver-plan/${planId}/driver-feedback`, payload)
+}
+
+export { fetchVehicleOptions }
+
+export default fuelRegisterApi
diff --git a/miniprogram/config/api.js b/miniprogram/config/api.js
index 7093f0acf..cbf430ce5 100644
--- a/miniprogram/config/api.js
+++ b/miniprogram/config/api.js
@@ -63,7 +63,13 @@ export const PAGE_PATHS = {
bxInvoiceAdmin: '/pages/bx/invoice/admin',
bxExpenseItem: '/pages/bx/admin/expense-item',
bxWfAdvance: '/pages/bx/admin/wf-advance',
- bxWfReimburse: '/pages/bx/admin/wf-reimburse'
+ bxWfReimburse: '/pages/bx/admin/wf-reimburse',
+ plFuelRegister: '/pages/pl/fuel-register/list',
+ plFuelRegisterForm: '/pages/pl/fuel-register/form',
+ plTransportPlan: '/pages/pl/transport-plan/list',
+ plTransportPlanDetail: '/pages/pl/transport-plan/detail',
+ plDriverPlan: '/pages/pl/driver-plan/list',
+ plDriverPlanDetail: '/pages/pl/driver-plan/detail'
}
/** 工作台菜单 code → 页面路径 */
@@ -80,7 +86,10 @@ export const WORKBENCH_ROUTE_MAP = {
'bx-invoice-admin': PAGE_PATHS.bxInvoiceAdmin,
'bx-expense-item': PAGE_PATHS.bxExpenseItem,
'bx-wf-advance': PAGE_PATHS.bxWfAdvance,
- 'bx-wf-reimburse': PAGE_PATHS.bxWfReimburse
+ 'bx-wf-reimburse': PAGE_PATHS.bxWfReimburse,
+ 'pl-fuel-register': PAGE_PATHS.plFuelRegister,
+ 'pl-transport-plan': PAGE_PATHS.plTransportPlan,
+ 'pl-driver-plan': PAGE_PATHS.plDriverPlan
}
export const TOKEN_KEY = 'sx_token'
diff --git a/miniprogram/config/workbench-menus.js b/miniprogram/config/workbench-menus.js
index 5fc6641fb..99d0a539f 100644
--- a/miniprogram/config/workbench-menus.js
+++ b/miniprogram/config/workbench-menus.js
@@ -56,19 +56,31 @@ export const WORKBENCH_MODULES = [
{
key: 'pl',
title: '运输管理',
- desc: '计划 · 运费 · 车辆事务',
+ desc: '加油登记 · 运输计划单 · 司机运输计划单',
iconText: '运输',
- visible: false,
+ visible: true,
menus: [
- { name: '运输计划', enabled: false, code: 'pl-plan', icon: '🚚' },
- { name: '计划归档', enabled: false, code: 'pl-plan-archive', icon: '🗂️' },
- { name: '购买证上传', enabled: false, code: 'pl-plan-cert', icon: '📜' },
- { name: '运费结算', enabled: false, code: 'pl-freight', icon: '💵' },
- { name: '运费审批', enabled: false, code: 'pl-freight-approval', icon: '🔏' },
- { name: '财务发放', enabled: false, icon: '💰' },
- { name: '车辆充值', enabled: false, icon: '⛽' },
- { name: '车辆维修', enabled: false, icon: '🔧' },
- { name: '运输地址库', enabled: false, icon: '📍' }
+ {
+ name: '加油登记',
+ enabled: true,
+ code: 'pl-fuel-register',
+ icon: '⛽',
+ perms: 'mini:pl:fuelRegister:list'
+ },
+ {
+ name: '运输计划单',
+ enabled: true,
+ code: 'pl-transport-plan',
+ icon: '📋',
+ perms: 'mini:pl:transportPlan:list'
+ },
+ {
+ name: '司机运输计划单',
+ enabled: true,
+ code: 'pl-driver-plan',
+ icon: '🚛',
+ perms: 'mini:pl:driverPlan:list'
+ }
]
}
]
diff --git a/miniprogram/pages.json b/miniprogram/pages.json
index a3b7553c1..f539d26f5 100644
--- a/miniprogram/pages.json
+++ b/miniprogram/pages.json
@@ -172,12 +172,42 @@
"style": { "navigationStyle": "custom", "navigationBarTitleText": "工作流(报销)" }
}
]
+ },
+ {
+ "root": "pages/pl",
+ "name": "pl",
+ "pages": [
+ {
+ "path": "fuel-register/list",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "加油登记" }
+ },
+ {
+ "path": "fuel-register/form",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "加油登记" }
+ },
+ {
+ "path": "transport-plan/list",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "运输计划单" }
+ },
+ {
+ "path": "transport-plan/detail",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "现场回传" }
+ },
+ {
+ "path": "driver-plan/list",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "司机运输计划单" }
+ },
+ {
+ "path": "driver-plan/detail",
+ "style": { "navigationStyle": "custom", "navigationBarTitleText": "司机回执" }
+ }
+ ]
}
],
"preloadRule": {
"pages/workbench/workbench": {
"network": "all",
- "packages": ["db", "bx"]
+ "packages": ["db", "bx", "pl"]
}
},
"globalStyle": {
diff --git a/miniprogram/pages/pl/driver-plan/detail.vue b/miniprogram/pages/pl/driver-plan/detail.vue
new file mode 100644
index 000000000..313ecec27
--- /dev/null
+++ b/miniprogram/pages/pl/driver-plan/detail.vue
@@ -0,0 +1,324 @@
+
+
+
+
+ ←
+ 司机回执
+
+
+
+
+ 加载中...
+ {{ errorMessage }}
+ 运输计划已作废
+ 运输计划已归档完成
+
+
+
+ 计划编号
+ {{ page.planNo || '—' }}
+
+
+ 计划日期
+ {{ page.planDate || '—' }}
+
+
+ 车牌
+ {{ page.licensePlate || '—' }}
+
+
+ 司机
+ {{ page.driverName || '—' }} {{ page.driverPhone || '' }}
+
+
+
+
+ {{ stop.label }} · {{ stop.customerName || '—'
+ }}({{ stop.businessType }})
+
+
+ 地址
+
+ {{ stop.address || '—' }}
+ 导航
+
+
+
+ 联系方式
+ {{ stop.contact || '—' }}
+
+
+ 注意事项
+ {{ stop.notes || '—' }}
+
+
+
+ {{ idx + 1 }}/{{ (stop.products || []).length }} {{ item.productName || '产品' }}
+ 计划:{{ formatPlanQty(item) }}
+ 规格型号:{{ item.model || '—' }}
+ 包装规格:{{ item.packageSpec || '—' }}
+
+ 实际数量
+
+
+ {{ item.unit || '' }}
+
+
+
+
+ 附件上传
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/pages/pl/driver-plan/list.vue b/miniprogram/pages/pl/driver-plan/list.vue
new file mode 100644
index 000000000..8b313c447
--- /dev/null
+++ b/miniprogram/pages/pl/driver-plan/list.vue
@@ -0,0 +1,177 @@
+
+
+
+
+ ←
+ 司机运输计划单
+
+
+
+
+
+
+
+
+
+ 加载中...
+ 暂无运输记录
+
+ {{ index + 1 }}
+
+ {{ cardTitle(row) }}
+ {{ row.statusLabel || '进行中' }}
+
+
+ 车牌:{{ row.licensePlate || '—' }}
+
+
+
+ {{ stop.label }} · {{ stop.customerName || '—' }}
+ 时间:{{ formatDay(stop.stopTime) }}
+ 地址:{{ stop.address || '—' }}
+
+ 产品:—
+
+
+ 产品:{{ prod.productName || '—' }}
+ 规格型号:{{ prod.model || '—' }}
+ 包装规格:{{ prod.packageSpec || '—' }}
+ 数量:{{ formatQty(prod.quantity) }} · 单位:{{ prod.unit || '—' }}
+
+
+
+ 运输计划号:{{ row.planNo || '—' }}
+
+
+
+
+
+
+
+
diff --git a/miniprogram/styles/pl-driver-plan.scss b/miniprogram/styles/pl-driver-plan.scss
new file mode 100644
index 000000000..fe5872e28
--- /dev/null
+++ b/miniprogram/styles/pl-driver-plan.scss
@@ -0,0 +1,316 @@
+/* 司机运输计划单 · 运输记录/回执正文(三墙绿;顶栏样式见 bx-theme) */
+.pl-driver-plan-page {
+ --pl-driver-primary: #059669;
+ --pl-driver-brand: #0a9f6d;
+ padding-bottom: 24px;
+ background: #f8fafc;
+ min-height: 100vh;
+ box-sizing: border-box;
+}
+
+.pl-driver-plan-page .filters {
+ margin: 12px 16px;
+ padding: 4px 4px 10px;
+ border-radius: 12px;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ box-sizing: border-box;
+}
+
+.pl-driver-plan-page .search-input {
+ display: block;
+ width: calc(100% - 8px);
+ height: 36px;
+ margin: 8px 4px 0;
+ padding: 0 12px;
+ border: 1px solid #cbd5e1;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #0f172a;
+ background: #fff;
+ box-sizing: border-box;
+}
+
+.pl-driver-plan-page .list-body {
+ padding: 0 16px 24px;
+}
+
+.pl-driver-plan-page .record-card {
+ position: relative;
+ margin-bottom: 10px;
+ padding: 16px 14px 14px;
+ border-radius: 12px;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+ overflow: hidden;
+}
+
+.pl-driver-plan-page .index-tag {
+ position: absolute;
+ top: 0;
+ left: 0;
+ min-width: 24px;
+ height: 22px;
+ padding: 0 8px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px 0 10px 0;
+ background: var(--pl-driver-primary);
+ color: #fff;
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1;
+}
+
+.pl-driver-plan-page .record-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ margin-bottom: 8px;
+ padding-left: 4px;
+}
+
+.pl-driver-plan-page .card-title {
+ font-size: 15px;
+ font-weight: 700;
+ color: #0f172a;
+ word-break: break-all;
+}
+
+.pl-driver-plan-page .tag {
+ flex-shrink: 0;
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 999px;
+}
+
+.pl-driver-plan-page .tag--success {
+ background: #ecfdf5;
+ color: #047857;
+}
+
+.pl-driver-plan-page .tag--danger {
+ background: #fef2f2;
+ color: #dc2626;
+}
+
+.pl-driver-plan-page .tag--muted {
+ background: #f1f5f9;
+ color: #64748b;
+}
+
+.pl-driver-plan-page .meta {
+ font-size: 13px;
+ color: #475569;
+ line-height: 1.5;
+ margin-top: 2px;
+}
+
+.pl-driver-plan-page .plate-hl {
+ color: var(--pl-driver-primary);
+ font-weight: 700;
+}
+
+.pl-driver-plan-page .stop-block {
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px dashed #e2e8f0;
+}
+
+.pl-driver-plan-page .stop-block__title {
+ font-size: 13px;
+ font-weight: 700;
+ color: #0f172a;
+ margin-bottom: 4px;
+}
+
+.pl-driver-plan-page .page-loading,
+.pl-driver-plan-page .empty-hint {
+ text-align: center;
+ color: #94a3b8;
+ padding: 48px 16px;
+ font-size: 14px;
+}
+
+/* detail */
+.pl-driver-plan-detail {
+ padding-bottom: 100px;
+}
+
+.pl-driver-plan-detail .section-card {
+ margin: 12px 16px;
+ padding: 12px 14px;
+ border-radius: 12px;
+ background: #fff;
+ border: 1px solid #e2e8f0;
+}
+
+.pl-driver-plan-detail .kv-row {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 8px 0;
+ border-bottom: 1px solid #f1f5f9;
+ font-size: 14px;
+}
+
+.pl-driver-plan-detail .kv-row:last-child {
+ border-bottom: none;
+}
+
+.pl-driver-plan-detail .kv-k {
+ color: #64748b;
+ flex-shrink: 0;
+}
+
+.pl-driver-plan-detail .kv-v {
+ color: #0f172a;
+ text-align: right;
+ word-break: break-all;
+}
+
+.pl-driver-plan-detail .stop-title {
+ font-size: 15px;
+ font-weight: 700;
+ color: #0f172a;
+ margin-bottom: 8px;
+}
+
+.pl-driver-plan-detail .addr-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.pl-driver-plan-detail .nav-btn {
+ flex-shrink: 0;
+ font-size: 12px;
+ color: #fff;
+ background: var(--pl-driver-primary);
+ border-radius: 999px;
+ padding: 4px 10px;
+}
+
+.pl-driver-plan-detail .product-card {
+ margin-top: 10px;
+ padding: 10px;
+ border-radius: 10px;
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+}
+
+.pl-driver-plan-detail .product-name {
+ font-weight: 700;
+ color: #0f172a;
+ margin-bottom: 6px;
+}
+
+.pl-driver-plan-detail .actual-box {
+ margin-top: 12px;
+ padding: 10px;
+ border-radius: 10px;
+ background: #ecfdf5;
+}
+
+.pl-driver-plan-detail .actual-label {
+ font-size: 13px;
+ font-weight: 700;
+ color: #047857;
+ margin-bottom: 6px;
+}
+
+.pl-driver-plan-detail .actual-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.pl-driver-plan-detail .actual-input {
+ flex: 1;
+ height: 40px;
+ padding: 0 12px;
+ border: 1px solid #a7f3d0;
+ border-radius: 8px;
+ background: #fff;
+ font-size: 16px;
+}
+
+.pl-driver-plan-detail .remark-input {
+ width: 100%;
+ min-height: 64px;
+ margin-top: 10px;
+ padding: 10px 12px;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ font-size: 14px;
+ box-sizing: border-box;
+}
+
+.pl-driver-plan-detail .attach-label {
+ margin-top: 12px;
+ font-size: 13px;
+ color: #64748b;
+}
+
+.pl-driver-plan-detail .attach-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 8px;
+}
+
+.pl-driver-plan-detail .attach-thumb {
+ width: 72px;
+ height: 72px;
+ border-radius: 8px;
+ background: #f1f5f9;
+}
+
+.pl-driver-plan-detail .attach-add {
+ width: 72px;
+ height: 72px;
+ border-radius: 8px;
+ border: 1px dashed #a7f3d0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--pl-driver-primary);
+ font-size: 28px;
+}
+
+.pl-driver-plan-detail .page-footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
+ background: #fff;
+ border-top: 1px solid #e2e8f0;
+ display: flex;
+ gap: 10px;
+ z-index: 20;
+}
+
+.pl-driver-plan-detail .footer-btn {
+ flex: 1;
+ height: 44px;
+ border-radius: 999px;
+ font-size: 15px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.pl-driver-plan-detail .footer-btn--ghost {
+ background: #ecfdf5;
+ color: #047857;
+}
+
+.pl-driver-plan-detail .footer-btn--primary {
+ background: var(--pl-driver-primary);
+ color: #fff;
+}