feat(web): 同步 web 端目录更新至 Gitea

包含加氢站站点信息、运维交车/故障、台账与数据分析等页面新增与改动。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-06-04 19:57:30 +08:00
parent d29e2a821b
commit d432d51eed
35 changed files with 26963 additions and 1463 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
"""生成「租赁账单」用户说明书 Word输出到用户桌面。"""
from pathlib import Path
from docx import Document
from docx.shared import Pt, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
def set_cell_shading(cell, fill_hex):
shading = OxmlElement("w:shd")
shading.set(qn("w:fill"), fill_hex)
cell._tc.get_or_add_tcPr().append(shading)
def add_heading_cn(doc, text, level):
h = doc.add_heading(text, level=level)
for r in h.runs:
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
return h
def add_para_cn(doc, text, bold=False):
p = doc.add_paragraph()
run = p.add_run(text)
run.font.size = Pt(11)
run.font.name = "PingFang SC"
run._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
run.bold = bold
return p
def add_bullet(doc, text):
p = doc.add_paragraph(style="List Bullet")
r = p.add_run(text)
r.font.size = Pt(11)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
def build(out_path: Path):
doc = Document()
sect = doc.sections[0]
sect.left_margin = Cm(2.2)
sect.right_margin = Cm(2.2)
t = doc.add_paragraph()
t.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = t.add_run("数字化资产 ONEOS 运管平台\n租赁账单 · 功能说明(用户操作)")
r.bold = True
r.font.size = Pt(18)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
add_para_cn(
doc,
"本文档依据原型页面「租赁账单.jsx」「租赁账单-查看.jsx」「租赁账单-收费明细.jsx」整理"
"说明从列表进入查看、再进入收费明细的常规操作与字段含义。正式系统以线上版本为准。",
)
add_heading_cn(doc, "一、模块概述", 1)
add_para_cn(
doc,
"「租赁账单」用于按租赁合同聚合展示多期账单,支持筛选、展开查看每期账单明细与金额,"
"在账单未提交前维护收费明细及列表中的部分成本字段;提交后收费明细不可再改。",
)
add_heading_cn(doc, "二、如何进入", 1)
add_bullet(doc, "登录系统后,左侧菜单选择「业务管理」→「租赁账单」。")
add_bullet(doc, "列表页面包屑为:业务管理 / 租赁账单。右上角可点「查看需求说明」打开产品需求全文。")
add_heading_cn(doc, "三、列表页操作", 1)
add_heading_cn(doc, "3.1 筛选区", 2)
add_para_cn(doc, "第一行默认展示:合同编码、项目名称、客户名称(均为可搜索下拉,支持输入关键字匹配)。")
add_para_cn(doc, "点击「展开」后增加:业务部门、业务负责人(下拉选择)、交车任务编码(文本框,模糊匹配)。")
add_bullet(doc, "重置:清空所有筛选条件。")
add_bullet(doc, "查询:按条件刷新列表(原型中与筛选联动)。")
add_heading_cn(doc, "3.2 主表(合同维度)", 2)
add_para_cn(
doc,
"每行代表一份租赁合同。点击行首「+」展开子表,查看该合同下各期账单。字段包括:"
"合同编码、合同类型(正式/试用)、项目名称、客户名称、合同生效日期、交车任务编码、业务部门、业务负责人。",
)
add_para_cn(doc, "表格底部分页:可切换页码、每页条数,并显示总条数。")
add_heading_cn(doc, "3.3 子表(账单期维度)", 2)
add_para_cn(doc, "子表列说明(与原型一致):")
tbl = doc.add_table(rows=13, cols=2)
tbl.style = "Table Grid"
hdr = ["字段", "说明"]
for j, h in enumerate(hdr):
tbl.rows[0].cells[j].text = h
set_cell_shading(tbl.rows[0].cells[j], "E6F4EA")
rows = [
("账单编号", "合同编码 + ZD + 四位期号。例HT-ZL-2025-001 第1期 → …ZD0001。用于后期与用友 YS 等对账取票。"),
("账单期数", "该合同下第几期账单。"),
("状态", "已提交 / 待提交等。需求中还规划「已结清」(财务到账等于实收后)。"),
("账单开始/结束日期", "本期计费区间,格式 YYYY-MM-DD。"),
("提车数量", "显示为「N辆」链接点击弹出车辆列表品牌、型号、车牌号。"),
("应收款总额", "系统计算的应收金额(需求:各车月租金总和 + 各车服务费总和)。"),
("实收款总额", "需求:月租金+服务费合计减去减免总金额。"),
("减免总金额", "各减免项合计。"),
("车辆成本", "按车型日成本 × 账单天数 × 车辆数汇总展示(只读计算)。"),
("氢费成本", "点击金额可改为输入框,两位小数,失焦保存(列表内快速维护)。"),
("其他成本", "同上,可点击编辑。"),
("操作", "见下文。"),
]
for i, (a, b) in enumerate(rows, start=1):
tbl.rows[i].cells[0].text = a
tbl.rows[i].cells[1].text = b
add_heading_cn(doc, "3.4 列表行操作:查看 / 收费明细", 2)
add_bullet(doc, "查看:任意状态均可进入「租赁账单 - 查看」页,浏览该期账单的只读明细与汇总。")
add_bullet(
doc,
"收费明细:仅当该期状态为「待提交」时显示;「已提交」后入口隐藏,表示收费项已确认不可再改。",
)
add_heading_cn(doc, "四、查看页(只读)", 1)
add_para_cn(doc, "面包屑:业务管理 / 租赁账单 / 查看。")
add_heading_cn(doc, "4.1 账单信息", 2)
add_para_cn(
doc,
"展示合同编码、合同类型、项目名称、客户名称、交车任务编码、账单编码、账单期数、"
"账单开始日期、账单结束日期(与列表规则一致)。",
)
add_heading_cn(doc, "4.2 账单明细汇总", 2)
add_bullet(
doc,
"应收款总额(可点击):展开包含「应收月租金合计、应收保证金合计、应收服务费合计」,"
"首期账单另含「氢费预付款应收」。",
)
add_bullet(
doc,
"实收款总额(可点击):包含实收租金、保证金、实收服务费、租金减免、服务费减免等;"
"首期另含氢费预收与氢费减免。",
)
add_bullet(doc, "开票总额:与实收口径一致但不含「应收车辆保证金」,用于开票参考。")
add_heading_cn(doc, "4.3 车辆明细表", 2)
add_para_cn(
doc,
"每车一行:序号、品牌、型号、车牌、应收/实收月租金、租金备注、减免金额及备注、减免证明(可点预览)、"
"应收保证金(首期按合同,非首期为 0、服务费项目点「管理」弹出各服务项目应收/实收/减免/备注)、"
"应收服务费与实收服务费合计。",
)
add_heading_cn(doc, "4.4 氢费预付款(仅首期)", 2)
add_para_cn(doc, "首期账单在表格下方展示氢费预付款应收、实收、减免金额及备注;第二期起整块不显示。")
add_heading_cn(doc, "4.5 返回", 2)
add_para_cn(doc, "点击「返回」回到租赁账单列表。")
add_heading_cn(doc, "五、收费明细页(编辑提交)", 1)
add_para_cn(doc, "面包屑:业务管理 / 租赁账单 / 收费明细。由列表子表「收费明细」进入。")
add_heading_cn(doc, "5.1 账单信息与汇总", 2)
add_para_cn(doc, "顶部「账单信息」与查看页相同。汇总区同样可点击「应收款总额」「实收款总额」查看分项;「开票金额」规则同查看页。")
add_heading_cn(doc, "5.2 可编辑内容", 2)
add_bullet(doc, "实收车辆月租金:输入框,两位小数,默认与应收一致,可按实收修改。")
add_bullet(doc, "车辆租金备注、减免金额、减免备注:文本或金额输入。")
add_bullet(doc, "减免证明:附件上传(多文件),单行展示文件名,可预览、可删除。")
add_bullet(doc, "服务费项目:点「管理」,在气泡内维护每项「实收费用」(必填 *)、「减免费用」、「备注」;实收服务费自动按实收费用汇总。")
add_bullet(doc, "氢费预付款(仅首期):实收金额、减免金额、减免备注可编辑;应收金额为合同带出只读。")
add_heading_cn(doc, "5.3 底部按钮", 2)
add_bullet(doc, "提交:弹出「请确认账单金额无误」,确认后提交成功并返回(原型逻辑)。")
add_bullet(doc, "保存:保存当前填写,不做完整校验,保存后返回。")
add_bullet(doc, "取消:提示未保存将丢失修改,确认后返回列表。")
add_heading_cn(doc, "六、操作建议流程(培训口径)", 1)
add_para_cn(doc, "1在列表用筛选定位合同 → 展开子表找到目标期数 → 看「状态」。")
add_para_cn(doc, "2先点「查看」核对应收与合同是否一致。")
add_para_cn(doc, "3若为期初「待提交」点「收费明细」补全实收、减免、服务费与附件 → 保存或提交。")
add_para_cn(doc, "4提交后仅能通过「查看」复核列表中可继续维护氢费成本、其他成本与收费明细提交状态独立以实际上线规则为准")
add_heading_cn(doc, "七、文档信息", 1)
add_para_cn(doc, "版本:与原型标注一致(租赁账单 2026-03-10查看/收费明细 2026-03-11。生成工具项目内 web端/业务管理/文档/_build_租赁账单手册.py")
out_path.parent.mkdir(parents=True, exist_ok=True)
doc.save(out_path)
print("Saved:", out_path)
if __name__ == "__main__":
desktop = Path.home() / "Desktop"
build(desktop / "租赁账单功能说明.docx")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,515 @@
// 【重要】必须使用 const Component 作为组件变量名
// 台账数据 - 车辆保险台账(保险分摊明细)
// 原型:客户名称 + 结算周期筛选、导出;日成本与分摊成本按业务公式计算(联调可替换为接口)
const Component = function () {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var antd = window.antd;
var App = antd.App;
var Breadcrumb = antd.Breadcrumb;
var Card = antd.Card;
var Button = antd.Button;
var Table = antd.Table;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Row = antd.Row;
var Col = antd.Col;
var Space = antd.Space;
var message = antd.message;
var CUSTOMER_OPTIONS = [
{ value: '浙江羚牛氢能科技有限公司', label: '浙江羚牛氢能科技有限公司' },
{ value: '杭州绿运物流有限公司', label: '杭州绿运物流有限公司' },
{ value: '宁波港城新能源车队', label: '宁波港城新能源车队' },
{ value: '绍兴氢能示范运营中心', label: '绍兴氢能示范运营中心' }
];
var PROJECT_BY_CUSTOMER = {
'浙江羚牛氢能科技有限公司': ['氢能重卡租赁一期', '园区通勤包车'],
'杭州绿运物流有限公司': ['城配氢能车辆项目', '冷链专线'],
'宁波港城新能源车队': ['港口短驳氢能车', '堆场转运'],
'绍兴氢能示范运营中心': ['示范线路运营', '加氢站接驳']
};
var PLATE_PREFIX = ['浙A', '浙B', '浙D', '浙G'];
function filterOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function numOrZero(v) {
if (v === null || v === undefined || v === '') return 0;
var n = Number(v);
return isNaN(n) ? 0 : n;
}
function fmtMoney(n, digits) {
if (n === null || n === undefined || n === '') return '-';
var x = Number(n);
if (isNaN(x)) return '-';
var d = digits === undefined ? 2 : digits;
return x.toLocaleString('zh-CN', { minimumFractionDigits: d, maximumFractionDigits: d });
}
function escapeCsv(v) {
var s = v == null ? '' : String(v);
if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1 || s.indexOf('\r') !== -1) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
function downloadCsv(filename, lines) {
var csv = lines.map(function (row) { return row.map(escapeCsv).join(','); }).join('\n');
var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function initialSettlementMonth() {
try {
if (window.dayjs) return window.dayjs('2026-05-01');
} catch (e1) {}
return null;
}
function settlementYm(d) {
if (!d || !window.dayjs) return '';
try {
return window.dayjs(d).format('YYYY-MM');
} catch (e2) {
return '';
}
}
/** 日成本 = 缴费金额 / 生效天数 */
function dailyCost(payment, effectiveDays) {
var pay = numOrZero(payment);
var days = numOrZero(effectiveDays);
if (days <= 0) return null;
return Math.round((pay / days) * 10000) / 10000;
}
/** 保险分摊成本 = Σ(各险日成本 × 分摊天数) */
function calcApportionCost(row) {
var d = numOrZero(row.apportionDays);
if (d <= 0) return null;
var sum = 0;
var has = false;
['compulsoryDaily', 'commercialDaily', 'excessDaily', 'cargoDaily'].forEach(function (k) {
var v = row[k];
if (v !== null && v !== undefined && !isNaN(Number(v))) {
sum += Number(v) * d;
has = true;
}
});
if (!has) return null;
return Math.round(sum * 100) / 100;
}
function buildMockRows(ym) {
var rows = [];
var seed = 0;
CUSTOMER_OPTIONS.forEach(function (cust, ci) {
var projects = PROJECT_BY_CUSTOMER[cust.value] || ['默认项目'];
projects.forEach(function (proj, pi) {
seed += 1;
var plate = PLATE_PREFIX[ci % PLATE_PREFIX.length] + String(10000 + seed).slice(-5) + 'F';
var effDays = 365;
var compulsoryPay = 950 + seed * 3;
var commercialPay = 4200 + seed * 17;
var excessPay = seed % 3 === 0 ? 1800 + seed * 5 : 0;
var cargoPay = seed % 2 === 0 ? 600 + seed * 2 : 0;
var apportionDays = 18 + ((seed + (ym ? ym.length : 0)) % 13);
var row = {
key: 'r' + seed,
seq: seed,
settlementCycle: ym || '2026-05',
customerName: cust.value,
projectName: proj,
plateNo: plate,
compulsoryDaily: dailyCost(compulsoryPay, effDays),
commercialDaily: dailyCost(commercialPay, effDays),
excessDaily: excessPay > 0 ? dailyCost(excessPay, effDays) : null,
cargoDaily: cargoPay > 0 ? dailyCost(cargoPay, effDays) : null,
apportionDays: apportionDays
};
row.apportionCost = calcApportionCost(row);
rows.push(row);
});
});
return rows;
}
var layoutStyle = {
padding: '16px 24px 24px',
minHeight: '100vh',
background: 'linear-gradient(165deg, #eef4ff 0%, #f5f7fa 42%, #f0f2f5 100%)'
};
var filterLabelStyle = { marginBottom: 6, fontSize: 13, color: 'rgba(0,0,0,0.55)', fontWeight: 500 };
var filterItemStyle = { marginBottom: 12 };
var filterControlStyle = { width: '100%' };
var filterActionsColStyle = { flex: '0 0 auto', marginLeft: 'auto' };
var filterCardStyle = {
marginBottom: 20,
borderRadius: 16,
boxShadow: '0 4px 20px -4px rgba(16,24,40,0.03), 0 0 0 1px rgba(16,24,40,0.06)',
border: 'none',
background: '#ffffff'
};
var tableCardStyle = {
borderRadius: 16,
boxShadow: '0 10px 32px -4px rgba(16,24,40,0.06), 0 0 0 1px rgba(16,24,40,0.04)',
border: 'none',
background: '#ffffff',
overflow: 'hidden'
};
var ledgerTableStyle =
'.ins-ledger-table-wrap{border-radius:12px;overflow:hidden;box-shadow:0 4px 24px -6px rgba(15,23,42,0.05),0 0 0 1px rgba(22,119,255,0.1)}' +
'.ins-ledger-table .ant-table-thead>tr>th{white-space:nowrap;color:#1e293b!important;font-weight:600!important;font-size:13px!important;' +
'background:#e8f4fc!important;border-bottom:1px solid #bae6fd!important;border-inline-end:1px solid #dbeafe!important;padding:0 8px!important;height:38px!important}' +
'.ins-ledger-table .ant-table-tbody>tr:not(.ant-table-measure-row)>td{white-space:nowrap;font-variant-numeric:tabular-nums;color:#334155;border-bottom:1px solid #f1f5f9!important;border-inline-end:1px solid #f8fafc!important;padding:0 8px!important;height:38px!important}' +
'.ins-ledger-table .ant-table-tbody>tr.ins-row-data:hover>td{background:#f0f9ff!important}' +
'.ins-ledger-table .ant-table-summary>tr>td{font-weight:700;background:#f8fafc!important;color:#0f172a!important;border-top:2px solid #cbd5e1!important;padding:0 8px!important;height:38px!important}';
var customerDraftState = useState(undefined);
var customerDraft = customerDraftState[0];
var setCustomerDraft = customerDraftState[1];
var monthDraftState = useState(initialSettlementMonth);
var monthDraft = monthDraftState[0];
var setMonthDraft = monthDraftState[1];
var customerAppliedState = useState(undefined);
var customerApplied = customerAppliedState[0];
var setCustomerApplied = customerAppliedState[1];
var monthAppliedState = useState(initialSettlementMonth);
var monthApplied = monthAppliedState[0];
var setMonthApplied = monthAppliedState[1];
var appliedYm = useMemo(function () {
return settlementYm(monthApplied) || '2026-05';
}, [monthApplied]);
var allRows = useMemo(function () {
return buildMockRows(appliedYm);
}, [appliedYm]);
var dataSource = useMemo(function () {
var list = allRows.filter(function (r) {
if (customerApplied && r.customerName !== customerApplied) return false;
return true;
});
return list.map(function (r, idx) {
return Object.assign({}, r, { seq: idx + 1 });
});
}, [allRows, customerApplied]);
var totalApportionCost = useMemo(function () {
return dataSource.reduce(function (acc, r) {
return acc + numOrZero(r.apportionCost);
}, 0);
}, [dataSource]);
var customerDisplayLabel = customerApplied || '默认全量数据';
var cycleDisplayLabel = appliedYm || '-';
var handleQuery = useCallback(function () {
setCustomerApplied(customerDraft);
setMonthApplied(monthDraft);
message.success('查询成功');
}, [customerDraft, monthDraft]);
var handleReset = useCallback(function () {
setCustomerDraft(undefined);
setMonthDraft(initialSettlementMonth());
setCustomerApplied(undefined);
setMonthApplied(initialSettlementMonth());
}, []);
var handleExport = useCallback(function () {
var headers = [
'序号',
'结算周期',
'客户名称',
'项目名称',
'车牌号',
'交强险日成本',
'商业险日成本',
'超赔险日成本',
'货物险日成本',
'分摊天数',
'保险分摊成本'
];
var body = dataSource.map(function (r) {
return [
r.seq,
r.settlementCycle,
r.customerName,
r.projectName,
r.plateNo,
r.compulsoryDaily,
r.commercialDaily,
r.excessDaily,
r.cargoDaily,
r.apportionDays,
r.apportionCost
];
});
body.push(['合计', '', '', '', '', '', '', '', '', '', totalApportionCost]);
downloadCsv('车辆保险台账_' + cycleDisplayLabel + '_' + new Date().getTime() + '.csv', [headers].concat(body));
message.success('已导出 CSV');
}, [dataSource, cycleDisplayLabel, totalApportionCost]);
var columns = useMemo(function () {
return [
{
title: '序号',
dataIndex: 'seq',
key: 'seq',
width: 64,
align: 'center',
fixed: 'left'
},
{
title: '结算周期',
dataIndex: 'settlementCycle',
key: 'settlementCycle',
width: 100,
align: 'center'
},
{
title: '客户名称',
dataIndex: 'customerName',
key: 'customerName',
width: 200,
align: 'center',
ellipsis: true
},
{
title: '项目名称',
dataIndex: 'projectName',
key: 'projectName',
width: 160,
align: 'center',
ellipsis: true
},
{
title: '车牌号',
dataIndex: 'plateNo',
key: 'plateNo',
width: 110,
align: 'center'
},
{
title: '交强险日成本',
dataIndex: 'compulsoryDaily',
key: 'compulsoryDaily',
width: 130,
align: 'right',
render: function (v) { return fmtMoney(v, 4); }
},
{
title: '商业险日成本',
dataIndex: 'commercialDaily',
key: 'commercialDaily',
width: 130,
align: 'right',
render: function (v) { return fmtMoney(v, 4); }
},
{
title: '超赔险日成本',
dataIndex: 'excessDaily',
key: 'excessDaily',
width: 130,
align: 'right',
render: function (v) { return fmtMoney(v, 4); }
},
{
title: '货物险日成本',
dataIndex: 'cargoDaily',
key: 'cargoDaily',
width: 130,
align: 'right',
render: function (v) { return fmtMoney(v, 4); }
},
{
title: '分摊天数',
dataIndex: 'apportionDays',
key: 'apportionDays',
width: 120,
align: 'center'
},
{
title: '保险分摊成本',
dataIndex: 'apportionCost',
key: 'apportionCost',
width: 140,
align: 'right',
fixed: 'right',
render: function (v) { return fmtMoney(v, 2); }
}
];
}, []);
var tableSummary = useCallback(function () {
return React.createElement(
Table.Summary,
null,
React.createElement(
Table.Summary.Row,
null,
React.createElement(Table.Summary.Cell, { index: 0, align: 'center', colSpan: 1 }, '合计'),
React.createElement(Table.Summary.Cell, { index: 1, colSpan: 9 }),
React.createElement(Table.Summary.Cell, { index: 10, align: 'right' }, fmtMoney(totalApportionCost, 2))
)
);
}, [totalApportionCost]);
return React.createElement(
App,
null,
React.createElement('style', null, ledgerTableStyle),
React.createElement(
'div',
{ style: layoutStyle },
React.createElement(Breadcrumb, {
style: { marginBottom: 12 },
items: [{ title: '台账数据' }, { title: '保险分摊明细' }]
}),
React.createElement(
Card,
{ style: filterCardStyle, bodyStyle: { paddingBottom: 4 } },
React.createElement(
Row,
{ gutter: [16, 16], align: 'bottom' },
React.createElement(
Col,
{ xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement(
'div',
{ style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '客户名称'),
React.createElement(Select, {
allowClear: true,
showSearch: true,
placeholder: '默认全量数据',
style: filterControlStyle,
value: customerDraft,
onChange: function (v) { setCustomerDraft(v); },
options: CUSTOMER_OPTIONS,
filterOption: filterOption
})
)
),
React.createElement(
Col,
{ xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement(
'div',
{ style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '结算周期'),
React.createElement(DatePicker, {
picker: 'month',
style: filterControlStyle,
placeholder: '请选择结算周期',
format: 'YYYY-MM',
value: monthDraft,
onChange: function (v) { setMonthDraft(v); }
})
)
),
React.createElement(
Col,
{ xs: 24, sm: 12, md: 8, lg: 6, style: filterActionsColStyle },
React.createElement(
'div',
{ style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '\u00a0'),
React.createElement(
Space,
{ wrap: true },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
)
)
)
)
),
React.createElement(
Card,
{ style: tableCardStyle, bodyStyle: { padding: '20px 20px 24px' } },
React.createElement(
'div',
{ style: { position: 'relative', marginBottom: 8, minHeight: 36 } },
React.createElement(
'div',
{
style: {
textAlign: 'center',
fontSize: 18,
fontWeight: 700,
color: 'rgba(15,23,42,0.92)',
letterSpacing: '0.02em',
padding: '0 88px'
}
},
'车辆保险台账'
),
React.createElement(
'div',
{ style: { position: 'absolute', right: 0, top: '50%', transform: 'translateY(-50%)' } },
React.createElement(Button, { onClick: handleExport }, '导出')
)
),
React.createElement(
'div',
{
style: {
textAlign: 'center',
marginBottom: 16,
fontSize: 13,
color: 'rgba(15,23,42,0.55)',
fontWeight: 500
}
},
'结算周期:',
cycleDisplayLabel,
'\u00A0\u00A0\u00A0\u00A0客户',
customerDisplayLabel
),
React.createElement(
'div',
{ className: 'ins-ledger-table-wrap' },
React.createElement(Table, {
className: 'ins-ledger-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: columns,
dataSource: dataSource,
pagination: false,
rowClassName: function () { return 'ins-row-data'; },
scroll: { x: 'max-content', y: 'calc(100vh - 320px)' },
sticky: true,
summary: tableSummary
})
)
)
)
);
};

View File

@@ -0,0 +1,199 @@
# 氢费(采购端)汇总报表 — 产品需求说明PRD
| 项目 | 内容 |
|------|------|
| 文档版本 | v1.0(业务版) |
| 产品模块 | 台账数据 → 氢费(采购端)汇总报表 |
| 文档类型 | 产品需求说明 |
| 适用读者 | 产品、采购、财务、运营、测试、项目 |
---
## 一、为什么做这件事
### 1.1 业务痛点
- 采购侧需按加氢站维度掌握氢费规模、应付与实付、开票与预付余额,现有数据分散在车辆氢费明细、打款、开票等模块,缺少一站汇总视图。
- 财务对账需快速识别「未付金额」「站点欠费」等风险,并下钻到账单、流水、发票凭据。
### 1.2 产品价值
| 价值点 | 说明 |
|--------|------|
| 站点汇总 | 一行一站,看清加氢量、应付、已付、未付、开票、未开票、当前余额 |
| 对账效率 | 顶部合计条随筛选即时汇总,支撑采购与财务日常核对 |
| 可追溯 | 关键金额可钻取明细,付款凭据、发票支持在线预览与下载 |
| 风险可见 | 未付/未开票金额为正、当前余额为负时突出展示 |
### 1.3 本期目标
建设 Web 端「氢费(采购端)汇总报表」,按地区、加氢站、结算方式查询,展示站点汇总列表及合计;支持多类钻取与 CSV 导出。
### 1.4 本期不做
- 报表内直接发起打款、开票(跳转或对接后续迭代)。
- 跨年度历史数据全量迁移方案(本期以 2026 年起统计口径描述为准,上线以数据准备结果为准)。
---
## 二、谁在用、用来干什么
### 2.1 用户角色
| 角色 | 典型诉求 |
|------|----------|
| **采购人员** | 按站点查看氢费与支付进度,核对当前余额、欠费站点 |
| **财务人员** | 核对应付/实付/未付/已开票,查看付款证明与发票附件 |
| **运营/主管** | 按地区、结算方式浏览整体规模与异常站点 |
### 2.2 核心使用场景
1. **日常巡检**:按地区或结算方式筛选,查看顶部合计与列表,关注未付、已欠费站点。
2. **站点下钻**:点击加氢总量、应付总金额等,打开明细弹窗核对构成。
3. **付款与发票核查**:从已付总金额、已开票金额钻取,预览付款凭据或发票。
4. **导出报送**:按当前筛选结果导出 CSV。
---
## 三、页面功能说明
### 3.1 页面组成
路径:**台账数据 → 氢费(采购端)汇总报表**
自上而下:
1. **查询条件区** — 地区、加氢站全称、结算方式
2. **汇总列表区** — 标题、导出、筛选摘要、顶部合计条、站点表格
3. **弹窗** — 需求说明、各类钻取明细、附件预览
面包屑行右上角提供 **「查看需求说明」**(本文档);汇总列表标题右侧提供 **「导出」**。
### 3.2 查询条件
| 查询项 | 业务说明 |
|--------|----------|
| 地区 | 加氢站所属省/市,支持搜索;空为全部 |
| 加氢站全称 | 从加氢站主数据选择;空为全部 |
| 结算方式 | 预付 / 月结;空为全部 |
**交互:** 修改条件后点击 **「查询」** 生效;**「重置」** 清空条件。查询成功给予简短提示。列表上方展示当前筛选摘要(地区、加氢站、结算方式)。
### 3.3 顶部合计条
表格上方独立展示(随当前列表筛选结果汇总):
| 合计项 | 计算口径 |
|--------|----------|
| 加氢总量(kg) | 列表各行加氢总量之和 |
| 应付总金额(元) | 列表各行应付总金额之和 |
| 已付总金额(元) | 列表各行已付总金额之和 |
| 未付总金额(元) | **应付总金额合计 已付总金额合计**;大于 0 时红色显示 |
| 未开票总金额(元) | **应付总金额合计 已开票总金额合计**;大于 0 时红色显示 |
| 充值总额(元) | 列表各行充值总额之和 |
| 当前总余额(元) | 列表各行当前余额之和;合计为负时红色显示 |
### 3.4 汇总列表字段
**列顺序(左→右):**
序号 → 地区 → 加氢站全称 → 结算方式 → 加氢总量(kg) → 应付总金额(元) → 已付总金额(元) → 未付总金额(元) → 已开票金额(元) → 未开票金额(元) → 充值总额(元) → 当前余额(元)
**表头列宽:** 支持鼠标拖动表头右侧调整列宽(最小宽度限制),便于长站名与金额列阅读。
| 字段 | 业务口径 | 交互 |
|------|----------|------|
| 结算方式 | 预付 / 月结 | 只读 |
| 加氢总量(kg) | 自 **2026 年起** 该站全部氢费加氢量之和 | 点击钻取 **加氢量明细**(仅加氢量,不含金额) |
| 应付总金额(元) | 该站「车辆氢费明细」**加氢总价(元)** 求和 | 点击钻取明细(加氢量、成本单价、成本总价) |
| 已付总金额(元) | 加氢站打款管理中 **已支付金额** 求和 | 点击钻取支付账单明细 |
| 未付总金额(元) | **应付总金额 已付总金额**(行内计算) | 只读;大于 0 红色 |
| 已开票金额(元) | 该站已开票金额合计 | 点击钻取开票记录 |
| 未开票金额(元) | **应付总金额 已开票金额**(行内计算) | 只读;大于 0 红色 |
| 充值总额(元) | 该站预充值(打款入账)金额合计 | 点击钻取充值明细 |
| 当前余额(元) | 2026 年期初余额,扣减 2025 年及以前未扣款记录后的实时余额 | 点击钻取余额变更明细;**为负** 时金额红色 + 左侧 **「已欠费」** 标签 |
### 3.5 钻取明细说明
#### 1加氢量明细由加氢总量进入
| 列 | 说明 |
|----|------|
| 序号、加氢时间、订单编号、车牌号、加氢量(kg) | 来源于车辆氢费明细,按当前加氢站筛选 |
| 合计 | 加氢量合计 |
#### 2应付总金额明细
| 列 | 说明 |
|----|------|
| 序号、加氢时间、订单编号、车牌号、加氢量(kg)、成本单价(元/kg)、成本总价(元) | 按站筛选的车辆氢费明细 |
| 合计 | 加氢量、成本总价;弹窗标题含站名 |
#### 3已付总金额明细由已付总金额进入
| 列 | 说明 |
|----|------|
| 加氢站全称、账单开始时间、账单结束时间、应付总金额(元)、已付总金额(元)、银行付款证明 | 按账单维度展示 |
| 银行付款证明 | **查看付款凭据** — 预览付款证明图片 |
| 合计 | 应付、实付合计 |
#### 4已开票明细由已开票金额进入
| 列 | 说明 |
|----|------|
| 加氢站全称、开票时间、开票金额(元)、发票 | |
| 发票 | **查看发票** — 在线预览 PDF/图片;**下载发票** — 下载 PDF/图片文件 |
| 合计 | 开票金额合计 |
#### 5充值总额明细由充值总额进入
| 列 | 说明 |
|----|------|
| 加氢站全称、支付时间、预充金额(元)、付款凭证 | 支付时间为 **YYYY-MM-DD** |
| 付款凭证 | **预览** — 在线查看;**下载** — 下载凭证文件 |
| 合计 | 预充金额(充值总额)合计 |
#### 6余额变更明细由当前余额进入
| 列 | 说明 |
|----|------|
| 加氢站全称、收入金额(元)、支出金额(元)、余额(元)、订单编号 | 收入/支出为空显示「—」;余额为负红色 |
| 合计 | 收入合计、支出合计、末行余额(与列表该行当前余额一致) |
### 3.6 附件预览
- 付款凭据、发票在弹窗中预览PDF 使用文档预览,图片直接展示。
- 发票支持下载,文件名与开票记录一致。
### 3.7 导出
- 点击 **「导出」** 下载当前筛选结果 CSV含列表字段及合计行
- 编码 UTF-8带 BOM便于 Excel 打开。
---
## 四、业务规则摘要
| 规则 | 说明 |
|------|------|
| 统计起点 | 加氢总量等业务口径默认 **2026 年起**(与车辆氢费明细、主数据生效规则一致) |
| 未付(行) | 应付总金额 已付总金额 |
| 未付(合计条) | 各站未付之和,等价于应付合计 已付合计 |
| 未开票(行) | 应付总金额 已开票金额 |
| 未开票(合计条) | 各站未开票之和,等价于应付合计 已开票合计 |
| 已欠费 | 仅 **当前余额 &lt; 0** 时展示标签 |
| 钻取范围 | 均限定为 **当前汇总行对应加氢站** |
---
## 五、验收要点(业务)
1. 查询、重置、筛选摘要、合计条与列表数据一致。
2. 各可点击金额/加氢量钻取弹窗字段与上文一致,合计正确。
3. 未付、已欠费展示规则符合第四节。
4. 付款凭据可预览;发票可预览与下载。
5. 列宽可拖动;导出字段完整。
6. 「查看需求说明」可打开本 PRD 全文。
---
**文档结束**

File diff suppressed because it is too large Load Diff

View File

@@ -3,11 +3,11 @@ var H2_LEDGER_REQUIREMENT_DOC = `# 车辆氢费明细 — 产品需求说明P
| 项目 | 内容 |
|------|------|
| 文档版本 | v2.0(业务版) |
| 文档版本 | v2.1(业务版) |
| 产品模块 | 台账数据 → 车辆氢费明细 |
| 文档类型 | 产品需求说明 |
| 适用读者 | 产品、业务、运营、测试、项目 |
| 修订说明 | 从业务与使用场景出发描述需求,不展开技术实现细节 |
| 修订说明 | 同步列表字段顺序、顶部合计条、承担方式、系统带出字段等现行页面行为 |
---
@@ -130,9 +130,9 @@ flowchart LR
| 客户名称 | 从客户库选择 |
| 加氢站名称 | 从加氢站库选择 |
| 业务员 | 按客户归属业务员筛选 |
| 结算状态 | 客户承担 / 我司承担 |
| 款状态 | 未付款 / 已付款 / 部分付款 |
| 开票公司 | 从开票主体选择 |
| 承担方式 | 客户承担 / 我司承担 / 客户自行结算 / 其他结算 |
| 客户收款状态 | 已付款 / 未付款(系统带出,用于筛选) |
| 开票公司 | 从开票主体选择(系统带出字段对应公司) |
查询成功给予简短成功提示。
@@ -154,7 +154,17 @@ flowchart LR
不同区域之间有明显分隔,已对账区背景略灰、待保存区背景略黄,降低误操作概率。
**部合计:** 对当前列表可见记录,合计 **加氢量、成本总价、加氢总价**(随筛选与「仅看异常」变化)
**部合计** 表格上方独立展示 **加氢量(kg)、成本总价(元)、加氢总价(元)** 三项合计
- **统计范围:** 与当前列表可见行一致。
- **随查询重算:** 点击「查询」应用筛选条件后,合计立即按新结果重新汇总。
- **随「仅显示异常数据」变化:** 开关开启时,仅对当前可见的异常记录(及本人待保存行)合计。
- **说明:** 合计条含「待保存 / 未对账 / 已对账」等当前列表中的全部可见记录,与导出范围(仅未对账+已对账)不同。
**列表列顺序(左→右,业务视角):**
序号 → 加氢日期 → 加氢时间 → 加氢站名称 → 客户名称 → 车牌号 → 加氢量 → 成本单价 → 成本总价 → 加氢单价 → 加氢总价 → 行驶里程 → 备注 → 业务员 → 承担方式 → 对账日期 → **收票日期** → **加氢站付款状态** → **开票日期** → **客户收款状态** → **开票公司** → 状态 → 订单编号 → 操作(操作列固定右侧)。
**系统带出字段(不可手工维护):** 收票日期、加氢站付款状态、开票日期、客户收款状态、开票公司;选择/变更客户等信息后由系统自动刷新,单元格为浅灰底只读样式。
**列表勾选:** 仅 **未对账** 记录可勾选,用于批量「完成对账」。
@@ -178,26 +188,28 @@ flowchart LR
| 信息项 | 用户是否填写 | 业务说明 |
|--------|--------------|----------|
| 状态 | 否 | 展示「未对账」「已对账」;待保存不显示标签 |
| 订单编号 | 否 | 系统按规则自动生成,不可改 |
| 加氢时间 | 是* | 精确到秒,必填 |
| 加氢日期 | 否 | 由加氢时间自动得出 |
| 加氢日期 | 否 | 由加氢时间自动得出,只读 |
| 加氢站名称 | 是* | 必选 |
| 客户名称 | 是* | 必选,位于加氢站右侧;决定业务员与系统带出信息 |
| 车牌号 | 是* | 必选,须为公司登记车辆 |
| 行驶里程 | 否 | 选填,用于业务留痕 |
| 加氢量(kg) | 是* | 必填,用于算总价 |
| 成本单价 | 是* | 必填,向站点的采购成本价 |
| 成本总价 | 否 | 加氢量 × 成本单价,自动计算 |
| 客户名称 | 是* | 必选,决定业务员与财务带出信息 |
| 加氢单价 | 是* | 必填,对客户的销售单价 |
| 加氢总价 | 否 | 加氢量 × 加氢单价,自动计算 |
| 业务员 | 否 | 选客户后自动带出 |
| 结算状态 | 是* | 客户承担 / 我司承担 |
| 开票日期 | 否 | 系统按客户等业务规则带出 |
| 对账日期 | 否 | 系统带出 |
| 付款状态 | 否 | 系统带出 |
| 开票公司 | 否 | 系统带出 |
| 行驶里程 | 否 | 选填,位于加氢总价之后 |
| 备注 | 否 | 自由文本 |
| 业务员 | 否 | 选客户后自动带出 |
| 承担方式 | 是* | 客户承担 / 我司承担 / 客户自行结算 / 其他结算 |
| 对账日期 | 否 | 完成对账等流程带出,只读 |
| 收票日期 | 否 | 由财务/收票模块自动带出,不可编辑 |
| 加氢站付款状态 | 否 | 已付款 / 未付款,由加氢站打款管理带出 |
| 开票日期 | 否 | 由开票模块自动带出 |
| 客户收款状态 | 否 | 已付款 / 未付款,由客户收款模块带出 |
| 开票公司 | 否 | 显示开票公司名称,系统自动带出 |
| 状态 | 否 | 展示「未对账」「已对账」;待保存不显示标签;列在列表末尾 |
| 订单编号 | 否 | 系统按规则自动生成,不可改;列在列表末尾 |
\\* 保存时校验,缺失则标红提示。
@@ -267,7 +279,7 @@ flowchart LR
### 8.1 表头批量改价 / 改结算
- 在 **成本单价**、**加氢单价** 列表头可批量填入统一单价,作用于当前可编辑的所有记录(待保存 + 未对账)。
- 在 **结算状态** 列表头可批量改为「客户承担」或「我司承担」
- 在 **承担方式** 列表头可批量改为四种承担方式之一
- **已对账** 记录不参与批量修改。
### 8.2 批量导入
@@ -277,11 +289,11 @@ flowchart LR
**流程:**
1. 点击「批量导入」→ 下载标准模板
2. 按模板填写(无需填开票日期、对账日期、付款状态、开票公司
2. 按模板填写(无需填对账日期及收票/开票/付款等系统带出字段
3. 上传文件 → 系统生成多条 **待保存** 记录
4. 业务核对列表 → 点击 **保存** 进入未对账
**模板包含列:** 加氢时间、加氢站名称、车牌号、行驶里程、加氢量、成本单价、客户名称、加氢单价、结算状态、备注
**模板包含列:** 加氢时间、加氢站名称、车牌号、加氢量、成本单价、客户名称、加氢单价、承担方式、备注(行驶里程可在备注前按业务需要填写,导入模板列顺序以页面下载为准)
**导入后状态:** 均为待保存,与手工新增一致,须保存后才参与对账与导出。
@@ -297,7 +309,7 @@ flowchart LR
- 弹窗文案:「请选择导出的列」;支持全选/取消全选;默认勾选全部可导出列。
- 无符合条件数据时提示:暂无未对账或已对账数据可导出。
**可导出列(业务名称):** 序号、状态、订单编号、加氢时间、加氢日期、加氢站名称、车牌号、行驶里程、加氢量、成本单价、成本总价、客户名称、加氢单价、加氢总价、业务员、结算状态、开票日期、对账日期、付款状态、开票公司、备注
**可导出列(业务名称):** 序号、状态、订单编号、加氢时间、加氢日期、加氢站名称、车牌号、加氢量、成本单价、成本总价、客户名称、加氢单价、加氢总价、行驶里程、业务员、承担方式、对账日期、备注、收票日期、加氢站付款状态、开票日期、客户收款状态、开票公司。
---
@@ -326,7 +338,7 @@ flowchart LR
### 10.1 保存
- 一次保存处理页面上**全部**待保存记录。
- 必填:加氢时间、加氢站、车牌(须为登记车辆)、客户、加氢量、成本单价、加氢单价、结算状态
- 必填:加氢时间、加氢站、车牌(须为登记车辆)、客户、加氢量、成本单价、加氢单价、承担方式
- 校验失败:仅格子标红,无「保存成功」类提示。
### 10.2 完成对账
@@ -338,8 +350,8 @@ flowchart LR
### 10.3 筛选与列表
- 查询点击后生效;重置恢复。
- 筛选后合计与所见列表一致
- 本人待保存新增行在筛选后仍可见。
- **顶部合计条** 对当前列表可见行实时汇总;修改筛选并点击「查询」后重新计算;「仅显示异常数据」开启时仅统计可见行
- 本人待保存新增行在筛选后仍可见(合计亦包含这些行,若其在列表中展示)
### 10.4 导出
@@ -375,9 +387,13 @@ flowchart LR
| 待保存 | 草稿,未进入对账流程 |
| 未对账 | 已保存,等待业务确认并完成对账 |
| 已对账 | 对账完成,业务员不可随意改动 |
| 客户承担 | 氢费由客户结算 |
| 我司承担 | 氢费由司承担 |
| 未付款 / 已付款 / 部分付款 | 系统带出的付款进度,业务只读 |
| 客户承担 | 氢费由客户承担 |
| 我司承担 | 氢费由司承担 |
| 客户自行结算 | 由客户自行与加氢站等方结算 |
| 其他结算 | 其他结算方式 |
| 加氢站付款状态 | 加氢站侧是否已付款,已付款 / 未付款 |
| 客户收款状态 | 客户侧是否已收款,已付款 / 未付款 |
| 收票日期 | 财务收票日期,系统带出 |
| 标准价 | 公司维护的、按加氢站与生效时段确定的参考单价 |
---

View File

@@ -2,11 +2,11 @@
| 项目 | 内容 |
|------|------|
| 文档版本 | v2.0(业务版) |
| 文档版本 | v2.1(业务版) |
| 产品模块 | 台账数据 → 车辆氢费明细 |
| 文档类型 | 产品需求说明 |
| 适用读者 | 产品、业务、运营、测试、项目 |
| 修订说明 | 从业务与使用场景出发描述需求,不展开技术实现细节 |
| 修订说明 | 同步列表字段顺序、顶部合计条、承担方式、系统带出字段等现行页面行为 |
---
@@ -75,14 +75,14 @@
### 3.1 端到端流程(推荐操作顺序)
```mermaid
\`\`\`mermaid
flowchart LR
A[录入/复制] --> B[待保存]
B --> C[点击保存]
C[保存/导入] --> D[未对账]
D --> E[勾选并完成对账]
E --> F[已对账]
```
\`\`\`
**说明:**
@@ -129,9 +129,9 @@ flowchart LR
| 客户名称 | 从客户库选择 |
| 加氢站名称 | 从加氢站库选择 |
| 业务员 | 按客户归属业务员筛选 |
| 结算状态 | 客户承担 / 我司承担 |
| 款状态 | 未付款 / 已付款 / 部分付款 |
| 开票公司 | 从开票主体选择 |
| 承担方式 | 客户承担 / 我司承担 / 客户自行结算 / 其他结算 |
| 客户收款状态 | 已付款 / 未付款(系统带出,用于筛选) |
| 开票公司 | 从开票主体选择(系统带出字段对应公司) |
查询成功给予简短成功提示。
@@ -153,7 +153,17 @@ flowchart LR
不同区域之间有明显分隔,已对账区背景略灰、待保存区背景略黄,降低误操作概率。
**部合计:** 对当前列表可见记录,合计 **加氢量、成本总价、加氢总价**(随筛选与「仅看异常」变化)
**部合计** 表格上方独立展示 **加氢量(kg)、成本总价(元)、加氢总价(元)** 三项合计
- **统计范围:** 与当前列表可见行一致。
- **随查询重算:** 点击「查询」应用筛选条件后,合计立即按新结果重新汇总。
- **随「仅显示异常数据」变化:** 开关开启时,仅对当前可见的异常记录(及本人待保存行)合计。
- **说明:** 合计条含「待保存 / 未对账 / 已对账」等当前列表中的全部可见记录,与导出范围(仅未对账+已对账)不同。
**列表列顺序(左→右,业务视角):**
序号 → 加氢日期 → 加氢时间 → 加氢站名称 → 客户名称 → 车牌号 → 加氢量 → 成本单价 → 成本总价 → 加氢单价 → 加氢总价 → 行驶里程 → 备注 → 业务员 → 承担方式 → 对账日期 → **收票日期****加氢站付款状态****开票日期****客户收款状态****开票公司** → 状态 → 订单编号 → 操作(操作列固定右侧)。
**系统带出字段(不可手工维护):** 收票日期、加氢站付款状态、开票日期、客户收款状态、开票公司;选择/变更客户等信息后由系统自动刷新,单元格为浅灰底只读样式。
**列表勾选:****未对账** 记录可勾选,用于批量「完成对账」。
@@ -177,28 +187,30 @@ flowchart LR
| 信息项 | 用户是否填写 | 业务说明 |
|--------|--------------|----------|
| 状态 | 否 | 展示「未对账」「已对账」;待保存不显示标签 |
| 订单编号 | 否 | 系统按规则自动生成,不可改 |
| 加氢时间 | 是* | 精确到秒,必填 |
| 加氢日期 | 否 | 由加氢时间自动得出 |
| 加氢日期 | 否 | 由加氢时间自动得出,只读 |
| 加氢站名称 | 是* | 必选 |
| 客户名称 | 是* | 必选,位于加氢站右侧;决定业务员与系统带出信息 |
| 车牌号 | 是* | 必选,须为公司登记车辆 |
| 行驶里程 | 否 | 选填,用于业务留痕 |
| 加氢量(kg) | 是* | 必填,用于算总价 |
| 成本单价 | 是* | 必填,向站点的采购成本价 |
| 成本总价 | 否 | 加氢量 × 成本单价,自动计算 |
| 客户名称 | 是* | 必选,决定业务员与财务带出信息 |
| 加氢单价 | 是* | 必填,对客户的销售单价 |
| 加氢总价 | 否 | 加氢量 × 加氢单价,自动计算 |
| 业务员 | 否 | 选客户后自动带出 |
| 结算状态 | 是* | 客户承担 / 我司承担 |
| 开票日期 | 否 | 系统按客户等业务规则带出 |
| 对账日期 | 否 | 系统带出 |
| 付款状态 | 否 | 系统带出 |
| 开票公司 | 否 | 系统带出 |
| 行驶里程 | 否 | 选填,位于加氢总价之后 |
| 备注 | 否 | 自由文本 |
| 业务员 | 否 | 选客户后自动带出 |
| 承担方式 | 是* | 客户承担 / 我司承担 / 客户自行结算 / 其他结算 |
| 对账日期 | 否 | 完成对账等流程带出,只读 |
| 收票日期 | 否 | 由财务/收票模块自动带出,不可编辑 |
| 加氢站付款状态 | 否 | 已付款 / 未付款,由加氢站打款管理带出 |
| 开票日期 | 否 | 由开票模块自动带出 |
| 客户收款状态 | 否 | 已付款 / 未付款,由客户收款模块带出 |
| 开票公司 | 否 | 显示开票公司名称,系统自动带出 |
| 状态 | 否 | 展示「未对账」「已对账」;待保存不显示标签;列在列表末尾 |
| 订单编号 | 否 | 系统按规则自动生成,不可改;列在列表末尾 |
\* 保存时校验,缺失则标红提示。
\\* 保存时校验,缺失则标红提示。
### 5.2 订单编号规则(业务口径)
@@ -266,7 +278,7 @@ flowchart LR
### 8.1 表头批量改价 / 改结算
-**成本单价**、**加氢单价** 列表头可批量填入统一单价,作用于当前可编辑的所有记录(待保存 + 未对账)。
-**结算状态** 列表头可批量改为「客户承担」或「我司承担」
-**承担方式** 列表头可批量改为四种承担方式之一
- **已对账** 记录不参与批量修改。
### 8.2 批量导入
@@ -276,11 +288,11 @@ flowchart LR
**流程:**
1. 点击「批量导入」→ 下载标准模板
2. 按模板填写(无需填开票日期、对账日期、付款状态、开票公司
2. 按模板填写(无需填对账日期及收票/开票/付款等系统带出字段
3. 上传文件 → 系统生成多条 **待保存** 记录
4. 业务核对列表 → 点击 **保存** 进入未对账
**模板包含列:** 加氢时间、加氢站名称、车牌号、行驶里程、加氢量、成本单价、客户名称、加氢单价、结算状态、备注
**模板包含列:** 加氢时间、加氢站名称、车牌号、加氢量、成本单价、客户名称、加氢单价、承担方式、备注(行驶里程可在备注前按业务需要填写,导入模板列顺序以页面下载为准)
**导入后状态:** 均为待保存,与手工新增一致,须保存后才参与对账与导出。
@@ -296,7 +308,7 @@ flowchart LR
- 弹窗文案:「请选择导出的列」;支持全选/取消全选;默认勾选全部可导出列。
- 无符合条件数据时提示:暂无未对账或已对账数据可导出。
**可导出列(业务名称):** 序号、状态、订单编号、加氢时间、加氢日期、加氢站名称、车牌号、行驶里程、加氢量、成本单价、成本总价、客户名称、加氢单价、加氢总价、业务员、结算状态、开票日期、对账日期、付款状态、开票公司、备注
**可导出列(业务名称):** 序号、状态、订单编号、加氢时间、加氢日期、加氢站名称、车牌号、加氢量、成本单价、成本总价、客户名称、加氢单价、加氢总价、行驶里程、业务员、承担方式、对账日期、备注、收票日期、加氢站付款状态、开票日期、客户收款状态、开票公司。
---
@@ -325,7 +337,7 @@ flowchart LR
### 10.1 保存
- 一次保存处理页面上**全部**待保存记录。
- 必填:加氢时间、加氢站、车牌(须为登记车辆)、客户、加氢量、成本单价、加氢单价、结算状态
- 必填:加氢时间、加氢站、车牌(须为登记车辆)、客户、加氢量、成本单价、加氢单价、承担方式
- 校验失败:仅格子标红,无「保存成功」类提示。
### 10.2 完成对账
@@ -337,8 +349,8 @@ flowchart LR
### 10.3 筛选与列表
- 查询点击后生效;重置恢复。
- 筛选后合计与所见列表一致
- 本人待保存新增行在筛选后仍可见。
- **顶部合计条** 对当前列表可见行实时汇总;修改筛选并点击「查询」后重新计算;「仅显示异常数据」开启时仅统计可见行
- 本人待保存新增行在筛选后仍可见(合计亦包含这些行,若其在列表中展示)
### 10.4 导出
@@ -374,9 +386,13 @@ flowchart LR
| 待保存 | 草稿,未进入对账流程 |
| 未对账 | 已保存,等待业务确认并完成对账 |
| 已对账 | 对账完成,业务员不可随意改动 |
| 客户承担 | 氢费由客户结算 |
| 我司承担 | 氢费由司承担 |
| 未付款 / 已付款 / 部分付款 | 系统带出的付款进度,业务只读 |
| 客户承担 | 氢费由客户承担 |
| 我司承担 | 氢费由司承担 |
| 客户自行结算 | 由客户自行与加氢站等方结算 |
| 其他结算 | 其他结算方式 |
| 加氢站付款状态 | 加氢站侧是否已付款,已付款 / 未付款 |
| 客户收款状态 | 客户侧是否已收款,已付款 / 未付款 |
| 收票日期 | 财务收票日期,系统带出 |
| 标准价 | 公司维护的、按加氢站与生效时段确定的参考单价 |
---

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,12 @@ const Component = function () {
var Typography = antd.Typography;
var message = antd.message;
var Tooltip = antd.Tooltip;
var Input = antd.Input;
var Checkbox = antd.Checkbox;
var Text = Typography.Text;
var Title = Typography.Title;
var RangePicker = DatePicker.RangePicker;
var pageBg = '#f5f7fa';
var cardRadius = 12;
@@ -94,6 +97,51 @@ const Component = function () {
var overdueReturnModalOpen = overdueReturnModalState[0];
var setOverdueReturnModalOpen = overdueReturnModalState[1];
var inspectModalState = useState(false);
var inspectModalOpen = inspectModalState[0];
var setInspectModalOpen = inspectModalState[1];
var genDeliveryPickOpenState = useState(false);
var genDeliveryPickOpen = genDeliveryPickOpenState[0];
var setGenDeliveryPickOpen = genDeliveryPickOpenState[1];
var genDeliveryConfigOpenState = useState(false);
var genDeliveryConfigOpen = genDeliveryConfigOpenState[0];
var setGenDeliveryConfigOpen = genDeliveryConfigOpenState[1];
var genDeliveryCustomerState = useState('');
var genDeliveryCustomer = genDeliveryCustomerState[0];
var setGenDeliveryCustomer = genDeliveryCustomerState[1];
var genDeliveryProjectIdState = useState(undefined);
var genDeliveryProjectId = genDeliveryProjectIdState[0];
var setGenDeliveryProjectId = genDeliveryProjectIdState[1];
var genDeliveryExpectedState = useState(null);
var genDeliveryExpected = genDeliveryExpectedState[0];
var setGenDeliveryExpected = genDeliveryExpectedState[1];
var genDeliveryBillingState = useState(null);
var genDeliveryBilling = genDeliveryBillingState[0];
var setGenDeliveryBilling = genDeliveryBillingState[1];
var genDeliverySelectedKeysState = useState([]);
var genDeliverySelectedKeys = genDeliverySelectedKeysState[0];
var setGenDeliverySelectedKeys = genDeliverySelectedKeysState[1];
var genReturnPickOpenState = useState(false);
var genReturnPickOpen = genReturnPickOpenState[0];
var setGenReturnPickOpen = genReturnPickOpenState[1];
var genReturnConfigOpenState = useState(false);
var genReturnConfigOpen = genReturnConfigOpenState[0];
var setGenReturnConfigOpen = genReturnConfigOpenState[1];
var genReturnCustomerState = useState('');
var genReturnCustomer = genReturnCustomerState[0];
var setGenReturnCustomer = genReturnCustomerState[1];
var genReturnProjectIdState = useState(undefined);
var genReturnProjectId = genReturnProjectIdState[0];
var setGenReturnProjectId = genReturnProjectIdState[1];
var genReturnDateState = useState(null);
var genReturnDate = genReturnDateState[0];
var setGenReturnDate = genReturnDateState[1];
var genReturnSelectedKeysState = useState([]);
var genReturnSelectedKeys = genReturnSelectedKeysState[0];
var setGenReturnSelectedKeys = genReturnSelectedKeysState[1];
// 业管-能源部 · 独立卡片「本日导入加氢明细条数」0 条时卡片内显示提示文案(联调接接口)
var energyH2ImportTodayState = useState(0);
var energyH2ImportTodayCount = energyH2ImportTodayState[0];
@@ -285,6 +333,26 @@ const Component = function () {
setOverdueReturnModalOpen(true);
}, []);
// 工作台-年审/等级评定:根据提供的列表要求展示示意数据
var inspectMockRows = useMemo(function () {
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
var fmtD = function (d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
};
var base = new Date();
base.setHours(12, 0, 0, 0);
var addDays = function (days) {
var x = new Date(base.getTime());
x.setDate(x.getDate() + days);
return x;
};
return [
{ id: 'wb_insp_1', plateNo: '沪A12345', vehicleType: '重型厢式货车', brand: '东风', model: 'DFH1180', nextDate: fmtD(addDays(2)), remainDays: 2 },
{ id: 'wb_insp_2', plateNo: '苏B88888', vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', nextDate: fmtD(addDays(5)), remainDays: 5 },
{ id: 'wb_insp_3', plateNo: '浙C66666', vehicleType: '轻型厢式货车', brand: '江淮', model: 'HFC1180', nextDate: fmtD(addDays(-1)), remainDays: -1 }
];
}, []);
// 待办任务表(原型:任务类型、任务名称、状态、任务时间、操作)
var dashboardTodoRows = useMemo(function () {
return [
@@ -832,13 +900,295 @@ const Component = function () {
);
}
var deliveryTaskProjectList = useMemo(function () {
return [
{
id: 'p1',
name: '嘉兴某某物流氢能运输项目',
contractCode: 'JXZL20260216YW101235A',
customerName: '嘉兴某某物流有限公司',
deliveryRegion: '浙江省 / 嘉兴市',
deliveryLocation: '浙江省嘉兴市南湖区科技大道1号',
vehicles: [
{ key: 'v1', seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车', deliveryStatus: 'submitted' },
{ key: 'v1c', seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10000', vin: 'LFV2BJCH8K3000001', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '已交车样例', deliveryStatus: 'completed' },
{ key: 'v2', seq: 2, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10002', vin: 'LFV2BJCH8K3123457', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '', deliveryStatus: 'submitted' },
{ key: 'v3', seq: 3, brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌', deliveryStatus: 'none' },
{ key: 'v4', seq: 4, brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '', deliveryStatus: 'none' },
{ key: 'v5', seq: 5, brand: '陕汽', model: '德龙氢能自卸', plateNo: '浙F20002', vin: 'SX1313GR456123456', monthRent: '13200', serviceFee: '880', deposit: '32000', remark: '固定线路', deliveryStatus: 'none' }
]
},
{
id: 'p2',
name: '上海某某运输氢能租赁项目',
contractCode: 'SHZL20260201YW200123A',
customerName: '上海某某运输公司',
deliveryRegion: '上海市 / 上海市',
deliveryLocation: '上海市浦东新区张江高科技园区',
vehicles: [
{ key: 'v7', seq: 2, brand: '宇通', model: '氢能公交 ZK6126', plateNo: '沪B40001', vin: 'LZYTAGCF8K4567890', monthRent: '22000', serviceFee: '1200', deposit: '50000', remark: '示范线路', deliveryStatus: 'none' },
{ key: 'v8', seq: 3, brand: '福田', model: '欧辉氢能大巴', plateNo: '', vin: 'LZYTBACR2M2345678', monthRent: '19800', serviceFee: '1100', deposit: '45000', remark: '', deliveryStatus: 'none' }
]
},
{
id: 'p3',
name: '杭州某某租赁氢能项目',
contractCode: 'HZZL20260115YW100089A',
customerName: '杭州某某租赁有限公司',
deliveryRegion: '浙江省 / 杭州市',
deliveryLocation: '浙江省杭州市余杭区未来科技城',
vehicles: [
{ key: 'v11', seq: 3, brand: '东风', model: '氢燃料电池厢货', plateNo: '浙A50001', vin: 'LFV2BJCH8K5678901', monthRent: '9200', serviceFee: '520', deposit: '22000', remark: '城配', deliveryStatus: 'none' },
{ key: 'v12', seq: 4, brand: '开沃', model: '创源氢能轻卡', plateNo: '浙A50002', vin: 'LJXTBACR9N6789012', monthRent: '8800', serviceFee: '480', deposit: '21000', remark: '', deliveryStatus: 'none' }
]
}
];
}, []);
function filterProjectsByCustomer(customerName) {
var kw = String(customerName || '').trim().toLowerCase();
if (!kw) return deliveryTaskProjectList;
return deliveryTaskProjectList.filter(function (p) {
return (p.customerName || '').toLowerCase().indexOf(kw) !== -1;
});
}
var genDeliveryCustomerOptions = useMemo(function () {
var seen = {};
var options = [];
deliveryTaskProjectList.forEach(function (p) {
var name = p.customerName;
if (name && !seen[name]) {
seen[name] = true;
options.push({ value: name, label: name });
}
});
return options;
}, [deliveryTaskProjectList]);
var genDeliveryProjectOptions = useMemo(function () {
if (!genDeliveryCustomer) return [];
return deliveryTaskProjectList.filter(function (p) {
return p.customerName === genDeliveryCustomer;
}).map(function (p) {
return { value: p.id, label: p.name };
});
}, [genDeliveryCustomer, deliveryTaskProjectList]);
var genReturnProjectOptions = useMemo(function () {
return filterProjectsByCustomer(genReturnCustomer).map(function (p) {
return { value: p.id, label: p.name };
});
}, [genReturnCustomer, deliveryTaskProjectList]);
var selectedGenDeliveryProject = useMemo(function () {
return deliveryTaskProjectList.find(function (p) { return p.id === genDeliveryProjectId; }) || null;
}, [genDeliveryProjectId, deliveryTaskProjectList]);
var selectedGenReturnProject = useMemo(function () {
return deliveryTaskProjectList.find(function (p) { return p.id === genReturnProjectId; }) || null;
}, [genReturnProjectId, deliveryTaskProjectList]);
var genDeliveryVehicleList = useMemo(function () {
if (!selectedGenDeliveryProject) return [];
return (selectedGenDeliveryProject.vehicles || []).filter(function (v) {
return v.deliveryStatus !== 'completed';
});
}, [selectedGenDeliveryProject]);
var genDeliverySelectableVehicles = useMemo(function () {
return genDeliveryVehicleList.filter(function (v) {
return v.deliveryStatus === 'none';
});
}, [genDeliveryVehicleList]);
var genReturnVehicleList = useMemo(function () {
if (!selectedGenReturnProject) return [];
return (selectedGenReturnProject.vehicles || []).filter(function (v) {
return v.plateNo && v.deliveryStatus === 'completed';
});
}, [selectedGenReturnProject]);
var genReturnSelectableVehicles = genReturnVehicleList;
var genDeliveryRowSelection = useMemo(function () {
return {
selectedRowKeys: genDeliverySelectedKeys,
onChange: function (keys) { setGenDeliverySelectedKeys(keys); },
getCheckboxProps: function (record) {
var disabled = record.deliveryStatus !== 'none';
return {
disabled: disabled,
title: disabled ? (record.deliveryStatus === 'submitted' ? '该车辆已创建交车任务' : '该车辆已完成交车') : undefined
};
},
selections: [
{
key: 'all',
text: '全选可选车辆',
onSelect: function () {
setGenDeliverySelectedKeys(genDeliverySelectableVehicles.map(function (v) { return v.key; }));
}
},
{ key: 'none', text: '清空选择', onSelect: function () { setGenDeliverySelectedKeys([]); } }
]
};
}, [genDeliverySelectedKeys, genDeliverySelectableVehicles]);
var genReturnRowSelection = useMemo(function () {
return {
selectedRowKeys: genReturnSelectedKeys,
onChange: function (keys) { setGenReturnSelectedKeys(keys); },
selections: [
{
key: 'all',
text: '全选',
onSelect: function () {
setGenReturnSelectedKeys(genReturnSelectableVehicles.map(function (v) { return v.key; }));
}
},
{ key: 'none', text: '清空选择', onSelect: function () { setGenReturnSelectedKeys([]); } }
]
};
}, [genReturnSelectedKeys, genReturnSelectableVehicles]);
function renderGenTaskLabel(text, required) {
return React.createElement('label', { className: 'workbench-gen-task-form-label' },
required ? React.createElement('span', { className: 'req' }, '*') : null,
text
);
}
function resetGenDeliveryFlow() {
setGenDeliveryCustomer('');
setGenDeliveryProjectId(undefined);
setGenDeliveryExpected(null);
setGenDeliveryBilling(null);
setGenDeliverySelectedKeys([]);
}
function resetGenReturnFlow() {
setGenReturnCustomer('');
setGenReturnProjectId(undefined);
setGenReturnDate(null);
setGenReturnSelectedKeys([]);
}
function openGenDeliveryPick() {
resetGenDeliveryFlow();
setGenDeliveryPickOpen(true);
}
function openGenReturnPick() {
resetGenReturnFlow();
setGenReturnPickOpen(true);
}
function handleGenDeliveryNext() {
if (!genDeliveryCustomer) {
message.warning('请选择客户名称');
return;
}
if (!genDeliveryProjectId) {
message.warning('请选择项目名称');
return;
}
setGenDeliveryExpected(null);
setGenDeliveryBilling(null);
setGenDeliverySelectedKeys([]);
setGenDeliveryPickOpen(false);
setGenDeliveryConfigOpen(true);
}
function handleGenDeliveryConfirm() {
if (!genDeliveryExpected || !genDeliveryExpected.length) {
message.warning('请选择预计交车日期');
return;
}
if (!genDeliveryBilling) {
message.warning('请选择开始计费日期');
return;
}
if (!genDeliverySelectedKeys.length) {
message.warning('请至少勾选一辆车辆');
return;
}
setGenDeliveryConfigOpen(false);
resetGenDeliveryFlow();
message.success('已生成交车任务(原型)');
}
function handleGenReturnNext() {
if (!genReturnCustomer.trim()) {
message.warning('请输入客户名称');
return;
}
if (!genReturnProjectId) {
message.warning('请选择项目名称');
return;
}
setGenReturnDate(null);
setGenReturnSelectedKeys([]);
setGenReturnPickOpen(false);
setGenReturnConfigOpen(true);
}
function handleGenReturnConfirm() {
if (!genReturnDate) {
message.warning('请选择预计还车日期');
return;
}
if (!genReturnSelectedKeys.length) {
message.warning('请至少勾选一辆车辆');
return;
}
setGenReturnConfigOpen(false);
resetGenReturnFlow();
message.success('已生成还车任务(原型)');
}
function handleQuickItemClick(it) {
if (it.action === 'genDelivery') {
openGenDeliveryPick();
return;
}
if (it.action === 'genReturn') {
openGenReturnPick();
return;
}
if (it.p) protoNav(it.p);
}
var genDeliveryVehicleColumns = useMemo(function () {
return [
{ title: '序号', dataIndex: 'seq', key: 'seq', width: 56 },
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 88 },
{ title: '型号', dataIndex: 'model', key: 'model', width: 140, ellipsis: true },
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100, render: function (v) { return v || '—'; } },
{ title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 168, ellipsis: true },
{ title: '月租金', dataIndex: 'monthRent', key: 'monthRent', width: 88, render: function (v) { return v ? v + ' 元' : '—'; } },
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 100, ellipsis: true, render: function (v) { return v || '—'; } }
];
}, []);
var genReturnVehicleColumns = useMemo(function () {
return [
{ title: '序号', dataIndex: 'seq', key: 'seq', width: 56 },
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 88 },
{ title: '型号', dataIndex: 'model', key: 'model', width: 140, ellipsis: true },
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 100 },
{ title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 168, ellipsis: true }
];
}, []);
var quickByRole = useMemo(function () {
return {
ye: {
label: '业管',
items: [
{ t: '生成交车任务', action: 'genDelivery', accent: 'primary' },
{ t: '生成还车任务', action: 'genReturn', accent: 'orange' },
{ t: '租赁合同', p: 'web端/车辆租赁合同/车辆租赁合同.jsx' },
{ t: '交车任务', p: 'web端/业务管理/交车任务.jsx' },
{ t: '提车应收款', p: 'web端/财务管理/提车应收款.jsx' },
{ t: '租赁账单', p: 'web端/业务管理/租赁账单.jsx' },
{ t: '还车应结款', p: 'web端/财务管理/还车应结款.jsx' },
@@ -846,7 +1196,7 @@ const Component = function () {
{ t: '氢费账单', p: 'web端/财务管理/氢费账单.jsx' },
{ t: '电费账单', p: 'web端/财务管理/电费账单.jsx' },
{ t: 'ETC账单', p: 'web端/业务管理/ETC管理.jsx' },
{ t: '保险管理', p: 'web端/业务管理/保险管理.jsx' },
{ t: '保险采购', p: 'web端/业务管理/保险采购.jsx' },
{ t: '审批中心', p: 'web端/审批中心.jsx' },
{ t: '意见建议', p: 'web端/意见建议.jsx' }
]
@@ -854,6 +1204,7 @@ const Component = function () {
yeEnergy: {
label: '业管-能源部',
items: [
{ t: '站点信息', p: 'web端/加氢站管理/站点信息.jsx' },
{ t: '加氢订单管理', p: 'web端/加氢站管理/加氢订单.jsx' },
{ t: '意见建议', p: 'web端/意见建议.jsx' }
]
@@ -863,6 +1214,7 @@ const Component = function () {
items: [
{ t: '车辆管理', p: 'web端/车辆管理.jsx' },
{ t: '证照管理', p: 'web端/运维管理/车辆业务/证照管理.jsx' },
{ t: '证照管理-编辑', p: 'web端/运维管理/车辆业务/证照管理-编辑.jsx' },
{ t: '备车管理', p: 'web端/运维管理/车辆业务/备车管理.jsx' },
{ t: '交车管理', p: 'web端/运维管理/车辆业务/交车管理.jsx' },
{ t: '还车管理', p: 'web端/运维管理/车辆业务/还车管理.jsx' },
@@ -1005,6 +1357,26 @@ const Component = function () {
}
];
var inspectModalColumns = [
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 },
{ title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 140 },
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 },
{ title: '型号', dataIndex: 'model', key: 'model', width: 120 },
{ title: '下次检验/等评日期', dataIndex: 'nextDate', key: 'nextDate', width: 160 },
{ title: '剩余天数', dataIndex: 'remainDays', key: 'remainDays', width: 120, render: function(v) {
return React.createElement(Text, { type: v < 0 ? 'danger' : undefined }, v + ' 天');
} },
{
title: '操作',
key: 'action',
width: 80,
fixed: 'right',
render: function (_, record) {
return React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { message.info('跳转年审详情(原型)'); } }, '年审');
}
}
];
var overdueDeliveryModalColumns = [
{ title: '预计交车时间', dataIndex: 'expectedDate', key: 'expectedDate', width: 220, ellipsis: true },
{ title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 150, ellipsis: true },
@@ -1334,6 +1706,14 @@ const Component = function () {
openOverdueReturnModal();
return;
}
if (s.key === 'w_ops_inspect') {
setInspectModalOpen(true);
return;
}
if (s.key === 'w_ops_maint') {
message.info('未来上线,敬请期待');
return;
}
message.info('「' + s.title + '」明细(原型,联调接口后打开列表)');
}
};
@@ -1532,6 +1912,22 @@ const Component = function () {
'.workbench-quick-item:focus-visible{box-shadow:0 0 0 2px rgba(22,119,255,0.2)}' +
'.workbench-quick-item-icon{flex-shrink:0;width:26px;height:26px;border-radius:6px;background:linear-gradient(135deg,#f0f5ff,#d6e4ff);line-height:26px;text-align:center;font-size:12px;font-weight:600;color:#1677ff}' +
'.workbench-quick-item-label{flex:1;min-width:0;font-size:11px;line-height:1.3;color:rgba(0,0,0,0.78);word-break:break-word;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}' +
'.workbench-quick-item--action{padding:7px 10px;min-height:38px}' +
'.workbench-quick-item--primary{border-color:rgba(22,119,255,0.2);background:linear-gradient(135deg,#f8fbff 0%,#eef5ff 100%)}' +
'.workbench-quick-item--primary:hover{border-color:rgba(22,119,255,0.35);background:linear-gradient(135deg,#f0f7ff 0%,#e6f0ff 100%)}' +
'.workbench-quick-item--primary .workbench-quick-item-icon{background:linear-gradient(135deg,#1677ff,#4096ff);color:#fff;box-shadow:0 2px 6px rgba(22,119,255,0.25)}' +
'.workbench-quick-item--orange{border-color:rgba(250,140,22,0.22);background:linear-gradient(135deg,#fffaf5 0%,#fff3e6 100%)}' +
'.workbench-quick-item--orange:hover{border-color:rgba(250,140,22,0.38);background:linear-gradient(135deg,#fff7ef 0%,#ffebd6 100%)}' +
'.workbench-quick-item--orange .workbench-quick-item-icon{background:linear-gradient(135deg,#fa8c16,#ffa940);color:#fff;box-shadow:0 2px 6px rgba(250,140,22,0.22)}' +
'.workbench-quick-section-hint{font-size:11px;line-height:1.35;color:rgba(0,0,0,0.45);padding:0 4px 6px;font-weight:500;letter-spacing:0.02em}' +
'.workbench-gen-task-modal .ant-modal-body{padding-top:14px}' +
'.workbench-gen-task-form-label{display:block;margin-bottom:6px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.88);line-height:1.4}' +
'.workbench-gen-task-form-label .req{color:#ff4d4f;margin-right:4px}' +
'.workbench-gen-task-section{background:#fafafa;border:1px solid rgba(0,0,0,0.06);border-radius:10px;padding:14px 16px;margin-bottom:14px}' +
'.workbench-gen-task-readonly{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 16px;margin-bottom:4px}' +
'.workbench-gen-task-readonly-item{font-size:12px;line-height:1.45;color:rgba(0,0,0,0.65)}' +
'.workbench-gen-task-readonly-item strong{color:rgba(0,0,0,0.88);font-weight:500;margin-right:6px}' +
'.workbench-gen-task-table-hint{font-size:12px;color:rgba(0,0,0,0.45);margin:4px 0 10px;line-height:1.45}' +
'.workbench-header-warning-dept-tabs{flex-shrink:0;max-width:100%}' +
'.workbench-header-warning-dept-tabs.ant-tabs{min-width:0}' +
'.workbench-header-warning-dept-tabs .ant-tabs-nav{margin:0!important;min-height:32px}' +
@@ -1730,18 +2126,46 @@ const Component = function () {
)
},
React.createElement('div', { className: 'workbench-quick-scroll', style: { padding: '6px 6px 4px' } },
quickItems.some(function (it) { return it.action; })
? React.createElement('div', { className: 'workbench-quick-section-hint' }, '常用操作')
: null,
React.createElement(Row, { gutter: [6, 6], style: { marginBottom: quickItems.some(function (it) { return !it.action; }) ? 8 : 0 } },
quickItems.filter(function (it) { return it.action; }).map(function (it, idx) {
var accentClass = it.accent === 'orange' ? ' workbench-quick-item--orange' : ' workbench-quick-item--primary';
return React.createElement(Col, { span: 24, key: it.t + '-action-' + idx },
React.createElement('div', {
className: 'workbench-quick-item workbench-quick-item--action' + accentClass,
role: 'button',
tabIndex: 0,
onClick: function () { handleQuickItemClick(it); },
onKeyDown: function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleQuickItemClick(it);
}
}
},
React.createElement('div', { className: 'workbench-quick-item-icon', 'aria-hidden': true }, it.t.charAt(0)),
React.createElement('div', { className: 'workbench-quick-item-label' }, it.t)
)
);
})
),
quickItems.some(function (it) { return !it.action; })
? React.createElement('div', { className: 'workbench-quick-section-hint' }, '功能入口')
: null,
React.createElement(Row, { gutter: [6, 6], style: { marginBottom: 0 } },
quickItems.map(function (it, idx) {
quickItems.filter(function (it) { return !it.action; }).map(function (it, idx) {
return React.createElement(Col, { span: 12, key: it.t + '-' + idx },
React.createElement('div', {
className: 'workbench-quick-item',
role: 'button',
tabIndex: 0,
onClick: function () { protoNav(it.p); },
onClick: function () { handleQuickItemClick(it); },
onKeyDown: function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
protoNav(it.p);
handleQuickItemClick(it);
}
}
},
@@ -1854,7 +2278,7 @@ const Component = function () {
destroyOnClose: true
},
React.createElement(Text, { type: 'secondary', style: { fontSize: 12, display: 'block', marginBottom: 10 } },
'以下任务当前日期已超过预计交车结束日期(含区间结束日),示意数据标红;列表字段与交车管理-待处理一致,联调接接口。'
'以下任务已超过预计交车结束日期,请与业管人员沟通交车是否延误,修改交车时间或及时处理'
),
React.createElement(Table, {
className: 'workbench-overdue-delivery-modal',
@@ -1887,6 +2311,258 @@ const Component = function () {
pagination: false,
scroll: { x: 1780, y: 360 }
})
),
React.createElement(Modal, {
title: '年审/等级评定',
open: inspectModalOpen,
width: 1000,
onCancel: function () { setInspectModalOpen(false); },
footer: React.createElement(Button, { onClick: function () { setInspectModalOpen(false); } }, '关闭'),
destroyOnClose: true
},
React.createElement(Table, {
size: 'small',
rowKey: 'id',
columns: inspectModalColumns,
dataSource: inspectMockRows,
pagination: false,
scroll: { x: 800, y: 360 }
})
),
React.createElement(Modal, {
className: 'workbench-gen-task-modal',
title: '生成交车任务',
open: genDeliveryPickOpen,
width: 520,
destroyOnClose: true,
onCancel: function () {
setGenDeliveryPickOpen(false);
resetGenDeliveryFlow();
},
footer: React.createElement(Space, null,
React.createElement(Button, {
onClick: function () {
setGenDeliveryPickOpen(false);
resetGenDeliveryFlow();
}
}, '取消'),
React.createElement(Button, { type: 'primary', onClick: handleGenDeliveryNext }, '生成')
)
},
React.createElement('div', { className: 'workbench-gen-task-section' },
React.createElement('div', { style: { marginBottom: 14 } },
renderGenTaskLabel('客户名称', true),
React.createElement(Select, {
showSearch: true,
allowClear: true,
placeholder: '请选择客户名称',
style: { width: '100%' },
value: genDeliveryCustomer || undefined,
options: genDeliveryCustomerOptions,
optionFilterProp: 'label',
onChange: function (val) {
setGenDeliveryCustomer(val || '');
setGenDeliveryProjectId(undefined);
}
})
),
React.createElement('div', null,
renderGenTaskLabel('项目名称', true),
React.createElement(Select, {
showSearch: true,
allowClear: true,
placeholder: genDeliveryCustomer
? (genDeliveryProjectOptions.length ? '请选择或搜索项目名称' : '该客户暂无可用项目')
: '请先选择客户名称',
style: { width: '100%' },
value: genDeliveryProjectId,
options: genDeliveryProjectOptions,
optionFilterProp: 'label',
disabled: !genDeliveryCustomer,
onChange: function (val) { setGenDeliveryProjectId(val); }
})
)
),
React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, '选择客户与项目后,将配置预计交车日期并勾选待交车辆。')
),
React.createElement(Modal, {
className: 'workbench-gen-task-modal',
title: '配置交车任务',
open: genDeliveryConfigOpen,
width: 980,
destroyOnClose: true,
onCancel: function () {
setGenDeliveryConfigOpen(false);
resetGenDeliveryFlow();
},
footer: React.createElement(Space, null,
React.createElement(Button, {
onClick: function () {
setGenDeliveryConfigOpen(false);
setGenDeliveryPickOpen(true);
}
}, '上一步'),
React.createElement(Button, {
onClick: function () {
setGenDeliveryConfigOpen(false);
resetGenDeliveryFlow();
}
}, '取消'),
React.createElement(Button, { type: 'primary', onClick: handleGenDeliveryConfirm }, '确认生成')
)
},
selectedGenDeliveryProject
? React.createElement('div', { className: 'workbench-gen-task-readonly' },
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '项目名称'), selectedGenDeliveryProject.name),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '合同编码'), selectedGenDeliveryProject.contractCode),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '客户名称'), selectedGenDeliveryProject.customerName),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '交车区域'), selectedGenDeliveryProject.deliveryRegion)
)
: null,
React.createElement(Row, { gutter: [16, 12], style: { marginBottom: 12 } },
React.createElement(Col, { xs: 24, md: 12 },
renderGenTaskLabel('预计交车日期', true),
React.createElement(RangePicker, {
style: { width: '100%' },
value: genDeliveryExpected,
onChange: function (val) { setGenDeliveryExpected(val); },
placeholder: ['开始日期', '结束日期']
})
),
React.createElement(Col, { xs: 24, md: 12 },
renderGenTaskLabel('开始计费日期', true),
React.createElement(DatePicker, {
style: { width: '100%' },
value: genDeliveryBilling,
onChange: function (val) { setGenDeliveryBilling(val); },
placeholder: '请选择开始计费日期'
})
)
),
React.createElement('div', { className: 'workbench-gen-task-table-hint' },
'勾选待交车辆;已创建交车任务或已完成交车的车辆不可选。'
),
React.createElement(Table, {
size: 'small',
rowKey: 'key',
columns: genDeliveryVehicleColumns,
dataSource: genDeliveryVehicleList,
rowSelection: genDeliveryRowSelection,
pagination: false,
scroll: { x: 860, y: 280 },
locale: { emptyText: '当前项目暂无可选车辆' }
})
),
React.createElement(Modal, {
className: 'workbench-gen-task-modal',
title: '生成还车任务',
open: genReturnPickOpen,
width: 520,
destroyOnClose: true,
onCancel: function () {
setGenReturnPickOpen(false);
resetGenReturnFlow();
},
footer: React.createElement(Space, null,
React.createElement(Button, {
onClick: function () {
setGenReturnPickOpen(false);
resetGenReturnFlow();
}
}, '取消'),
React.createElement(Button, { type: 'primary', onClick: handleGenReturnNext }, '生成')
)
},
React.createElement('div', { className: 'workbench-gen-task-section' },
React.createElement('div', { style: { marginBottom: 14 } },
renderGenTaskLabel('客户名称', true),
React.createElement(Input, {
placeholder: '请输入客户名称',
value: genReturnCustomer,
onChange: function (e) {
setGenReturnCustomer(e.target.value);
setGenReturnProjectId(undefined);
},
allowClear: true
})
),
React.createElement('div', null,
renderGenTaskLabel('项目名称', true),
React.createElement(Select, {
showSearch: true,
allowClear: true,
placeholder: genReturnProjectOptions.length ? '请选择或搜索项目名称' : '暂无匹配项目,请调整客户名称',
style: { width: '100%' },
value: genReturnProjectId,
options: genReturnProjectOptions,
optionFilterProp: 'label',
onChange: function (val) { setGenReturnProjectId(val); }
})
)
),
React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, '选择客户与项目后,将配置预计还车日期并勾选待还车辆。')
),
React.createElement(Modal, {
className: 'workbench-gen-task-modal',
title: '配置还车任务',
open: genReturnConfigOpen,
width: 860,
destroyOnClose: true,
onCancel: function () {
setGenReturnConfigOpen(false);
resetGenReturnFlow();
},
footer: React.createElement(Space, null,
React.createElement(Button, {
onClick: function () {
setGenReturnConfigOpen(false);
setGenReturnPickOpen(true);
}
}, '上一步'),
React.createElement(Button, {
onClick: function () {
setGenReturnConfigOpen(false);
resetGenReturnFlow();
}
}, '取消'),
React.createElement(Button, { type: 'primary', onClick: handleGenReturnConfirm }, '确认生成')
)
},
selectedGenReturnProject
? React.createElement('div', { className: 'workbench-gen-task-readonly' },
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '项目名称'), selectedGenReturnProject.name),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '合同编码'), selectedGenReturnProject.contractCode),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '客户名称'), selectedGenReturnProject.customerName),
React.createElement('div', { className: 'workbench-gen-task-readonly-item' }, React.createElement('strong', null, '交车区域'), selectedGenReturnProject.deliveryRegion)
)
: null,
React.createElement('div', { style: { marginBottom: 12, maxWidth: 320 } },
renderGenTaskLabel('预计还车日期', true),
React.createElement(DatePicker, {
style: { width: '100%' },
value: genReturnDate,
onChange: function (val) { setGenReturnDate(val); },
placeholder: '请选择预计还车日期'
})
),
React.createElement('div', { className: 'workbench-gen-task-table-hint' },
'勾选已交车、待还车辆;仅展示可发起还车流程的车辆。'
),
React.createElement(Table, {
size: 'small',
rowKey: 'key',
columns: genReturnVehicleColumns,
dataSource: genReturnVehicleList,
rowSelection: genReturnRowSelection,
pagination: false,
scroll: { x: 620, y: 280 },
locale: { emptyText: '当前项目暂无已交车车辆' }
})
)
)
);

View File

@@ -0,0 +1,232 @@
// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型
// 帮助中心 - 功能说明书
const Component = function () {
var antd = window.antd;
var Layout = antd.Layout;
var Menu = antd.Menu;
var Typography = antd.Typography;
var Button = antd.Button;
var Space = antd.Space;
var Divider = antd.Divider;
var Title = Typography.Title;
var Paragraph = Typography.Paragraph;
var Text = Typography.Text;
var Sider = Layout.Sider;
var Content = Layout.Content;
var React = window.React;
var useState = React.useState;
var useMemo = React.useMemo;
// 菜单数据源(依据系统最新结构)
var menuItems = [
{
key: '1',
label: '工作台'
},
{
key: '2',
label: '业务管理组',
children: [
{ key: '2-1', label: '客户管理' },
{ key: '2-2', label: '合同创建' },
{ key: '2-3', label: '提车应收款' },
{ key: '2-4', label: '交还车任务' },
{ key: '2-5', label: '租赁账单' },
{ key: '2-6', label: '还车应结款' },
{ key: '2-7', label: '替换车' },
{ key: '2-8', label: '调拨' }
]
},
{
key: '3',
label: '运维服务组',
children: [
{ key: '3-1', label: '车辆管理' },
{ key: '3-2', label: '备件库管理' },
{ key: '3-3', label: '备车' },
{ key: '3-4', label: '交车' },
{ key: '3-5', label: '还车' },
{ key: '3-6', label: '异动' }
]
},
{
key: '4',
label: '业务管理组-能源部',
children: [
{ key: '4-1', label: '加氢记录' },
{ key: '4-2', label: 'ETC记录' },
{ key: '4-3', label: '充电记录' },
{ key: '4-4', label: '能源账单' },
{ key: '4-5', label: '能源账户' },
{ key: '4-6', label: '充值管理' }
]
},
{
key: '5',
label: '安全部',
children: [
{ key: '5-1', label: '事故管理' },
{ key: '5-2', label: '违章管理' },
{ key: '5-3', label: '司机管理' },
{ key: '5-4', label: '安全培训资料' },
{ key: '5-5', label: '安全培训记录' }
]
},
{
key: '6',
label: '审批中心'
}
];
// 扁平化数据用于获取上下文和翻页
var flattenMenu = useMemo(function () {
var list = [];
var traverse = function (items) {
items.forEach(function (item) {
if (!item.children) {
list.push(item);
} else {
list.push(item);
traverse(item.children);
}
});
};
traverse(menuItems);
return list;
}, []);
var ms1 = useState('1');
var selectedKey = ms1[0];
var setSelectedKey = ms1[1];
var currentIndex = flattenMenu.findIndex(function (item) {
return item.key === selectedKey;
});
var currentItem = flattenMenu[currentIndex];
var prevItem = currentIndex > 0 ? flattenMenu[currentIndex - 1] : null;
var nextItem = currentIndex < flattenMenu.length - 1 ? flattenMenu[currentIndex + 1] : null;
var handleMenuClick = function (e) {
setSelectedKey(e.key);
};
// 渲染正文内容
var renderContent = function () {
return React.createElement(
Typography,
null,
React.createElement(Title, { level: 2, className: 'acro-text-xxl' }, currentItem ? currentItem.label : '内容建设中'),
React.createElement(
Paragraph,
{ className: 'acro-text-base', style: { color: 'rgba(0,0,0,0.45)' } },
'该章节(' + (currentItem ? currentItem.label : '') + ')的功能说明正在建设中,您可以随时在这里修改补充...'
)
);
};
var styles = {
layout: {
minHeight: '100vh',
backgroundColor: '#fff',
},
sider: {
backgroundColor: '#fafafa',
borderRight: '1px solid #f0f0f0',
overflowY: 'auto',
height: '100vh',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
},
siderHeader: {
padding: '24px 20px 12px',
fontSize: 18,
fontWeight: 600,
color: 'rgba(0,0,0,0.85)',
borderBottom: '1px solid #f0f0f0'
},
contentWrapper: {
marginLeft: 280,
padding: '40px 60px',
maxWidth: 1000,
display: 'flex',
flexDirection: 'column',
minHeight: '100vh'
},
contentInner: {
flex: 1,
marginBottom: 40
},
footerNav: {
display: 'flex',
justifyContent: 'space-between',
borderTop: '1px solid #f0f0f0',
paddingTop: 24,
marginTop: 'auto'
}
};
return React.createElement(
Layout,
{ style: styles.layout },
React.createElement(
Sider,
{ width: 280, style: styles.sider, theme: 'light' },
React.createElement('div', { style: styles.siderHeader }, '《 功能说明书 》'),
React.createElement(Menu, {
mode: 'inline',
selectedKeys: [selectedKey],
defaultOpenKeys: ['2', '3', '4', '5'],
style: { borderRight: 0, padding: '12px 0' },
items: menuItems,
onClick: handleMenuClick
})
),
React.createElement(
Layout,
{ style: { backgroundColor: '#fff' } },
React.createElement(
Content,
{ style: styles.contentWrapper },
React.createElement(
'div',
{ style: styles.contentInner },
renderContent()
),
React.createElement(
'div',
{ style: styles.footerNav },
prevItem
? React.createElement(
Button,
{
type: 'link',
size: 'large',
onClick: function () { setSelectedKey(prevItem.key); },
style: { padding: 0 }
},
'← 上一页:' + prevItem.label
)
: React.createElement('div', null),
nextItem
? React.createElement(
Button,
{
type: 'link',
size: 'large',
onClick: function () { setSelectedKey(nextItem.key); },
style: { padding: 0 }
},
'下一页:' + nextItem.label + ' →'
)
: React.createElement('div', null)
)
)
)
);
};

View File

@@ -0,0 +1,839 @@
// 【重要】必须使用 const Component 作为组件变量名
// 数据分析 - 业务台账(多层级表头:自营/租赁/销售/氢费/电费/ETC 各业绩·成本·利润;其他单列)
// 原型:年份 + 业务部筛选、导出;业绩列可钻取业务员 → 项目明细(联调可替换接口)
const Component = function () {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var antd = window.antd;
var App = antd.App;
var Breadcrumb = antd.Breadcrumb;
var Card = antd.Card;
var Button = antd.Button;
var Table = antd.Table;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Row = antd.Row;
var Col = antd.Col;
var Space = antd.Space;
var Modal = antd.Modal;
var message = antd.message;
/** 与示意图一致:前 6 类为三列;其他为单列 */
var TRIPLE_DEFS = [
{ key: 'self', groupTitle: '自营业务', short: '自营' },
{ key: 'lease', groupTitle: '租赁业务', short: '租赁' },
{ key: 'resale', groupTitle: '销售', short: '销售' },
{ key: 'h2', groupTitle: '氢费', short: '氢费' },
{ key: 'apply', groupTitle: '电费', short: '电费' },
{ key: 'etc', groupTitle: 'ETC', short: 'ETC' }
];
var TRIPLE_KEYS = TRIPLE_DEFS.map(function (c) { return c.key; });
var ALL_CAT_FOR_DRILL = TRIPLE_DEFS.map(function (c) {
return { key: c.key, label: c.groupTitle };
}).concat([{ key: 'other', label: '其他' }]);
function filterOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function fmtMoney(n) {
if (n === null || n === undefined || n === '') return '-';
var x = Number(n);
if (isNaN(x)) return '-';
if (x === 0) return '-';
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function escapeCsv(v) {
var s = v == null ? '' : String(v);
if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1 || s.indexOf('\r') !== -1) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
function downloadCsv(filename, lines) {
var csv = lines.map(function (row) { return row.map(escapeCsv).join(','); }).join('\n');
var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function initialYear() {
try {
if (window.dayjs) return window.dayjs('2026-01-01');
} catch (e1) {}
return null;
}
function numOrZero(v) {
if (v === null || v === undefined || v === '') return 0;
var n = Number(v);
return isNaN(n) ? 0 : n;
}
function pad2(m) {
var n = Number(m);
if (n < 10) return '0' + n;
return String(n);
}
/** 将金额(元)均分到 n 行,保证合计精确 */
function splitAcrossN(total, n) {
if (n <= 0) return [];
var cents = Math.round(numOrZero(total) * 100);
if (cents === 0) {
var z = [];
var j;
for (j = 0; j < n; j++) z.push(0);
return z;
}
var each = Math.floor(cents / n);
var rem = cents % n;
var out = [];
var i;
for (i = 0; i < n; i++) {
out.push((each + (i < rem ? 1 : 0)) / 100);
}
return out;
}
function sumLedgerRows(monthRows) {
var acc = { month: 13, monthLabel: '合计', rowType: 'total', key: 'total' };
TRIPLE_KEYS.forEach(function (k) {
acc[k + 'Perf'] = 0;
acc[k + 'Cost'] = 0;
acc[k + 'Profit'] = 0;
});
acc.otherAmount = 0;
(monthRows || []).forEach(function (r) {
TRIPLE_KEYS.forEach(function (k) {
acc[k + 'Perf'] += numOrZero(r[k + 'Perf']);
acc[k + 'Cost'] += numOrZero(r[k + 'Cost']);
acc[k + 'Profit'] += numOrZero(r[k + 'Profit']);
});
acc.otherAmount += numOrZero(r.otherAmount);
});
TRIPLE_KEYS.forEach(function (k) {
if (acc[k + 'Perf'] === 0) acc[k + 'Perf'] = null;
if (acc[k + 'Cost'] === 0) acc[k + 'Cost'] = null;
if (acc[k + 'Profit'] === 0) acc[k + 'Profit'] = null;
});
if (acc.otherAmount === 0) acc.otherAmount = null;
return acc;
}
/** 演示数据20261 月氢气/申办/ETC 等与业务部汇总台账样例同源占位 */
function buildMockYear2026() {
var rows = [];
var i;
for (i = 1; i <= 12; i++) {
var selfPerf = 250000 + Math.random() * 100000;
var selfCost = selfPerf * (0.8 + Math.random() * 0.1);
var leasePerf = 180000 + Math.random() * 50000;
var leaseCost = leasePerf * (0.6 + Math.random() * 0.1);
var resalePerf = 300000 + Math.random() * 200000;
var resaleCost = resalePerf * (0.7 + Math.random() * 0.15);
var h2Perf = 80000 + Math.random() * 60000;
var h2Cost = h2Perf * (0.6 + Math.random() * 0.2);
var applyPerf = 3000 + Math.random() * 2000;
var applyCost = applyPerf * (0.3 + Math.random() * 0.1);
var etcPerf = 70000 + Math.random() * 20000;
var etcCost = etcPerf * (0.5 + Math.random() * 0.1);
var otherAmount = 8000 + Math.random() * 5000;
var src = {
selfPerf: Math.round(selfPerf * 100) / 100,
selfCost: Math.round(selfCost * 100) / 100,
selfProfit: Math.round((selfPerf - selfCost) * 100) / 100,
leasePerf: Math.round(leasePerf * 100) / 100,
leaseCost: Math.round(leaseCost * 100) / 100,
leaseProfit: Math.round((leasePerf - leaseCost) * 100) / 100,
resalePerf: Math.round(resalePerf * 100) / 100,
resaleCost: Math.round(resaleCost * 100) / 100,
resaleProfit: Math.round((resalePerf - resaleCost) * 100) / 100,
h2Perf: Math.round(h2Perf * 100) / 100,
h2Cost: Math.round(h2Cost * 100) / 100,
h2Profit: Math.round((h2Perf - h2Cost) * 100) / 100,
applyPerf: Math.round(applyPerf * 100) / 100,
applyCost: Math.round(applyCost * 100) / 100,
applyProfit: Math.round((applyPerf - applyCost) * 100) / 100,
etcPerf: Math.round(etcPerf * 100) / 100,
etcCost: Math.round(etcCost * 100) / 100,
etcProfit: Math.round((etcPerf - etcCost) * 100) / 100,
otherAmount: Math.round(otherAmount * 100) / 100
};
rows.push({
key: 'm' + i,
month: i,
monthLabel: i + '月',
rowType: 'month',
selfPerf: src.selfPerf, selfCost: src.selfCost, selfProfit: src.selfProfit,
leasePerf: src.leasePerf, leaseCost: src.leaseCost, leaseProfit: src.leaseProfit,
resalePerf: src.resalePerf, resaleCost: src.resaleCost, resaleProfit: src.resaleProfit,
h2Perf: src.h2Perf, h2Cost: src.h2Cost, h2Profit: src.h2Profit,
applyPerf: src.applyPerf, applyCost: src.applyCost, applyProfit: src.applyProfit,
etcPerf: src.etcPerf, etcCost: src.etcCost, etcProfit: src.etcProfit,
otherAmount: src.otherAmount
});
}
rows.push(sumLedgerRows(rows));
return rows;
}
function mockSalesmenDrill(month, catKey, cellPerf) {
var base = [
{ key: 's1', name: '尚建华', ratio: 0.42 },
{ key: 's2', name: '刘念念', ratio: 0.35 },
{ key: 's3', name: '谯云', ratio: 0.15 },
{ key: 's4', name: '董剑煜', ratio: 0.08 }
];
var total = numOrZero(cellPerf);
if (total <= 0) total = 100000;
var assigned = 0;
return base.map(function (b, idx) {
if (idx === base.length - 1) {
return { key: b.key, salesperson: b.name, amount: Math.round((total - assigned) * 100) / 100 };
}
var amt = Math.round(total * b.ratio * 100) / 100;
assigned += amt;
return { key: b.key, salesperson: b.name, amount: amt };
});
}
function mockProjectRows(salesperson, catKey) {
var catLabel = (ALL_CAT_FOR_DRILL.find(function (c) { return c.key === catKey; }) || {}).label || catKey;
return [
{ key: 'p1', projectCode: 'PRJ-2026-001', projectName: catLabel + ' · 嘉兴冷链城配项目', plateNo: '沪A62261F', amount: null, bizDate: '2026-01-08', remark: '演示' },
{ key: 'p2', projectCode: 'PRJ-2026-018', projectName: catLabel + ' · 沪浙干线运输', plateNo: '粤AGP3649', amount: null, bizDate: '2026-01-15', remark: '-' },
{ key: 'p3', projectCode: 'PRJ-2026-033', projectName: catLabel + ' · 园区短驳', plateNo: '苏E·D32891', amount: null, bizDate: '2026-01-22', remark: '-' }
].map(function (r, i, arr) {
var share = i === arr.length - 1
? 1 - arr.slice(0, -1).reduce(function (acc) { return acc + 0.31; }, 0)
: 0.31;
return Object.assign({}, r, { amount: Math.round(88000 * share * 100) / 100 });
});
}
/**
* 自营/租赁业绩钻取:按已选业务部列出业务员行;金额列由主表当月汇总均分(氢费=氢气业绩,电费=申办业绩,与演示口径一致)
*/
function buildSelfLeaseDrillRows(salesModal, deptApplied, deptOptions, yearApplied) {
var src = salesModal.sourceRow;
if (!src || salesModal.month == null) {
return { rows: [], totals: { main: 0, h2: 0, elec: 0, etc: 0, other: 0 } };
}
var year = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '2026';
var month = salesModal.month;
var ym = year + '-' + pad2(month);
var deptValues = (deptApplied && deptApplied.length)
? deptApplied.slice()
: deptOptions.map(function (o) { return o.value; });
var namesPool = ['尚建华', '刘念念', '谯云', '董剑煜', '陈思', '周宁'];
var perDept = 2;
var n = deptValues.length * perDept;
if (n === 0) {
return { rows: [], totals: { main: 0, h2: 0, elec: 0, etc: 0, other: 0 } };
}
var totalMain = numOrZero(salesModal.perf);
var totalH2 = numOrZero(src.h2Perf);
var totalElec = numOrZero(src.applyPerf);
var totalEtc = numOrZero(src.etcPerf);
var totalOther = numOrZero(src.otherAmount);
var mains = splitAcrossN(totalMain, n);
var h2s = splitAcrossN(totalH2, n);
var elecs = splitAcrossN(totalElec, n);
var etss = splitAcrossN(totalEtc, n);
var others = splitAcrossN(totalOther, n);
var rows = [];
var idx = 0;
var d;
for (d = 0; d < deptValues.length; d++) {
var dv = deptValues[d];
var deptLabel = (deptOptions.find(function (o) { return o.value === dv; }) || {}).label || dv;
var j;
for (j = 0; j < perDept; j++) {
rows.push({
key: 'drill-' + ym + '-' + String(dv) + '-' + j,
ym: ym,
monthRowSpan: idx === 0 ? n : 0,
deptName: deptLabel,
salesperson: namesPool[idx % namesPool.length],
mainPerf: mains[idx] || 0,
h2PerfCol: h2s[idx] || 0,
elecPerf: elecs[idx] || 0,
etcPerfCol: etss[idx] || 0,
otherAmt: others[idx] || 0
});
idx++;
}
}
return {
rows: rows,
totals: {
main: totalMain,
h2: totalH2,
elec: totalElec,
etc: totalEtc,
other: totalOther
}
};
}
var layoutStyle = {
padding: '20px 24px 32px',
minHeight: '100vh',
background: 'linear-gradient(165deg, #eef4ff 0%, #f5f7fa 42%, #f0f2f5 100%)'
};
var filterLabelStyle = { marginBottom: 6, fontSize: 13, color: 'rgba(0,0,0,0.55)', fontWeight: 500 };
var filterItemStyle = { marginBottom: 12 };
var filterControlStyle = { width: '100%' };
var filterActionsColStyle = { flex: '0 0 auto', marginLeft: 'auto' };
var filterCardStyle = {
marginBottom: 20,
borderRadius: 16,
boxShadow: '0 4px 20px -4px rgba(16,24,40,0.03), 0 0 0 1px rgba(16,24,40,0.06)',
border: 'none',
background: '#ffffff'
};
var tableCardStyle = {
borderRadius: 16,
boxShadow: '0 10px 32px -4px rgba(16,24,40,0.06), 0 0 0 1px rgba(16,24,40,0.04)',
border: 'none',
background: '#ffffff',
overflow: 'hidden'
};
var ledgerTableStyle =
'.biz-standbook-table-wrap{border-radius:12px;overflow:hidden;box-shadow:0 4px 24px -6px rgba(15,23,42,0.05),0 0 0 1px rgba(22,119,255,0.1)}' +
'.biz-standbook-table .ant-table-thead>tr>th{white-space:nowrap;color:#1e293b!important;font-weight:600!important;font-size:13px!important;' +
'background:#f8fafc!important;border-bottom:1px solid #e2e8f0!important;border-inline-end:1px solid #f1f5f9!important;padding:12px 16px!important;transition:background 0.2s}' +
'.biz-standbook-table .ant-table-thead>tr:first-child>th{text-align:center;background:#f1f5f9!important;color:#0f172a!important;font-size:14px!important;border-bottom:2px solid #e2e8f0!important}' +
'.biz-standbook-table .ant-table-tbody>tr:not(.ant-table-measure-row)>td{white-space:nowrap;font-variant-numeric:tabular-nums;color:#334155;border-bottom:1px solid #f1f5f9!important;border-inline-end:1px solid #f8fafc!important;padding:12px 16px!important}' +
'.biz-standbook-table .ant-table-tbody>tr.biz-row-month:hover>td{background:#f0f9ff!important;color:#0f172a}' +
'.biz-standbook-table .ant-table-tbody>tr[data-row-key=\"total\"]>td{font-weight:700;background:#f8fafc!important;color:#0f172a!important;border-top:2px solid #cbd5e1!important;border-bottom:none!important}' +
'.biz-standbook-perf-link{cursor:pointer;color:#0ea5e9;padding:4px 8px;margin:-4px -8px;border:none;background:transparent;font:inherit;border-radius:6px;transition:all 0.2s}' +
'.biz-standbook-perf-link:hover{color:#0284c7;background:#e0f2fe}' +
'.biz-standbook-perf-link:focus{outline:2px solid #38bdf8;outline-offset:2px}' +
'@media (prefers-reduced-motion:reduce){.biz-standbook-table .ant-table-tbody>tr,.biz-standbook-perf-link{transition:none}}';
var deptOptions = useMemo(function () {
return [
{ value: '业务二部', label: '业务二部' },
{ value: '华东业务部', label: '华东业务部' },
{ value: '华南业务部', label: '华南业务部' },
{ value: '华北业务部', label: '华北业务部' },
{ value: '西南业务部', label: '西南业务部' }
];
}, []);
var yearDraftState = useState(initialYear);
var yearDraft = yearDraftState[0];
var setYearDraft = yearDraftState[1];
var yearAppliedState = useState(initialYear);
var yearApplied = yearAppliedState[0];
var setYearApplied = yearAppliedState[1];
/** 业务部多选:空数组表示「全部」 */
var deptDraftState = useState([]);
var deptDraft = deptDraftState[0];
var setDeptDraft = deptDraftState[1];
var deptAppliedState = useState([]);
var deptApplied = deptAppliedState[0];
var setDeptApplied = deptAppliedState[1];
var dataSource = useMemo(function () {
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '';
if (y === '2026') return buildMockYear2026();
return [];
}, [yearApplied]);
var tableTitle = useMemo(function () {
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '—';
return '浙江羚牛氢能业务部汇总台账(' + y + '年度)';
}, [yearApplied]);
/** 与已应用筛选一致:空为全部,多选为「、」连接 */
var deptDisplayLabel = useMemo(function () {
if (!deptApplied || deptApplied.length === 0) return '全部';
return deptApplied.map(function (v) {
var o = deptOptions.find(function (x) { return x.value === v; });
return o ? o.label : v;
}).join('、');
}, [deptApplied, deptOptions]);
var handleQuery = useCallback(function () {
setYearApplied(yearDraft);
setDeptApplied(deptDraft);
}, [yearDraft, deptDraft]);
var handleReset = useCallback(function () {
var y0 = initialYear();
setYearDraft(y0);
setYearApplied(y0);
setDeptDraft([]);
setDeptApplied([]);
}, []);
var viewState = useState('main');
var view = viewState[0];
var setView = viewState[1];
var salesModalState = useState({ month: null, monthLabel: '', catKey: '', catLabel: '', perf: null, sourceRow: null });
var salesModal = salesModalState[0];
var setSalesModal = salesModalState[1];
var projectModalState = useState({ salesperson: '', catKey: '', amount: null });
var projectModal = projectModalState[0];
var setProjectModal = projectModalState[1];
var showSelfLeaseDrill = !!((view === 'sales' || view === 'project') && (salesModal.catKey === 'self' || salesModal.catKey === 'lease') && salesModal.sourceRow);
var salesModalRows = useMemo(function () {
if (view === 'main' || salesModal.month == null || !salesModal.catKey) return [];
if (salesModal.catKey === 'self' || salesModal.catKey === 'lease') return [];
return mockSalesmenDrill(salesModal.month, salesModal.catKey, salesModal.perf);
}, [view, salesModal.month, salesModal.catKey, salesModal.perf]);
var selfLeaseDrillPayload = useMemo(function () {
if (view === 'main' || (salesModal.catKey !== 'self' && salesModal.catKey !== 'lease') || !salesModal.sourceRow) {
return { rows: [], totals: { main: 0, h2: 0, elec: 0, etc: 0, other: 0 } };
}
return buildSelfLeaseDrillRows(salesModal, deptApplied, deptOptions, yearApplied);
}, [view, salesModal.catKey, salesModal.sourceRow, salesModal.perf, salesModal.month, deptApplied, deptOptions, yearApplied]);
var selfLeaseDrillTitle = useMemo(function () {
if (view === 'main' || salesModal.month == null) return '';
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : 'YYYY';
var ym = y + '-' + pad2(salesModal.month);
var kind = salesModal.catKey === 'lease' ? '租赁' : '自营';
return '浙江羚牛氢能业务员' + kind + '业务汇总(' + ym + '';
}, [view, salesModal.month, salesModal.catKey, yearApplied]);
var projectModalRows = useMemo(function () {
if (view !== 'project' || !projectModal.salesperson) return [];
return mockProjectRows(projectModal.salesperson, projectModal.catKey || 'self');
}, [view, projectModal.salesperson, projectModal.catKey]);
var openPerfDrill = useCallback(function (row, catKey) {
if (row.rowType === 'total') {
message.info('合计行不支持钻取,请从各月份对应的「业绩」进入');
return;
}
var v = row[catKey + 'Perf'];
if (v === null || v === undefined || v === '' || numOrZero(v) === 0) {
message.warning('该单元格无业绩数据');
return;
}
var catLabel = (TRIPLE_DEFS.find(function (c) { return c.key === catKey; }) || {}).groupTitle || catKey;
setSalesModal({
month: row.month,
monthLabel: row.monthLabel,
catKey: catKey,
catLabel: catLabel,
perf: v,
sourceRow: (catKey === 'self' || catKey === 'lease') ? row : null
});
setView('sales');
}, []);
var openProjectDrill = useCallback(function (r) {
setProjectModal({
salesperson: r.salesperson,
catKey: salesModal.catKey,
amount: r.amount
});
setView('project');
}, [salesModal.catKey]);
var openProjectDrillFromDetail = useCallback(function (r) {
setProjectModal({
salesperson: r.salesperson,
catKey: salesModal.catKey,
amount: r.mainPerf
});
setView('project');
}, [salesModal.catKey]);
var salesModalColumns = useMemo(function () {
return [
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 120 },
{
title: '业绩金额',
dataIndex: 'amount',
key: 'amount',
align: 'right',
render: function (v, r) {
return React.createElement('button', {
type: 'button',
className: 'biz-standbook-perf-link',
onClick: function () { openProjectDrill(r); }
}, fmtMoney(v));
}
},
{ title: '说明', key: 'hint', width: 200, render: function () { return React.createElement('span', { style: { color: 'rgba(0,0,0,0.45)', fontSize: 12 } }, '点击金额查看项目明细'); } }
];
}, [openProjectDrill]);
var selfLeaseDrillColumns = useMemo(function () {
var mainTitle = salesModal.catKey === 'lease' ? '租赁业绩' : '自营业绩';
return [
{
title: '月份',
dataIndex: 'ym',
key: 'ym',
width: 104,
align: 'center',
onCell: function (record) {
return { rowSpan: record.monthRowSpan };
}
},
{ title: '业务部门', dataIndex: 'deptName', key: 'deptName', width: 124 },
{ title: '业务人员', dataIndex: 'salesperson', key: 'salesperson', width: 100 },
{
title: mainTitle,
dataIndex: 'mainPerf',
key: 'mainPerf',
align: 'right',
width: 120,
render: function (v, r) {
if (v === null || v === undefined || v === '' || numOrZero(v) === 0) {
return fmtMoney(v);
}
return React.createElement('button', {
type: 'button',
className: 'biz-standbook-perf-link',
onClick: function () { openProjectDrillFromDetail(r); }
}, fmtMoney(v));
}
},
{ title: '氢费业绩', dataIndex: 'h2PerfCol', key: 'h2PerfCol', align: 'right', width: 118, render: function (v) { return fmtMoney(v); } },
{ title: '电费业绩', dataIndex: 'elecPerf', key: 'elecPerf', align: 'right', width: 118, render: function (v) { return fmtMoney(v); } },
{ title: 'ETC业绩', dataIndex: 'etcPerfCol', key: 'etcPerfCol', align: 'right', width: 112, render: function (v) { return fmtMoney(v); } },
{ title: '其他', dataIndex: 'otherAmt', key: 'otherAmt', align: 'right', width: 100, render: function (v) { return fmtMoney(v); } }
];
}, [salesModal.catKey, openProjectDrillFromDetail]);
var projectModalColumns = useMemo(function () {
return [
{ title: '项目编号', dataIndex: 'projectCode', key: 'projectCode', width: 130 },
{ title: '项目名称', dataIndex: 'projectName', key: 'projectName', ellipsis: true },
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
{ title: '业绩金额', dataIndex: 'amount', key: 'amount', align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '业务日期', dataIndex: 'bizDate', key: 'bizDate', width: 110 },
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 80 }
];
}, []);
var handleExport = useCallback(function () {
if (!dataSource || dataSource.length === 0) {
message.warning('当前无数据可导出,请先查询');
return;
}
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : 'ledger';
var headers = ['月份'];
TRIPLE_DEFS.forEach(function (c) {
headers.push(c.short + '业绩', c.short + '成本', c.short + '利润');
});
headers.push('其他');
var body = [headers];
dataSource.forEach(function (r) {
var line = [r.monthLabel];
TRIPLE_KEYS.forEach(function (k) {
line.push(fmtMoney(r[k + 'Perf']), fmtMoney(r[k + 'Cost']), fmtMoney(r[k + 'Profit']));
});
line.push(fmtMoney(r.otherAmount));
body.push(line);
});
var deptCsv = (deptApplied && deptApplied.length)
? deptApplied.map(function (v) {
var o = deptOptions.find(function (x) { return x.value === v; });
return o ? o.label : v;
}).join('、')
: '全部';
body.push(['业务部', deptCsv]);
downloadCsv('业务台账_' + y + '_' + new Date().getTime() + '.csv', body);
message.success('已导出');
}, [dataSource, yearApplied, deptApplied, deptOptions]);
var ledgerColumns = useMemo(function () {
var cols = [
{
title: '月份',
dataIndex: 'monthLabel',
key: 'monthLabel',
fixed: 'left',
width: 76,
align: 'center',
render: function (t, r) {
if (r.rowType === 'total') return React.createElement('span', { style: { fontWeight: 700 } }, t);
return t;
}
}
];
TRIPLE_DEFS.forEach(function (cat) {
var ck = cat.key;
var s = cat.short;
cols.push({
title: cat.groupTitle,
key: 'grp-' + ck,
align: 'center',
children: [
{
title: s + '业绩',
dataIndex: ck + 'Perf',
key: ck + 'Perf',
width: 112,
align: 'right',
render: function (v, row) {
if (row.rowType === 'total' || v === null || v === undefined || v === '' || numOrZero(v) === 0) {
return fmtMoney(v);
}
return React.createElement('button', {
type: 'button',
className: 'biz-standbook-perf-link',
onClick: function () { openPerfDrill(row, ck); }
}, fmtMoney(v));
}
},
{
title: s + '成本',
dataIndex: ck + 'Cost',
key: ck + 'Cost',
width: 112,
align: 'right',
render: function (vmt) { return fmtMoney(vmt); }
},
{
title: s + '利润',
dataIndex: ck + 'Profit',
key: ck + 'Profit',
width: 112,
align: 'right',
render: function (vp) { return fmtMoney(vp); }
}
]
});
});
cols.push({
title: '其他',
dataIndex: 'otherAmount',
key: 'otherAmount',
width: 108,
align: 'right',
render: function (vo) { return fmtMoney(vo); }
});
return cols;
}, [openPerfDrill]);
var rowClassName = useCallback(function (record) {
if (record.rowType === 'total') return '';
return 'biz-row-month';
}, []);
var renderMainView = function () {
return React.createElement(React.Fragment, null,
React.createElement(Card, { style: filterCardStyle, bodyStyle: { paddingBottom: 4 } },
React.createElement(Row, { gutter: [16, 16], align: 'bottom' },
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '年份选择'),
React.createElement(DatePicker, {
picker: 'year',
style: filterControlStyle,
placeholder: '请选择年份',
format: 'YYYY',
value: yearDraft,
onChange: function (v) { setYearDraft(v); }
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务部'),
React.createElement(Select, {
mode: 'multiple',
placeholder: '全部',
style: filterControlStyle,
value: deptDraft,
onChange: function (v) { setDeptDraft(v || []); },
options: deptOptions,
showSearch: true,
allowClear: true,
maxTagCount: 2,
filterOption: filterOption
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6, style: filterActionsColStyle },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '\u00a0'),
React.createElement(Space, { wrap: true },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
)
)
)
)
),
React.createElement(Card, { style: tableCardStyle, bodyStyle: { padding: '20px 20px 24px' } },
React.createElement('div', { style: { position: 'relative', marginBottom: 8, minHeight: 36 } },
React.createElement('div', { style: { textAlign: 'center', fontSize: 18, fontWeight: 700, color: 'rgba(15,23,42,0.92)', letterSpacing: '0.02em', padding: '0 88px' } }, tableTitle),
React.createElement('div', { style: { position: 'absolute', right: 0, top: '50%', transform: 'translateY(-50%)' } },
React.createElement(Button, { onClick: handleExport }, '导出')
)
),
React.createElement('div', { style: { textAlign: 'center', marginBottom: 16, fontSize: 13, color: 'rgba(15,23,42,0.55)', fontWeight: 500 } },
'业务部:',
deptDisplayLabel
),
React.createElement('div', { className: 'biz-standbook-table-wrap' },
React.createElement(Table, {
className: 'biz-standbook-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: ledgerColumns,
dataSource: dataSource,
pagination: false,
rowClassName: rowClassName,
scroll: { x: 'max-content', y: 520 },
sticky: true
})
)
)
);
};
var renderSalesView = function () {
var titleText = showSelfLeaseDrill ? selfLeaseDrillTitle : ('业务员业绩 — ' + (salesModal.monthLabel || '') + ' / ' + (salesModal.catLabel || ''));
return React.createElement(Card, { style: tableCardStyle, bodyStyle: { padding: '24px' } },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', marginBottom: 20 } },
React.createElement(Button, {
onClick: function () { setView('main'); },
style: { marginRight: 16 }
}, '返回上一步'),
React.createElement('div', { style: { fontSize: 18, fontWeight: 700, color: '#0f172a' } }, titleText)
),
showSelfLeaseDrill
? React.createElement('div', { className: 'biz-standbook-table-wrap' },
React.createElement(Table, {
className: 'biz-standbook-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: selfLeaseDrillColumns,
dataSource: selfLeaseDrillPayload.rows,
pagination: false,
scroll: { x: 'max-content' },
summary: function () {
var totals = selfLeaseDrillPayload.totals;
var Sm = Table.Summary;
var Row = Sm.Row;
var Cell = Sm.Cell;
return React.createElement(Sm, null,
React.createElement(Row, null,
React.createElement(Cell, { index: 0, colSpan: 3, align: 'center' }, '合计'),
React.createElement(Cell, { index: 3, align: 'right' }, fmtMoney(totals.main)),
React.createElement(Cell, { index: 4, align: 'right' }, fmtMoney(totals.h2)),
React.createElement(Cell, { index: 5, align: 'right' }, fmtMoney(totals.elec)),
React.createElement(Cell, { index: 6, align: 'right' }, fmtMoney(totals.etc)),
React.createElement(Cell, { index: 7, align: 'right' }, fmtMoney(totals.other))
)
);
}
})
)
: React.createElement(React.Fragment, null,
React.createElement('div', { style: { marginBottom: 16, color: '#64748b', fontSize: 14 } },
'从总表钻取:本业务线下各业务员业绩构成。点击「业绩金额」继续查看项目明细。'
),
React.createElement('div', { className: 'biz-standbook-table-wrap' },
React.createElement(Table, {
className: 'biz-standbook-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: salesModalColumns,
dataSource: salesModalRows,
pagination: false,
scroll: { x: 'max-content' }
})
)
)
);
};
var renderProjectView = function () {
var titleText = '项目明细 — ' + (projectModal.salesperson || '') + ' · ' + ((TRIPLE_DEFS.find(function (c) { return c.key === projectModal.catKey; }) || {}).groupTitle || '');
return React.createElement(Card, { style: tableCardStyle, bodyStyle: { padding: '24px' } },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', marginBottom: 20 } },
React.createElement(Button, {
onClick: function () { setView('sales'); },
style: { marginRight: 16 }
}, '返回上一步'),
React.createElement('div', { style: { fontSize: 18, fontWeight: 700, color: '#0f172a' } }, titleText)
),
React.createElement('div', { style: { marginBottom: 16, color: '#64748b', fontSize: 14 } },
'二级钻取:该项目业务员名下具体项目/车辆维度业绩(演示数据)。'
),
React.createElement('div', { className: 'biz-standbook-table-wrap' },
React.createElement(Table, {
className: 'biz-standbook-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: projectModalColumns,
dataSource: projectModalRows,
pagination: false,
scroll: { x: 'max-content' }
})
)
);
};
var breadcrumbItems = [{ title: '数据分析' }, { title: '业务台账' }];
if (view === 'sales' || view === 'project') {
breadcrumbItems.push({ title: showSelfLeaseDrill ? '业务员汇总' : '业务员业绩' });
}
if (view === 'project') {
breadcrumbItems.push({ title: '项目明细' });
}
return React.createElement(App, null,
React.createElement('style', null, ledgerTableStyle),
React.createElement('div', { style: layoutStyle },
React.createElement(Breadcrumb, { style: { marginBottom: 14 }, items: breadcrumbItems }),
view === 'main' ? renderMainView() : null,
view === 'sales' ? renderSalesView() : null,
view === 'project' ? renderProjectView() : null
)
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -108,53 +108,59 @@ const Component = function () {
*/
function buildMockYear2026() {
var rows = [];
var template = [
{
month: 1,
selfPerf: 285000.5, selfCost: 240000, selfProfit: 45000.5,
leasePerf: 188000, leaseCost: 120000, leaseProfit: 68000,
salesPerf: 420000, salesCost: 310000, salesProfit: 110000,
inspectionPerf: 131241.59, inspectionCost: 88000, inspectionProfit: 43241.59,
agencyPerf: 4004.73, agencyCost: 1200, agencyProfit: 2804.73,
etcPerf: 79750.92, etcCost: 45000, etcProfit: 34750.92,
otherPerf: 12000, otherCost: 5000, otherProfit: 7000
},
{
month: 2,
selfPerf: 260000, selfCost: 230000, selfProfit: 30000,
leasePerf: 195000, leaseCost: 125000, leaseProfit: 70000,
salesPerf: null, salesCost: null, salesProfit: null,
inspectionPerf: 98000, inspectionCost: 60000, inspectionProfit: 38000,
agencyPerf: 3200, agencyCost: 1000, agencyProfit: 2200,
etcPerf: 72000, etcCost: 40000, etcProfit: 32000,
otherPerf: null, otherCost: null, otherProfit: null
},
{
month: 3,
selfPerf: 270000, selfCost: 235000, selfProfit: 35000,
leasePerf: 200000, leaseCost: 128000, leaseProfit: 72000,
salesPerf: 380000, salesCost: 290000, salesProfit: 90000,
inspectionPerf: 105000, inspectionCost: 70000, inspectionProfit: 35000,
agencyPerf: 4100, agencyCost: 1100, agencyProfit: 3000,
etcPerf: 81000, etcCost: 43000, etcProfit: 38000,
otherPerf: 8500, otherCost: 3000, otherProfit: 5500
}
];
var i;
for (i = 1; i <= 12; i++) {
var src = template[i - 1];
if (!src) {
src = {
month: i,
selfPerf: null, selfCost: null, selfProfit: null,
leasePerf: null, leaseCost: null, leaseProfit: null,
salesPerf: null, salesCost: null, salesProfit: null,
inspectionPerf: null, inspectionCost: null, inspectionProfit: null,
agencyPerf: null, agencyCost: null, agencyProfit: null,
etcPerf: null, etcCost: null, etcProfit: null,
otherPerf: null, otherCost: null, otherProfit: null
};
}
var selfPerf = 250000 + Math.random() * 100000;
var selfCost = selfPerf * (0.8 + Math.random() * 0.1);
var leasePerf = 180000 + Math.random() * 50000;
var leaseCost = leasePerf * (0.6 + Math.random() * 0.1);
var salesPerf = 300000 + Math.random() * 200000;
var salesCost = salesPerf * (0.7 + Math.random() * 0.15);
var inspectionPerf = 80000 + Math.random() * 60000;
var inspectionCost = inspectionPerf * (0.6 + Math.random() * 0.2);
var agencyPerf = 3000 + Math.random() * 2000;
var agencyCost = agencyPerf * (0.3 + Math.random() * 0.1);
var etcPerf = 70000 + Math.random() * 20000;
var etcCost = etcPerf * (0.5 + Math.random() * 0.1);
var otherPerf = 8000 + Math.random() * 5000;
var otherCost = otherPerf * (0.4 + Math.random() * 0.2);
var src = {
selfPerf: Math.round(selfPerf * 100) / 100,
selfCost: Math.round(selfCost * 100) / 100,
selfProfit: Math.round((selfPerf - selfCost) * 100) / 100,
leasePerf: Math.round(leasePerf * 100) / 100,
leaseCost: Math.round(leaseCost * 100) / 100,
leaseProfit: Math.round((leasePerf - leaseCost) * 100) / 100,
salesPerf: Math.round(salesPerf * 100) / 100,
salesCost: Math.round(salesCost * 100) / 100,
salesProfit: Math.round((salesPerf - salesCost) * 100) / 100,
inspectionPerf: Math.round(inspectionPerf * 100) / 100,
inspectionCost: Math.round(inspectionCost * 100) / 100,
inspectionProfit: Math.round((inspectionPerf - inspectionCost) * 100) / 100,
agencyPerf: Math.round(agencyPerf * 100) / 100,
agencyCost: Math.round(agencyCost * 100) / 100,
agencyProfit: Math.round((agencyPerf - agencyCost) * 100) / 100,
etcPerf: Math.round(etcPerf * 100) / 100,
etcCost: Math.round(etcCost * 100) / 100,
etcProfit: Math.round((etcPerf - etcCost) * 100) / 100,
otherPerf: Math.round(otherPerf * 100) / 100,
otherCost: Math.round(otherCost * 100) / 100,
otherProfit: Math.round((otherPerf - otherCost) * 100) / 100
};
rows.push({
key: 'm' + i,
month: i,

View File

@@ -0,0 +1,752 @@
// 【重要】必须使用 const Component 作为组件变量名
// 数据分析 - 客户服务部业务统计报表(依据业务台账模版数据统计方案)
// 经营总览 KPI + 租赁/物流/能源/成本账户/未收预警;支持汇总→明细钻取(原型演示数据)
const Component = function () {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var antd = window.antd;
var App = antd.App;
var Breadcrumb = antd.Breadcrumb;
var Card = antd.Card;
var Button = antd.Button;
var Table = antd.Table;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Row = antd.Row;
var Col = antd.Col;
var Tabs = antd.Tabs;
var Space = antd.Space;
var Modal = antd.Modal;
var Tag = antd.Tag;
var Statistic = antd.Statistic;
var message = antd.message;
function filterOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function fmtMoney(n) {
if (n === null || n === undefined || n === '') return '—';
var x = Number(n);
if (isNaN(x)) return '—';
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function fmtPct(n) {
if (n === null || n === undefined || n === '') return '—';
var x = Number(n);
if (isNaN(x)) return '—';
return (x * 100).toFixed(1) + '%';
}
function numOrZero(v) {
if (v === null || v === undefined || v === '') return 0;
var n = Number(v);
return isNaN(n) ? 0 : n;
}
function escapeCsv(v) {
var s = v == null ? '' : String(v);
if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
function downloadCsv(filename, lines) {
var csv = lines.map(function (row) { return row.map(escapeCsv).join(','); }).join('\n');
var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function initialYear() {
try {
if (window.dayjs) return window.dayjs('2026-01-01');
} catch (e1) {}
return null;
}
function getAppliedYearMonth(yearApplied, monthApplied) {
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '2026';
var m = monthApplied != null && monthApplied !== '' ? Number(monthApplied) : null;
return { year: y, month: m };
}
var SALESPERSONS = ['谈云', '刘念念', '谯云', '董剑煜', '尚建华'];
var CUSTOMERS = [
'嘉兴古道物流有限公司', '杭州绿道城配科技有限公司', '宁波港联氢运物流有限公司',
'上海虹钦物流有限公司', '嘉兴市乍浦港口经营有限公司', '荣达餐饮(广东)集团有限公司'
];
function buildMockKpi(ym) {
var seed = Number(ym.year) * 100 + (ym.month || 5);
return {
totalRevenue: 4280000 + (seed % 17) * 12000,
totalProfit: 612000 + (seed % 11) * 8000,
uncollected: 386500 + (seed % 9) * 5000,
accountBalance: 1258000 + (seed % 13) * 3000,
activeVehicles: 186 + (seed % 7)
};
}
function buildOverviewRows(ym) {
var lines = [
{ key: 'lease', line: '租赁业务', receivable: 1280000, received: 1120000, cost: 720000, profit: 400000 },
{ key: 'logistics', line: '物流业务', receivable: 2100000, received: 1980000, cost: 1650000, profit: 330000 },
{ key: 'energy', line: '能源销售', receivable: 680000, received: 620000, cost: 480000, profit: 140000 },
{ key: 'etc', line: 'ETC及其他', receivable: 220000, received: 210000, cost: 95000, profit: 115000 }
];
return lines.map(function (r) {
var uncollected = r.receivable - r.received;
return Object.assign({}, r, {
uncollected: uncollected,
profitRate: r.receivable > 0 ? r.profit / r.receivable : 0,
year: ym.year,
monthLabel: ym.month ? ym.month + '月' : '全年累计'
});
});
}
function buildLeaseSummary(ym, filters) {
var rows = [];
var i;
for (i = 0; i < 8; i++) {
var sp = SALESPERSONS[i % SALESPERSONS.length];
var cu = CUSTOMERS[i % CUSTOMERS.length];
if (filters.salesperson && sp !== filters.salesperson) continue;
if (filters.customer && cu !== filters.customer) continue;
var recv = 120000 + i * 18500;
var got = recv - (i % 3 === 0 ? 22000 : 0);
var cost = recv * 0.58;
rows.push({
key: 'lease-' + i,
year: ym.year,
month: ym.month || 5,
salesperson: sp,
customerName: cu,
receivable: recv,
uncollected: recv - got,
cost: cost,
profit: got - cost,
profitRate: recv > 0 ? (got - cost) / recv : 0
});
}
return rows;
}
function buildLogisticsSummary(ym, filters) {
var projects = ['沪浙干线', '嘉兴冷链城配', '宁波港区短驳', '盒马城配专线'];
var rows = [];
projects.forEach(function (p, i) {
var sp = SALESPERSONS[i % SALESPERSONS.length];
var cu = CUSTOMERS[(i + 1) % CUSTOMERS.length];
if (filters.salesperson && sp !== filters.salesperson) return;
if (filters.customer && cu !== filters.customer) return;
var orders = 120 + i * 35;
var recv = 280000 + i * 95000;
var got = recv - (i === 2 ? 45000 : 8000);
rows.push({
key: 'log-' + i,
month: ym.month || 5,
salesperson: sp,
projectName: p,
customerName: cu,
orderCount: orders,
receivable: recv,
received: got,
uncollected: recv - got,
invoiceAmount: got * 0.95
});
});
return rows;
}
function buildEnergySummary(ym) {
return CUSTOMERS.slice(0, 5).map(function (c, i) {
var kg = 4200 + i * 680;
var cost = kg * 32;
var sales = kg * 38;
return {
key: 'eng-' + i,
year: ym.year,
month: ym.month || 5,
customerName: c,
salesperson: SALESPERSONS[i % SALESPERSONS.length],
h2Kg: kg,
costAmount: cost,
salesAmount: sales,
grossProfit: sales - cost,
collectStatus: i % 2 === 0 ? '已收款' : '待收款'
};
});
}
function buildAccountRows() {
return CUSTOMERS.slice(0, 6).map(function (c, i) {
var open = 50000 + i * 12000;
var recharge = 200000 + i * 30000;
var usedH2 = 120000 + i * 22000;
var usedElec = 45000 + i * 8000;
return {
key: 'acc-' + i,
customerName: c,
salesperson: SALESPERSONS[i % SALESPERSONS.length],
openBalance: open,
recharge: recharge,
usedH2: usedH2,
usedElec: usedElec,
closeBalance: open + recharge - usedH2 - usedElec
};
});
}
function buildUncollectedRows(ym) {
var rows = [];
var i;
for (i = 0; i < 10; i++) {
var recv = 80000 + i * 12000;
var uncol = 15000 + (i % 4) * 8000;
rows.push({
key: 'unc-' + i,
line: i % 2 === 0 ? '租赁' : '物流',
salesperson: SALESPERSONS[i % SALESPERSONS.length],
customerName: CUSTOMERS[i % CUSTOMERS.length],
plateNo: '浙F0' + (3280 + i) + 'F',
dueDate: '2026-0' + ((i % 5) + 1) + '-15',
receivable: recv,
uncollected: uncol,
overdueDays: (i % 5) * 7 + 3
});
}
return rows;
}
function mockLeaseDetail(row) {
return [
{ key: 'd1', plateNo: '浙F03298F', receivable: row.receivable * 0.4, received: row.receivable * 0.35, uncollected: row.receivable * 0.05 },
{ key: 'd2', plateNo: '粤AGP3649', receivable: row.receivable * 0.35, received: row.receivable * 0.32, uncollected: row.receivable * 0.03 },
{ key: 'd3', plateNo: '沪A62261F', receivable: row.receivable * 0.25, received: row.receivable * 0.2, uncollected: row.receivable * 0.05 }
];
}
function mockLogisticsDetail(row) {
return [
{ key: 'd1', plateNo: '浙F05519F', tripDate: '2026-05-08', amount: row.receivable * 0.45, h2Fee: 3200, etcFee: 890 },
{ key: 'd2', plateNo: '浙F02698F', tripDate: '2026-05-10', amount: row.receivable * 0.35, h2Fee: 2800, etcFee: 720 },
{ key: 'd3', plateNo: '粤AGR5099', tripDate: '2026-05-12', amount: row.receivable * 0.2, h2Fee: 1500, etcFee: 410 }
];
}
var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' };
var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' };
var filterItemStyle = { marginBottom: 12 };
var filterControlStyle = { width: '100%' };
var profitNegStyle = { background: '#fff1f0', padding: '2px 8px', borderRadius: 4, display: 'inline-block' };
var drillLinkStyle = { cursor: 'pointer', color: '#1677ff', border: 'none', background: 'none', padding: 0, font: 'inherit' };
var tableStyle =
'.cs-dept-stat-table .ant-table-thead>tr>th{background:#e6f4ff!important;font-weight:500;font-size:12px}' +
'.cs-dept-stat-table .ant-table-tbody>tr>td{white-space:nowrap}' +
'.cs-dept-kpi .ant-statistic-title{font-size:13px;color:rgba(0,0,0,0.55)}';
var yearDraftState = useState(initialYear);
var yearDraft = yearDraftState[0];
var setYearDraft = yearDraftState[1];
var yearAppliedState = useState(initialYear);
var yearApplied = yearAppliedState[0];
var setYearApplied = yearAppliedState[1];
var monthDraftState = useState(5);
var monthDraft = monthDraftState[0];
var setMonthDraft = monthDraftState[1];
var monthAppliedState = useState(5);
var monthApplied = monthAppliedState[0];
var setMonthApplied = monthAppliedState[1];
var spDraftState = useState(undefined);
var spDraft = spDraftState[0];
var setSpDraft = spDraftState[1];
var spAppliedState = useState(undefined);
var spApplied = spAppliedState[0];
var setSpApplied = spAppliedState[1];
var cuDraftState = useState(undefined);
var cuDraft = cuDraftState[0];
var setCuDraft = cuDraftState[1];
var cuAppliedState = useState(undefined);
var cuApplied = cuAppliedState[0];
var setCuApplied = cuAppliedState[1];
var activeTabState = useState('overview');
var activeTab = activeTabState[0];
var setActiveTab = activeTabState[1];
var drillState = useState({ open: false, type: '', title: '', rows: [] });
var drill = drillState[0];
var setDrill = drillState[1];
var ym = useMemo(function () {
return getAppliedYearMonth(yearApplied, monthApplied);
}, [yearApplied, monthApplied]);
var filters = useMemo(function () {
return { salesperson: spApplied, customer: cuApplied };
}, [spApplied, cuApplied]);
var kpi = useMemo(function () { return buildMockKpi(ym); }, [ym]);
var overviewRows = useMemo(function () { return buildOverviewRows(ym); }, [ym]);
var leaseRows = useMemo(function () { return buildLeaseSummary(ym, filters); }, [ym, filters]);
var logisticsRows = useMemo(function () { return buildLogisticsSummary(ym, filters); }, [ym, filters]);
var energyRows = useMemo(function () { return buildEnergySummary(ym); }, [ym]);
var accountRows = useMemo(function () { return buildAccountRows(); }, []);
var uncollectedRows = useMemo(function () { return buildUncollectedRows(ym); }, [ym]);
var monthOptions = useMemo(function () {
var opts = [{ value: '', label: '全年' }];
var m;
for (m = 1; m <= 12; m++) opts.push({ value: m, label: m + '月' });
return opts;
}, []);
var spOptions = useMemo(function () {
return [{ value: '', label: '全部业务员' }].concat(SALESPERSONS.map(function (s) { return { value: s, label: s }; }));
}, []);
var cuOptions = useMemo(function () {
return [{ value: '', label: '全部客户' }].concat(CUSTOMERS.map(function (c) { return { value: c, label: c }; }));
}, []);
var handleQuery = useCallback(function () {
setYearApplied(yearDraft);
setMonthApplied(monthDraft === '' ? null : monthDraft);
setSpApplied(spDraft || undefined);
setCuApplied(cuDraft || undefined);
}, [yearDraft, monthDraft, spDraft, cuDraft]);
var handleReset = useCallback(function () {
var y0 = initialYear();
setYearDraft(y0);
setYearApplied(y0);
setMonthDraft(5);
setMonthApplied(5);
setSpDraft(undefined);
setSpApplied(undefined);
setCuDraft(undefined);
setCuApplied(undefined);
}, []);
var openDrill = useCallback(function (type, row) {
var rows = [];
var title = '';
if (type === 'lease') {
rows = mockLeaseDetail(row);
title = '租赁车辆明细 · ' + row.customerName;
} else if (type === 'logistics') {
rows = mockLogisticsDetail(row);
title = '物流运单明细 · ' + row.projectName;
}
setDrill({ open: true, type: type, title: title, rows: rows });
}, []);
var closeDrill = useCallback(function () {
setDrill({ open: false, type: '', title: '', rows: [] });
}, []);
var pageTitle = useMemo(function () {
var m = monthApplied ? monthApplied + '月' : '全年';
return ym.year + '年' + m + ' · 客户服务部业务统计';
}, [ym, monthApplied]);
var overviewColumns = useMemo(function () {
return [
{ title: '业务条线', dataIndex: 'line', key: 'line', width: 100, fixed: 'left' },
{ title: '应收', dataIndex: 'receivable', key: 'receivable', align: 'right', render: fmtMoney },
{ title: '实收', dataIndex: 'received', key: 'received', align: 'right', render: fmtMoney },
{ title: '未收', dataIndex: 'uncollected', key: 'uncollected', align: 'right', render: function (v) {
var n = Number(v);
if (!isNaN(n) && n > 0) return React.createElement('span', { style: { color: '#f53f3f' } }, fmtMoney(v));
return fmtMoney(v);
}},
{ title: '成本', dataIndex: 'cost', key: 'cost', align: 'right', render: fmtMoney },
{ title: '利润', dataIndex: 'profit', key: 'profit', align: 'right', render: function (v) {
var n = Number(v);
var neg = !isNaN(n) && n < 0;
return neg ? React.createElement('span', { style: profitNegStyle }, fmtMoney(v)) : fmtMoney(v);
}},
{ title: '利润率', dataIndex: 'profitRate', key: 'profitRate', align: 'right', width: 88, render: fmtPct }
];
}, []);
var leaseColumns = useMemo(function () {
return [
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88 },
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true },
{ title: '应收', dataIndex: 'receivable', key: 'receivable', align: 'right', render: fmtMoney },
{ title: '未收', dataIndex: 'uncollected', key: 'uncollected', align: 'right', render: fmtMoney },
{ title: '成本', dataIndex: 'cost', key: 'cost', align: 'right', render: fmtMoney },
{ title: '利润', dataIndex: 'profit', key: 'profit', align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '利润率', dataIndex: 'profitRate', key: 'profitRate', align: 'right', render: fmtPct },
{
title: '操作',
key: 'action',
width: 88,
fixed: 'right',
render: function (_, r) {
return React.createElement('button', { type: 'button', style: drillLinkStyle, onClick: function () { openDrill('lease', r); } }, '明细');
}
}
];
}, [openDrill]);
var logisticsColumns = useMemo(function () {
return [
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88 },
{ title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140 },
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 180, ellipsis: true },
{ title: '运单量', dataIndex: 'orderCount', key: 'orderCount', align: 'right', width: 80 },
{ title: '应收', dataIndex: 'receivable', key: 'receivable', align: 'right', render: fmtMoney },
{ title: '实收', dataIndex: 'received', key: 'received', align: 'right', render: fmtMoney },
{ title: '未收', dataIndex: 'uncollected', key: 'uncollected', align: 'right', render: fmtMoney },
{
title: '操作',
key: 'action',
width: 88,
fixed: 'right',
render: function (_, r) {
return React.createElement('button', { type: 'button', style: drillLinkStyle, onClick: function () { openDrill('logistics', r); } }, '明细');
}
}
];
}, [openDrill]);
var energyColumns = useMemo(function () {
return [
{ title: '客户名', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true },
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88 },
{ title: '加氢量(kg)', dataIndex: 'h2Kg', key: 'h2Kg', align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '成本金额', dataIndex: 'costAmount', key: 'costAmount', align: 'right', render: fmtMoney },
{ title: '销售金额', dataIndex: 'salesAmount', key: 'salesAmount', align: 'right', render: fmtMoney },
{ title: '毛利', dataIndex: 'grossProfit', key: 'grossProfit', align: 'right', render: fmtMoney },
{
title: '收款状态',
dataIndex: 'collectStatus',
key: 'collectStatus',
width: 96,
render: function (v) {
return React.createElement(Tag, { color: v === '已收款' ? 'success' : 'warning' }, v);
}
}
];
}, []);
var accountColumns = useMemo(function () {
return [
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true },
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88 },
{ title: '上年结存', dataIndex: 'openBalance', key: 'openBalance', align: 'right', render: fmtMoney },
{ title: '本年充值', dataIndex: 'recharge', key: 'recharge', align: 'right', render: fmtMoney },
{ title: '已用氢费', dataIndex: 'usedH2', key: 'usedH2', align: 'right', render: fmtMoney },
{ title: '已用电费', dataIndex: 'usedElec', key: 'usedElec', align: 'right', render: fmtMoney },
{ title: '期末结余', dataIndex: 'closeBalance', key: 'closeBalance', align: 'right', render: fmtMoney }
];
}, []);
var uncollectedColumns = useMemo(function () {
return [
{ title: '条线', dataIndex: 'line', key: 'line', width: 72 },
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88 },
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 180, ellipsis: true },
{ title: '车牌', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
{ title: '应付款日', dataIndex: 'dueDate', key: 'dueDate', width: 110 },
{ title: '应收', dataIndex: 'receivable', key: 'receivable', align: 'right', render: fmtMoney },
{ title: '未收', dataIndex: 'uncollected', key: 'uncollected', align: 'right', render: fmtMoney },
{
title: '超期天数',
dataIndex: 'overdueDays',
key: 'overdueDays',
align: 'right',
width: 96,
render: function (v) {
var n = Number(v);
if (n > 14) return React.createElement(Tag, { color: 'error' }, v + ' 天');
if (n > 0) return React.createElement(Tag, { color: 'warning' }, v + ' 天');
return v;
}
}
];
}, []);
var drillColumns = useMemo(function () {
if (drill.type === 'lease') {
return [
{ title: '车牌号码', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
{ title: '应收', dataIndex: 'receivable', key: 'receivable', align: 'right', render: fmtMoney },
{ title: '实收', dataIndex: 'received', key: 'received', align: 'right', render: fmtMoney },
{ title: '未收', dataIndex: 'uncollected', key: 'uncollected', align: 'right', render: fmtMoney }
];
}
return [
{ title: '车牌', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
{ title: '出车日期', dataIndex: 'tripDate', key: 'tripDate', width: 110 },
{ title: '金额', dataIndex: 'amount', key: 'amount', align: 'right', render: fmtMoney },
{ title: '氢费', dataIndex: 'h2Fee', key: 'h2Fee', align: 'right', render: fmtMoney },
{ title: 'ETC', dataIndex: 'etcFee', key: 'etcFee', align: 'right', render: fmtMoney }
];
}, [drill.type]);
var handleExport = useCallback(function () {
var headers = ['业务条线', '应收', '实收', '未收', '成本', '利润'];
var body = [headers].concat(overviewRows.map(function (r) {
return [r.line, r.receivable, r.received, r.uncollected, r.cost, r.profit];
}));
downloadCsv('客户服务部业务统计_' + ym.year + '_' + new Date().getTime() + '.csv', body);
message.success('已导出经营总览');
}, [overviewRows, ym.year]);
var tabItems = useMemo(function () {
return [
{
key: 'overview',
label: '经营总览',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: false,
scroll: { x: 900 },
columns: overviewColumns,
dataSource: overviewRows
})
},
{
key: 'lease',
label: '租赁经营',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: { pageSize: 10, showSizeChanger: true },
scroll: { x: 1100 },
columns: leaseColumns,
dataSource: leaseRows
})
},
{
key: 'logistics',
label: '物流经营',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: { pageSize: 10, showSizeChanger: true },
scroll: { x: 1100 },
columns: logisticsColumns,
dataSource: logisticsRows
})
},
{
key: 'energy',
label: '能源销售',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: { pageSize: 10 },
scroll: { x: 1000 },
columns: energyColumns,
dataSource: energyRows
})
},
{
key: 'account',
label: '成本与账户',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: { pageSize: 10 },
scroll: { x: 1000 },
columns: accountColumns,
dataSource: accountRows
})
},
{
key: 'uncollected',
label: '未收预警',
children: React.createElement(Table, {
className: 'cs-dept-stat-table',
rowKey: 'key',
size: 'small',
bordered: true,
pagination: { pageSize: 10 },
scroll: { x: 1100 },
columns: uncollectedColumns,
dataSource: uncollectedRows
})
}
];
}, [
overviewColumns, overviewRows, leaseColumns, leaseRows, logisticsColumns, logisticsRows,
energyColumns, energyRows, accountColumns, accountRows, uncollectedColumns, uncollectedRows
]);
return React.createElement(
App,
null,
React.createElement('style', null, tableStyle),
React.createElement(
'div',
{ style: layoutStyle },
React.createElement(Breadcrumb, {
style: { marginBottom: 12 },
items: [
{ title: '数据分析' },
{ title: '客户服务部业务统计' }
]
}),
React.createElement(
Card,
{ bordered: false, style: { marginBottom: 16 } },
React.createElement('div', { style: { fontSize: 18, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.88)' } }, pageTitle),
React.createElement(
Row,
{ gutter: 16, align: 'bottom' },
React.createElement(Col, { xs: 24, sm: 12, md: 6, lg: 4 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '统计年份'),
React.createElement(DatePicker, {
picker: 'year',
style: filterControlStyle,
value: yearDraft,
onChange: setYearDraft,
allowClear: false
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 6, lg: 4 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '统计月份'),
React.createElement(Select, {
style: filterControlStyle,
value: monthDraft,
onChange: setMonthDraft,
options: monthOptions,
allowClear: false
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 6, lg: 5 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务员'),
React.createElement(Select, {
style: filterControlStyle,
value: spDraft,
onChange: setSpDraft,
options: spOptions,
allowClear: true,
showSearch: true,
filterOption: filterOption,
placeholder: '全部业务员'
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 6, lg: 5 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '客户名称'),
React.createElement(Select, {
style: filterControlStyle,
value: cuDraft,
onChange: setCuDraft,
options: cuOptions,
allowClear: true,
showSearch: true,
filterOption: filterOption,
placeholder: '全部客户'
})
)
),
React.createElement(Col, { xs: 24, sm: 24, md: 24, lg: 6 },
React.createElement(Space, { style: { marginBottom: 12 } },
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'),
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { onClick: handleExport }, '导出总览')
)
)
)
),
React.createElement(
Row,
{ gutter: 16, style: { marginBottom: 16 }, className: 'cs-dept-kpi' },
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 4 },
React.createElement(Card, { size: 'small', bordered: false },
React.createElement(Statistic, { title: '总收入(应收口径)', value: kpi.totalRevenue, precision: 2, suffix: '元' })
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 4 },
React.createElement(Card, { size: 'small', bordered: false },
React.createElement(Statistic, { title: '总利润', value: kpi.totalProfit, precision: 2, suffix: '元', valueStyle: { color: '#00b42a' } })
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 4 },
React.createElement(Card, { size: 'small', bordered: false },
React.createElement(Statistic, { title: '未收余额', value: kpi.uncollected, precision: 2, suffix: '元', valueStyle: { color: '#f53f3f' } })
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 4 },
React.createElement(Card, { size: 'small', bordered: false },
React.createElement(Statistic, { title: '氢电账户结余', value: kpi.accountBalance, precision: 2, suffix: '元' })
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 4 },
React.createElement(Card, { size: 'small', bordered: false },
React.createElement(Statistic, { title: '在营车辆', value: kpi.activeVehicles, suffix: '台' })
)
)
),
React.createElement(
Card,
{ bordered: false, bodyStyle: { paddingTop: 12 } },
React.createElement(Tabs, {
activeKey: activeTab,
onChange: setActiveTab,
items: tabItems,
destroyInactiveTabPane: false
}),
React.createElement('div', { style: { marginTop: 12, fontSize: 12, color: 'rgba(0,0,0,0.45)' } },
'说明:数据为原型演示;联调后由租赁/物流/能源/收支管控等台账子表汇总写入。租赁、物流汇总行可点击「明细」钻取至车辆/运单层级。'
)
),
React.createElement(Modal, {
title: drill.title || '明细',
open: drill.open,
onCancel: closeDrill,
footer: null,
width: 720,
destroyOnClose: true
},
React.createElement(Table, {
rowKey: 'key',
size: 'small',
bordered: true,
pagination: false,
columns: drillColumns,
dataSource: drill.rows
})
)
)
);
};

View File

@@ -0,0 +1,271 @@
# -*- coding: utf-8 -*-
"""生成「还车应结款」用户操作说明 Word 文档及原型配图(脚本可重复运行)"""
import os
from pathlib import Path
from docx import Document
from docx.shared import Pt, Inches, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError:
Image = None
ROOT = Path(__file__).resolve().parent
OUT_DOC = ROOT / "还车应结款-用户操作说明.docx"
LIST_SCREENSHOT = Path(
"/Users/sylvawong/.cursor/projects/Users-sylvawong-Desktop-CURSOR-ONE-OS/assets/"
"2230B49A-9740-471C-ADE4-FC7723DC4CA9_4_5005_c-70f13b01-98b2-42e3-a4f5-6824f02f1838.png"
)
def set_cell_shading(cell, fill_hex):
"""Word 表格单元格背景色"""
shading = OxmlElement("w:shd")
shading.set(qn("w:fill"), fill_hex)
cell._tc.get_or_add_tcPr().append(shading)
def add_heading_cn(doc, text, level):
h = doc.add_heading(text, level=level)
for r in h.runs:
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
return h
def add_para_cn(doc, text, bold=False):
p = doc.add_paragraph()
run = p.add_run(text)
run.font.size = Pt(11)
run.font.name = "PingFang SC"
run._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
run.bold = bold
return p
def make_prototype_figure(path: Path, title: str, lines: list):
"""根据原型结构生成示意配图(非系统实拍,便于培训对照 JSX页面区块"""
if Image is None:
return False
w, h = 900, 520
img = Image.new("RGB", (w, h), (250, 250, 250))
draw = ImageDraw.Draw(img)
draw.rectangle([0, 0, w - 1, h - 1], outline=(200, 200, 200), width=2)
try:
font_title = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 22, index=1)
font_body = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 17, index=1)
font_small = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 14, index=1)
except OSError:
font_title = font_body = font_small = ImageFont.load_default()
y = 20
draw.text((24, y), title, fill=(17, 24, 39), font=font_title)
y += 44
draw.line([(24, y), (w - 24, y)], fill=(217, 217, 217), width=1)
y += 16
for line in lines:
draw.text((32, y), line, fill=(51, 51, 51), font=font_body)
y += 28
note = "说明:本图为原型页面结构示意,正式文档可替换为系统实拍截图。"
draw.text((24, h - 36), note, fill=(120, 120, 120), font=font_small)
img.save(path, "PNG")
return True
def build():
ROOT.mkdir(parents=True, exist_ok=True)
fig_view = ROOT / "配图-还车应结款-查看页-原型结构.png"
fig_fee = ROOT / "配图-还车应结款-费用明细-原型结构.png"
make_prototype_figure(
fig_view,
"还车应结款 · 查看页(原型 还车应结款-查看.jsx",
[
"面包屑:财务管理 / 还车应结款 / 查看",
"卡片① 还车车辆明细表(车牌、合同、项目、客户、交还车时间)",
" · 易损保 / 轮胎保 / 养护保(是/否 + 提示图标说明)",
"卡片② 还车费用明细",
" · 统计:保证金、待结算、应退还、应补缴(可点开分项)",
" · 业务服务组 / 能源组 / 运维部 / 安全组(只读展示)",
"底部:返回列表",
],
)
make_prototype_figure(
fig_fee,
"还车应结款 · 费用明细页(原型 还车应结款-费用明细.jsx",
[
"面包屑:财务管理 / 还车应结款(含「查看需求说明」)",
"卡片 还车车辆明细(同上)",
"卡片 还车费用明细:顶部四统计 + 可折叠四组",
" · 业务服务组:固定费用行 + 可增删行;车辆租金三块",
" · 能源采购组:氢量差/交还车氢量/单价/氢电费/预付款退费",
" · 运维部:清洗保养维修等;轮胎磨损说明气泡;无忧包减免",
" · 安全组:违章清单 + 事故清单;保存/提交/撤回",
"底栏提交审核15天倒计时+四组已提交) / 取消",
],
)
doc = Document()
sect = doc.sections[0]
sect.page_height = Cm(29.7)
sect.page_width = Cm(21.0)
sect.left_margin = Cm(2.2)
sect.right_margin = Cm(2.2)
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
r = title.add_run("数字化资产 ONEOS 运管平台\n还车应结款 · 用户操作说明(培训版)")
r.bold = True
r.font.size = Pt(18)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
add_para_cn(
doc,
"适用范围:财务管理模块下的「还车应结款」列表、只读查看页、费用明细协作页。"
"配图说明图1 为同类财务列表界面实拍(布局与还车应结款列表一致);"
"图 2、图 3 为依据原型 JSX 页面结构整理的示意配图,可在定稿前替换为系统截图。",
)
add_heading_cn(doc, "一、模块作用与入口", 1)
add_para_cn(
doc,
"用于车辆还车后汇总安全、业务服务、运维、能源等数据,形成待结算金额并进入审批/账单流程。"
"入口:左侧菜单 财务管理 → 还车应结款。",
)
add_heading_cn(doc, "二、列表页:筛选与查询", 1)
add_para_cn(doc, "筛选区支持:合同编号、客户名称、项目名称(可展开后:车牌号、还车时间范围、审批状态)。按钮:重置、查询。")
tbl = doc.add_table(rows=8, cols=2)
tbl.style = "Table Grid"
hdr = ["条件", "说明"]
for j, t in enumerate(hdr):
tbl.rows[0].cells[j].text = t
set_cell_shading(tbl.rows[0].cells[j], "E6F4EA")
rows_data = [
("合同编号", "下拉 + 输入模糊匹配"),
("客户名称", "同上"),
("项目名称", "同上"),
("车牌号", "展开后,同上"),
("还车时间", "起止日期,精确到日"),
("审批状态", "待提交/待审批/审批中/审批完成/审批驳回/撤回"),
("导出", "列表卡片右上角导出当前结果"),
]
for i, (a, b) in enumerate(rows_data, start=1):
tbl.rows[i].cells[0].text = a
tbl.rows[i].cells[1].text = b
add_heading_cn(doc, "三、列表页:表格字段与审批状态", 1)
add_para_cn(doc, "提交情况四列:安全组、业务服务组、运维组、能源组。绿点=已提交,灰点=未提交,后接提交人姓名。")
add_para_cn(doc, "审批状态含义:待提交=四组未齐且未提审批;待审批=已提审无人处理;审批中=有节点已通过未完;审批完成=流程结束;审批驳回=任一节点的驳回;撤回=终审前主动撤回。")
add_heading_cn(doc, "四、列表页:操作按钮", 1)
ops = [
"查看:进入只读「查看」页。",
"生成账单:仅「待审批」显示;弹窗账单并可打印(需允许浏览器弹窗)。",
"费用明细:待审批/审批中/审批完成 时隐藏;其余可编辑阶段进入费用明细。",
"撤回:仅待审批、审批中显示;确认后状态改为撤回。",
]
for o in ops:
p = doc.add_paragraph(style="List Bullet")
r = p.add_run(o)
r.font.name = "PingFang SC"
r._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
r.font.size = Pt(11)
add_heading_cn(doc, "五、配图", 1)
p_cap1 = doc.add_paragraph()
p_cap1.alignment = WD_ALIGN_PARAGRAPH.CENTER
r1 = p_cap1.add_run(
"图 1 财务管理 · 列表页布局参考(实拍;与「还车应结款」列表同一套:侧栏、筛选、表格、分页)"
)
r1.font.size = Pt(10)
r1.font.name = "PingFang SC"
r1._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
r1.italic = True
if LIST_SCREENSHOT.is_file():
doc.add_picture(str(LIST_SCREENSHOT), width=Inches(6.2))
doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER
else:
add_para_cn(doc, "未找到图1源文件请将财务列表截图置于文档此处", bold=True)
p_cap2 = doc.add_paragraph()
p_cap2.alignment = WD_ALIGN_PARAGRAPH.CENTER
r2 = p_cap2.add_run("图 2 还车应结款 · 查看页(原型结构示意)")
r2.font.size = Pt(10)
r2.font.name = "PingFang SC"
r2._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
r2.italic = True
if fig_view.is_file():
doc.add_picture(str(fig_view), width=Inches(6.2))
doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER
p_cap3 = doc.add_paragraph()
p_cap3.alignment = WD_ALIGN_PARAGRAPH.CENTER
r3 = p_cap3.add_run("图 3 还车应结款 · 费用明细页(原型结构示意)")
r3.font.size = Pt(10)
r3.font.name = "PingFang SC"
r3._element.rPr.rFonts.set(qn("w:eastAsia"), "PingFang SC")
r3.italic = True
if fig_fee.is_file():
doc.add_picture(str(fig_fee), width=Inches(6.2))
doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER
add_heading_cn(doc, "六、查看页(只读)要点", 1)
add_para_cn(
doc,
"车辆明细含三项保险及提示文案。费用区四张统计卡可点击展开分项。"
"业务服务组、能源组、运维部、安全组为只读表格;轮胎磨损可悬停查看逐胎明细。底部返回列表。",
)
add_heading_cn(doc, "七、费用明细页:各组分工", 1)
add_para_cn(
doc,
"业务服务组5 项固定费用 + 可新增行;车辆租金(已收/实际/应退,实际租金默认按日折算可手改)。保存/提交/撤回;已提交后锁定至撤回。",
)
add_para_cn(
doc,
"能源采购组:交还车氢量、单价只读;交车氢量大于还车时氢量差补缴自动算;氢费/电费/预付款退费可填;最后一辆车红色提示。",
)
add_para_cn(
doc,
"运维部:清洗、保养、维修、车损等;证件丢失按规则自动计价;送/接车服务费禁用反写;轮胎磨损与胎纹合计规则见需求说明;无忧包减免与三项保险挂钩。",
)
add_para_cn(
doc,
"安全组:违章清单、事故清单;确认后提交。",
)
add_heading_cn(doc, "八、金额计算(口径)", 1)
add_para_cn(
doc,
"待结算总额 = 业务服务组费用合计 + 车辆应退租金 + 氢量差补缴 + 氢费补缴 + 电费补缴 预付款退费 + 运维部费用合计。",
)
add_para_cn(
doc,
"应退还总额:保证金 待结算 为正时取该值。应补缴总额:保证金 待结算 为负时取绝对值。",
)
add_heading_cn(doc, "九、提交审核条件", 1)
add_para_cn(
doc,
"原型规则:单据生成后满 15 天倒计时结束,且业务服务组、能源采购组、运维部、安全组均为「已提交」,"
"底部「提交审核」才可点;否则按钮禁用并显示剩余天/小时。提交前系统校验各必填金额与无忧包减免等。",
)
add_heading_cn(doc, "十、修订记录", 1)
add_para_cn(doc, "文档版本V1.0 生成依据web端/财务管理/还车应结款.jsx、还车应结款-查看.jsx、还车应结款-费用明细.jsx 内嵌需求说明。")
doc.save(OUT_DOC)
print("Wrote:", OUT_DOC)
if __name__ == "__main__":
build()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -113,6 +113,12 @@ const Component = function() {
var os1j = React.useState('');
var serviceItemSearch = os1j[0];
var setServiceItemSearch = os1j[1];
var copyPopState = React.useState(null);
var copyPopover = copyPopState[0];
var setCopyPopover = copyPopState[1];
var copyQtyState = React.useState('1');
var copyQuantity = copyQtyState[0];
var setCopyQuantity = copyQtyState[1];
var os1k = React.useState(null);
var plateNoDropdownRect = os1k[0];
var setPlateNoDropdownRect = os1k[1];
@@ -296,16 +302,57 @@ const Component = function() {
next.splice(index, 1);
setRentalOrders(next.length ? next : [Object.assign({}, emptyRentalRow)]);
};
var copyRentalRow = function(index) {
var row = rentalOrders[index];
if (!row) return;
var serviceItemsCopy = (row.serviceItems || []).map(function(si) { return { project: si.project || '', fee: si.fee || '', effectiveDate: si.effectiveDate || '' }; });
var buildDuplicateRentalRow = function(sourceRow) {
var serviceItemsCopy = (sourceRow.serviceItems || []).map(function(si) { return { project: si.project || '', fee: si.fee || '', effectiveDate: si.effectiveDate || '' }; });
if (serviceItemsCopy.length === 0) serviceItemsCopy = [{ project: '', fee: '', effectiveDate: '' }];
var newRow = { id: nextRentalRowIdRef.current++, brand: row.brand || '', model: row.model || '', plateNo: '', vin: '', monthRent: row.monthRent || '', serviceItems: serviceItemsCopy, deposit: row.deposit || '', remark: row.remark || '' };
return { id: nextRentalRowIdRef.current++, brand: sourceRow.brand || '', model: sourceRow.model || '', plateNo: '', vin: '', monthRent: sourceRow.monthRent || '', serviceItems: serviceItemsCopy, deposit: sourceRow.deposit || '', remark: sourceRow.remark || '' };
};
var applyRentalRowCopies = function(rowIndex, count) {
var row = rentalOrders[rowIndex];
if (!row) return;
var n = typeof count === 'number' ? count : parseInt(String(count), 10);
if (isNaN(n) || n < 0) {
if (message.warning) message.warning('请输入非负整数');
return;
}
n = Math.floor(n);
if (n === 0) {
setCopyPopover(null);
if (message.info) message.info('复制数量为 0未新增行');
return;
}
var next = rentalOrders.slice(0);
next.splice(index + 1, 0, newRow);
var insertAt = rowIndex + 1;
for (var i = 0; i < n; i++) {
next.splice(insertAt + i, 0, buildDuplicateRentalRow(row));
}
setRentalOrders(next);
if (typeof message !== 'undefined' && message.success) message.success('已复制该行(车牌号已清空)');
setCopyPopover(null);
if (message.success) message.success('已复制 ' + n + ' 条记录(车牌号已清空)');
};
var confirmRentalCopyPopover = function() {
if (!copyPopover) return;
if (copyQuantity === '') {
if (message.warning) message.warning('请输入复制数量');
return;
}
if (!/^\d+$/.test(copyQuantity)) {
if (message.warning) message.warning('请输入非负整数');
return;
}
applyRentalRowCopies(copyPopover.index, parseInt(copyQuantity, 10));
};
var openRentalCopyPopover = function(rowIndex, anchorEl) {
if (!anchorEl || !anchorEl.getBoundingClientRect) return;
var r = anchorEl.getBoundingClientRect();
var cardH = 168;
var top = r.bottom + 8;
if (top + cardH > window.innerHeight - 8) top = Math.max(8, r.top - cardH - 8);
var left = r.left;
var cardW = 240;
if (left + cardW > window.innerWidth - 8) left = Math.max(8, window.innerWidth - cardW - 8);
setCopyPopover({ index: rowIndex, top: top, left: left });
setCopyQuantity('1');
};
var updateRentalOrder = function(index, field, value) {
var next = rentalOrders.slice(0);
@@ -384,6 +431,22 @@ const Component = function() {
return function() { document.removeEventListener('mousedown', handler); };
}, [deliveryRegionOpen]);
React.useEffect(function() {
if (!copyPopover) return;
var handler = function(e) {
var pop = document.getElementById('rental-copy-popover');
if (pop && pop.contains(e.target)) return;
setCopyPopover(null);
};
var t = window.setTimeout(function() {
document.addEventListener('mousedown', handler);
}, 0);
return function() {
window.clearTimeout(t);
document.removeEventListener('mousedown', handler);
};
}, [copyPopover]);
React.useEffect(function() {
if (plateNoFocusRow === null) { setPlateNoDropdownRect(null); return; }
var timer = setTimeout(function() {
@@ -451,7 +514,10 @@ const Component = function() {
btnGroupItemActive: { backgroundColor: '#1677ff', color: '#fff', borderColor: '#1677ff', borderRightColor: '#1677ff' },
feeSectionTitle: { fontSize: 16, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 },
feeSectionTitleFirst: { marginTop: 0 },
modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' }
modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' },
copyPopoverCard: { position: 'fixed', zIndex: 2000, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.12), 0 0 1px rgba(0,0,0,0.08)', border: '1px solid #f0f0f0', padding: '14px 16px', minWidth: 232, maxWidth: 'calc(100vw - 24px)' },
copyPopoverTitle: { fontSize: 14, fontWeight: 600, color: 'rgba(0,0,0,0.88)', marginBottom: 12 },
copyPopoverFooter: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }
};
var CardBlock = function(props) {
@@ -582,7 +648,7 @@ const Component = function() {
React.createElement('td', { style: styles.rentalTdCenter }, calcRowServiceFee(row) + ' 元'),
React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateRentalOrder(idx, 'deposit', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))),
React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '备注', value: row.remark || '', onChange: function(e) { updateRentalOrder(idx, 'remark', e.target.value); }, style: Object.assign({}, styles.rentalInput, { width: '100%' }) })),
React.createElement('td', { style: styles.rentalTdCenter }, React.createElement('div', { style: { display: 'inline-flex', gap: 4, alignItems: 'center' } }, React.createElement(Button, { type: 'link', size: 'small', onClick: function() { copyRentalRow(idx); } }, '复制'), React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')))
React.createElement('td', { style: styles.rentalTdCenter }, React.createElement('div', { style: { display: 'inline-flex', gap: 4, alignItems: 'center' } }, React.createElement(Button, { type: 'link', size: 'small', 'aria-haspopup': 'dialog', 'aria-expanded': copyPopover && copyPopover.index === idx, onClick: function(e) { e.preventDefault(); e.stopPropagation(); openRentalCopyPopover(idx, e.currentTarget); } }, '复制'), React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')))
);
});
@@ -609,6 +675,41 @@ const Component = function() {
var rentalTableTbody = React.createElement('tbody', null, rentalTableBody);
var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, rentalTableThead, rentalTableTbody);
var rentalTableWrap = React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl);
var rentalCopyPopoverEl = copyPopover
? React.createElement('div', {
id: 'rental-copy-popover',
role: 'dialog',
'aria-modal': 'false',
'aria-label': '复制租赁订单行',
style: Object.assign({}, styles.copyPopoverCard, { top: copyPopover.top, left: copyPopover.left }),
onMouseDown: function(e) { e.stopPropagation(); }
},
React.createElement('div', { style: styles.copyPopoverTitle }, '复制本行'),
React.createElement('div', null,
React.createElement('label', { style: { display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 } }, '复制数量'),
React.createElement(Input, {
value: copyQuantity,
placeholder: '非负整数,如 3',
inputMode: 'numeric',
pattern: '[0-9]*',
autoComplete: 'off',
'aria-label': '复制数量,非负整数',
onChange: function(e) {
var raw = (e.target && e.target.value) || '';
var digits = raw.replace(/\D/g, '');
setCopyQuantity(digits);
},
onPressEnter: confirmRentalCopyPopover,
style: { width: '100%' }
})
),
React.createElement('div', { style: styles.copyPopoverFooter },
React.createElement(Button, { size: 'small', onClick: function() { setCopyPopover(null); } }, '取消'),
React.createElement(Button, { type: 'primary', size: 'small', onClick: confirmRentalCopyPopover }, '确认')
)
)
: null;
var rentalContent = React.createElement('div', null, rentalSummary, formErrors.rentalOrders ? React.createElement('div', { style: styles.errMsg }, formErrors.rentalOrders) : null, rentalTableWrap, React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRentalRow }, '添加一行'), hydrogenFormRow);
var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费'));
@@ -692,6 +793,7 @@ const Component = function() {
React.createElement(CardBlock, { id: 'card-fee', cardStyle: { marginTop: 16 }, title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent),
React.createElement(CardBlock, { id: 'card-billing', cardStyle: { marginTop: 16 }, title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent),
React.createElement('div', { style: { height: 60 } }),
rentalCopyPopoverEl,
serviceModalContent,
reqSpecModalContent,
React.createElement('div', { style: styles.footer }, React.createElement(Button, { type: 'primary', size: 'large', onClick: function() { if (validateSubmitAndReview()) { message.success('租赁合同已提交审核。'); } } }, '提交并审核'), React.createElement(Button, { size: 'large', onClick: function() { message.info('保存,加入租赁合同列表(仅操作人可查看编辑)'); } }, '保存'), React.createElement(Button, { size: 'large', onClick: function() { message.info('取消'); } }, '取消'))

View File

@@ -15,6 +15,8 @@ const Component = function() {
var Card = antd.Card;
var DatePicker = antd.DatePicker;
var Popover = antd.Popover;
var Tag = antd.Tag;
var Avatar = antd.Avatar;
var Dropdown = antd.Dropdown;
var Modal = antd.Modal;
var Upload = antd.Upload;
@@ -258,7 +260,12 @@ const Component = function() {
updater: '李专员',
updateTime: '2025-02-15 16:00',
remark: '-',
legalStampedContractUploaded: undefined
legalStampedContractUploaded: undefined,
approvalFlowNodes: [
{ nodeTitle: '业务服务主管审批', result: 'passed', operatorName: '姚守涛', operatorTime: '2026-04-29 17:57:15' },
{ nodeTitle: '发起审批', result: 'passed', operatorName: '超级用户', operatorTime: '2026-04-28 17:44:45' },
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['超级用户', '金可鹏'] }
]
},
// 5. 审批中 + 变更(已通过审批后做了变更并重新提交)
{
@@ -285,7 +292,12 @@ const Component = function() {
updater: '李专员',
updateTime: '2025-02-22 14:00',
remark: '变更车辆数量',
legalStampedContractUploaded: undefined
legalStampedContractUploaded: undefined,
approvalFlowNodes: [
{ nodeTitle: '法务审核', result: 'passed', operatorName: '李法务', operatorTime: '2025-02-21 18:00:00' },
{ nodeTitle: '发起审批', result: 'passed', operatorName: '李专员', operatorTime: '2025-02-22 10:00:00' },
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['张经理', '赵总监'] }
]
},
// 6. 审批通过 + 合同进行中(正式合同)
{
@@ -664,6 +676,131 @@ const Component = function() {
{ title: '交车人', dataIndex: 'deliveryPerson', key: 'deliveryPerson', width: 100 }
];
function approvalAvatarText(name) {
if (!name) return '?';
var s = String(name);
return s.length <= 2 ? s : s.slice(-2);
}
function renderApprovalFlowContent(nodes) {
var list = nodes && nodes.length ? nodes : [];
if (!list.length) {
return React.createElement('div', { style: { color: 'rgba(0,0,0,0.45)', fontSize: 13, padding: '4px 0' } }, '暂无审批节点明细');
}
return React.createElement(
'div',
{ style: { maxWidth: 360, paddingTop: 4 } },
list.map(function(node, index) {
var isLast = index === list.length - 1;
var isPending = node.result === 'pending';
var dot = React.createElement(
Avatar,
{
size: 28,
style: {
backgroundColor: isPending ? '#1890ff' : '#f0f0f0',
color: isPending ? '#fff' : 'rgba(0,0,0,0.45)',
fontSize: 11,
lineHeight: '28px',
flexShrink: 0
},
children: isPending
? React.createElement(
'svg',
{ viewBox: '0 0 24 24', width: 14, height: 14, fill: 'currentColor', style: { verticalAlign: 'middle' } },
React.createElement('path', {
d: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4zm5-9h2v2h-2V5zm0 4h2v2h-2V9z'
})
)
: approvalAvatarText(node.operatorName)
}
);
var body = React.createElement(
'div',
{ style: { flex: 1, paddingLeft: 12, paddingBottom: isLast ? 0 : 14, minWidth: 0 } },
React.createElement(
'div',
{ style: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginBottom: 6 } },
React.createElement('span', { style: { fontSize: 14, color: 'rgba(0,0,0,0.88)', fontWeight: 500 } }, node.nodeTitle || '-'),
React.createElement(Tag, { color: isPending ? 'processing' : 'success', style: { margin: 0 } }, isPending ? '待审核' : '通过')
),
isPending && node.pendingApprovers && node.pendingApprovers.length
? React.createElement(
Space,
{ size: [8, 8], wrap: true },
node.pendingApprovers.map(function(apName, i) {
return React.createElement(
Tag,
{
key: i,
style: {
margin: 0,
color: '#1890ff',
background: '#e6f7ff',
borderColor: '#91d5ff'
}
},
React.createElement(
'span',
null,
React.createElement(
'svg',
{
viewBox: '0 0 24 24',
width: 12,
height: 12,
fill: '#1890ff',
style: { marginRight: 4, verticalAlign: '-2px' }
},
React.createElement('path', {
d: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'
})
),
apName
)
);
})
)
: React.createElement(
'div',
{ style: { fontSize: 13, color: 'rgba(0,0,0,0.45)' } },
(node.operatorName || '') + (node.operatorTime ? ' ' + node.operatorTime : '')
)
);
return React.createElement(
'div',
{ key: index, style: { display: 'flex', alignItems: 'stretch' } },
React.createElement(
'div',
{
style: {
width: 36,
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}
},
React.createElement('div', { style: { lineHeight: 0 } }, dot),
isLast
? null
: React.createElement('div', {
style: {
flex: 1,
width: 2,
minHeight: 12,
marginTop: 4,
background: '#f0f0f0',
borderRadius: 1
}
})
),
body
);
})
);
}
function getMoreMenuItems(record) {
var status = record.contractStatus;
var type = record.contractType;
@@ -818,7 +955,37 @@ const Component = function() {
);
}
},
{ title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 },
{
title: '审批状态',
dataIndex: 'approvalStatus',
key: 'approvalStatus',
width: 100,
render: function(text, record) {
if (text !== '审批中') return text;
var nodes = record.approvalFlowNodes;
return React.createElement(
Popover,
{
content: renderApprovalFlowContent(nodes),
trigger: 'hover',
placement: 'rightTop',
overlayInnerStyle: { maxWidth: 400 },
mouseEnterDelay: 0.15
},
React.createElement(
'span',
{
style: {
cursor: 'help',
borderBottom: '1px dashed rgba(0,0,0,0.35)',
color: 'rgba(0,0,0,0.88)'
}
},
text
)
);
}
},
{ title: '合同状态', dataIndex: 'contractStatus', key: 'contractStatus', width: 110 },
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140 },
{ title: '签约公司', dataIndex: 'signingCompany', key: 'signingCompany', width: 100 },

View File

@@ -10,6 +10,8 @@ const Component = function () {
var Tabs = antd.Tabs;
var Table = antd.Table;
var Tag = antd.Tag;
var Badge = antd.Badge;
var Alert = antd.Alert;
var Button = antd.Button;
var Tooltip = antd.Tooltip;
var Select = antd.Select;
@@ -75,31 +77,59 @@ const Component = function () {
stackVendor: 'XXXXXXXX企业'
};
// 证照管理 Tab证件照片占位(可替换为真实 URL支持 Image 预览)
var licensePhotoPlaceholder = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="336" height="224"><rect fill="#f5f5f5" width="100%" height="100%"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#8c8c8c" font-size="14" font-family="system-ui,sans-serif">行驶证/证照示意图</text></svg>');
function certPhotoPlaceholder(captionText) {
return 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="336" height="224"><rect fill="#f5f5f5" width="100%" height="100%"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#8c8c8c" font-size="13" font-family="system-ui,sans-serif">' + String(captionText || '') + '</text></svg>');
}
/** 证照管理 Tab mock字段与录入页设计稿一致本页仅查看 */
var certificateDetail = {
drivingLicense: { img: licensePhotoPlaceholder, regDate: '2025-07-01', scrapDate: '2038-12-31', inspectExpire: '2027-07-31' },
operationPermit: { img: licensePhotoPlaceholder, permitNo: '营330482001234', regDate: '2025-07-15', inspectExpire: '2026-07-14' },
passPermit: { img: licensePhotoPlaceholder, code: 'TX-ZJ-2025-0088', area: '浙江省嘉兴市平湖市行政辖区' },
registrationCert: { img: licensePhotoPlaceholder },
h2Permit: {
photos: [
{ label: '特种设备使用登记证', src: certPhotoPlaceholder('特种设备使用登记证') },
{ label: '特种设备使用标志', src: certPhotoPlaceholder('特种设备使用标志') }
],
cylinderVendor: '某某高压气瓶制造有限公司',
cylinderInspectDate: '2025-04-01',
cylinderCycleMonth: '36',
cylinderValidUntil: '2028-03-31'
/** 证照管理 Tab八类证照只读展示(字段口径对齐「证照管理-编辑」) */
var LICENSE_VIEW_ANCHOR_DATE = '2026-06-01';
var licensePhotoPlaceholder = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="336" height="224"><rect fill="#f5f5f5" width="100%" height="100%"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#8c8c8c" font-size="14" font-family="system-ui,sans-serif">暂无影像</text></svg>');
var licenseViewBundle = {
driverLicense: {
photos: ['https://picsum.photos/seed/vd-zjf-lic/600/400', 'https://picsum.photos/seed/vd-zjf-lic2/600/400'],
regDate: '2025-07-01',
issueDate: '2025-07-01',
scrapDate: '2038-12-31',
expireDate: '2027-07-31',
shNextEvaluation: ''
},
h2Card: { img: licensePhotoPlaceholder, stationName: '嘉兴港区某某加氢站' },
safetyValve: { img: licensePhotoPlaceholder, inspectDate: '2025-05-10', cycleMonth: '12', validUntil: '2026-05-09' },
pressureGauge: { img: licensePhotoPlaceholder, inspectDate: '2025-05-10', cycleMonth: '12', valveInspectValidUntil: '2026-05-09' }
transportLicense: {
photos: ['https://picsum.photos/seed/vd-zjf-trans/600/400'],
licenseNo: '浙字330482001234号',
issueDate: '2025-07-15',
expireDate: '2026-07-14',
inspectValidUntil: '2026-07-20'
},
registrationCert: { photos: ['https://picsum.photos/seed/vd-zjf-reg/600/400'] },
specialEquipCert: { photos: ['https://picsum.photos/seed/vd-zjf-spec/600/400'] },
specialEquipDecal: {
photos: ['https://picsum.photos/seed/vd-zjf-decal/600/400'],
nextInspectDate: '2026-07-20'
},
hydrogenCard: {
cardNo: 'H2-3304-0690-0088',
cardType: '中石化加氢卡',
balance: 8650.5,
issueDate: '2025-03-10 10:15',
issueUser: '能源管理部-张晓'
},
safetyValve: {
photos: ['https://picsum.photos/seed/vd-zjf-valve/600/400'],
inspectDate: '2025-05-10',
nextInspectDate: '2026-05-09'
},
pressureGauge: {
photos: ['https://picsum.photos/seed/vd-zjf-gauge/600/400'],
inspectDate: '2025-12-15',
nextInspectDate: '2026-06-14'
}
};
var CERT_NAV_ITEMS = [
{ key: 'driverLicense', label: '行驶证' },
{ key: 'transportLicense', label: '道路运输证' },
{ key: 'registrationCert', label: '登记证' },
{ key: 'specialEquipCert', label: '特种设备使用登记证' },
{ key: 'specialEquipDecal', label: '特种设备使用标识' },
{ key: 'hydrogenCard', label: '加氢卡' },
{ key: 'safetyValve', label: '安全阀' },
{ key: 'pressureGauge', label: '压力表' }
];
var maintenanceList = [
{ key: '1', item: '变速器油', kmCycle: '60000', monthCycle: '24', laborCost: '0', materialCost: '571', total: '571', lastKm: '' },
@@ -110,6 +140,7 @@ const Component = function () {
];
var activeTab = useState('model');
var licenseActiveNav = useState('driverLicense');
var rearFilterDraft = useState({ installRange: null, deviceType: undefined });
var rearFilterApplied = useState({ installRange: null, deviceType: undefined });
var leaseFilterDraft = useState({ contractCode: undefined, projectName: undefined, customerName: undefined });
@@ -539,122 +570,334 @@ const Component = function () {
);
}
function CertFieldRow(label, value) {
var show = value != null && String(value).trim() !== '';
function LicViewFieldRow(label, value) {
var show = value != null && String(value).trim() !== '' && String(value).trim() !== '—';
return React.createElement('div', { style: { display: 'flex', gap: 8, marginBottom: 10, fontSize: 14, lineHeight: '22px' } },
React.createElement('span', { style: { color: 'rgba(0,0,0,0.45)', width: 168, flexShrink: 0 } }, label + ''),
React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)' } }, show ? value : '—')
);
}
/** photoCaption左侧附图说明与设计稿上「行驶证」「营运证」等一致 */
function CertSectionCard(title, imageSrc, fieldNodes, photoCaption) {
var cap = photoCaption != null && String(photoCaption).trim() !== '' ? String(photoCaption) : '证件照片';
var right = fieldNodes && fieldNodes.length
? React.createElement('div', {
style: {
flex: 1,
minWidth: 260,
display: 'flex',
alignItems: 'center',
alignSelf: 'stretch'
}
}, React.createElement('div', { style: { width: '100%' } }, fieldNodes))
: null;
return React.createElement(Card, { key: title, size: 'small', style: { marginBottom: 16 }, title: title },
React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'stretch' } },
React.createElement('div', { style: { flexShrink: 0, display: 'flex', flexDirection: 'column' } },
React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, cap),
React.createElement(Image, {
function mergeLicViewStatus(a, b) {
var rank = { error: 0, warning: 1, default: 2, success: 3, processing: 2 };
var ra = rank[a] != null ? rank[a] : 9;
var rb = rank[b] != null ? rank[b] : 9;
return ra <= rb ? a : b;
}
function diffDaysFromAnchor(dateStr) {
if (!dateStr) return null;
var exp = new Date(dateStr);
var today = new Date(LICENSE_VIEW_ANCHOR_DATE);
exp.setHours(0, 0, 0, 0);
today.setHours(0, 0, 0, 0);
return Math.ceil((exp.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
}
function getLicViewBadgeStatus(key) {
var item = licenseViewBundle[key];
if (!item) return 'default';
if (key === 'hydrogenCard') {
return item.cardNo ? 'success' : 'default';
}
if (!item.photos || !item.photos.length) return 'default';
if (key === 'driverLicense') {
var dd = diffDaysFromAnchor(item.expireDate);
if (dd == null) return 'success';
if (dd <= 0) return 'error';
if (dd <= 90) return 'warning';
return 'success';
}
if (key === 'transportLicense') {
var fromDate = function (d) {
var days = diffDaysFromAnchor(d);
if (days == null) return 'success';
if (days <= 0) return 'error';
if (days <= 60) return 'warning';
return 'success';
};
return mergeLicViewStatus(fromDate(item.expireDate), fromDate(item.inspectValidUntil));
}
if (key === 'specialEquipDecal' || key === 'safetyValve' || key === 'pressureGauge') {
var nd = diffDaysFromAnchor(item.nextInspectDate);
if (nd == null) return 'success';
if (nd <= 0) return 'error';
if (nd <= 60) return 'warning';
return 'success';
}
return 'success';
}
function scrollToLicenseAnchor(key, setActive) {
setActive(key);
var el = document.getElementById('vd-lic-' + key);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function LicViewPhotoGrid(photos, photoLabel) {
var list = photos && photos.length ? photos : [];
if (!list.length) {
return React.createElement('div', { style: { flexShrink: 0 } },
React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, photoLabel || '证件照片'),
React.createElement(Image, {
width: 168,
height: 112,
style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' },
src: licensePhotoPlaceholder,
alt: '暂无影像',
preview: false
})
);
}
return React.createElement('div', { style: { flexShrink: 0 } },
React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } },
(photoLabel || '证件照片') + (list.length > 1 ? '' + list.length + '张)' : '')
),
React.createElement('div', { style: { display: 'flex', gap: 12, flexWrap: 'wrap' } },
list.map(function (url, idx) {
return React.createElement(Image, {
key: idx,
width: 168,
height: 112,
style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' },
src: imageSrc,
alt: title,
src: url,
alt: (photoLabel || '证照') + '-' + (idx + 1),
preview: true
})
),
right
});
})
)
);
}
/** 证照多图:每项 { label, src },如加氢证下登记证 + 使用标志 */
function CertSectionCardMulti(title, photoSlots, fieldNodes) {
var right = fieldNodes && fieldNodes.length
function LicViewCertCard(cfg) {
var fields = cfg.fields || [];
var right = fields.length
? React.createElement('div', {
style: {
flex: 1,
minWidth: 260,
display: 'flex',
alignItems: 'center',
alignSelf: 'stretch'
}
}, React.createElement('div', { style: { width: '100%' } }, fieldNodes))
style: { flex: 1, minWidth: 260, display: 'flex', alignItems: 'center', alignSelf: 'stretch' }
}, React.createElement('div', { style: { width: '100%' } },
fields.map(function (f, i) { return React.createElement('div', { key: i }, LicViewFieldRow(f.label, f.value)); })
))
: null;
var slots = photoSlots || [];
var photosRow = React.createElement('div', { style: { display: 'flex', gap: 16, flexWrap: 'wrap', flexShrink: 0, alignItems: 'flex-start' } },
slots.map(function (slot, idx) {
return React.createElement('div', { key: idx, style: { display: 'flex', flexDirection: 'column' } },
React.createElement('div', { style: { marginBottom: 8, color: 'rgba(0,0,0,0.45)', fontSize: 13 } }, slot.label || '证件照片'),
React.createElement(Image, {
width: 168,
height: 112,
style: { objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', background: '#fafafa' },
src: slot.src,
alt: (slot.label || title) + '',
preview: true
})
return React.createElement(Card, {
id: 'vd-lic-' + cfg.id,
size: 'small',
style: { marginBottom: 16, scrollMarginTop: 72 },
title: cfg.title
},
cfg.alertTop || null,
React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'stretch' } },
LicViewPhotoGrid(cfg.photos, cfg.photoLabel),
right
),
cfg.extraBottom || null
);
}
function fmtH2Balance(n) {
var num = Number(n);
if (!Number.isFinite(num)) return '—';
return '¥ ' + num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
var lic = licenseViewBundle;
var licNavActive = licenseActiveNav[0];
var setLicNavActive = licenseActiveNav[1];
var isShPlate = String(overview.plateNo || '').indexOf('沪') === 0;
var driverDays = diffDaysFromAnchor(lic.driverLicense.expireDate);
var transportExpDays = diffDaysFromAnchor(lic.transportLicense.expireDate);
var decalDays = diffDaysFromAnchor(lic.specialEquipDecal.nextInspectDate);
var licenseIndexBar = React.createElement(Card, {
size: 'small',
style: { marginBottom: 16, borderRadius: 12, border: '1px solid #e2e8f0' },
bodyStyle: { padding: '14px 16px' }
},
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 12 } },
React.createElement('span', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a' } }, '证照分类索引'),
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10, fontSize: 11, color: '#64748b' } },
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, React.createElement(Badge, { status: 'success' }), '正常'),
React.createElement(Tooltip, { title: '行驶证≤90天 / 道路运输证≤60天 / 特种设备标识≤60天 / 安全阀≤60天 / 压力表≤60天' },
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'help' } }, React.createElement(Badge, { status: 'warning' }), '临期')
),
React.createElement(Tooltip, { title: '对应证件检验/有效期已到期' },
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4, cursor: 'help' } }, React.createElement(Badge, { status: 'error' }), '已到期')
),
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } }, React.createElement(Badge, { status: 'default' }), '未上传')
)
),
React.createElement('div', {
className: 'vd-lic-nav-grid',
style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 8 },
role: 'navigation',
'aria-label': '证照索引'
},
CERT_NAV_ITEMS.map(function (nav) {
var active = licNavActive === nav.key;
return React.createElement('button', {
key: nav.key,
type: 'button',
onClick: function () { scrollToLicenseAnchor(nav.key, setLicNavActive); },
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 8,
padding: '8px 12px',
borderRadius: 8,
fontSize: 13,
fontWeight: active ? 600 : 500,
color: active ? '#065f46' : '#475569',
background: active ? '#ecfdf5' : '#f8fafc',
border: active ? '1px solid #a7f3d0' : '1px solid #f1f5f9',
cursor: 'pointer',
textAlign: 'left'
}
},
React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, nav.label),
React.createElement(Badge, { status: getLicViewBadgeStatus(nav.key) })
);
})
);
return React.createElement(Card, { key: title, size: 'small', style: { marginBottom: 16 }, title: title },
React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'stretch' } },
photosRow,
right
)
);
}
)
);
function fmtCycleMonth(m) {
var s = m == null ? '' : String(m).trim();
return s ? s + ' 月' : '—';
}
var cd = certificateDetail;
var licenseManagementTabContent = React.createElement('div', { style: { padding: '0 4px' } },
CertSectionCard('行驶证信息', cd.drivingLicense.img, [
CertFieldRow('注册日期', cd.drivingLicense.regDate),
CertFieldRow('强制报废日期', cd.drivingLicense.scrapDate),
CertFieldRow('行驶证审验有效期', cd.drivingLicense.inspectExpire)
], '行驶证'),
CertSectionCard('营运证信息', cd.operationPermit.img, [
CertFieldRow('营运证编号', cd.operationPermit.permitNo),
CertFieldRow('注册日期', cd.operationPermit.regDate),
CertFieldRow('审验有效期', cd.operationPermit.inspectExpire)
], '营运证'),
CertSectionCard('通行证信息', cd.passPermit.img, [
CertFieldRow('通行证编码', cd.passPermit.code),
CertFieldRow('通行区域', cd.passPermit.area)
], '通行证'),
CertSectionCard('登记证信息', cd.registrationCert.img, null, '登记证'),
CertSectionCardMulti('加氢证信息', cd.h2Permit.photos, [
CertFieldRow('氢气瓶厂家', cd.h2Permit.cylinderVendor),
CertFieldRow('氢气瓶检验日期', cd.h2Permit.cylinderInspectDate),
CertFieldRow('氢气瓶检验周期', fmtCycleMonth(cd.h2Permit.cylinderCycleMonth)),
CertFieldRow('氢气瓶检测有效期', cd.h2Permit.cylinderValidUntil)
]),
CertSectionCard('加氢卡信息', cd.h2Card.img, [
CertFieldRow('加氢卡对应加氢站', cd.h2Card.stationName)
], '加氢卡'),
CertSectionCard('安全阀信息', cd.safetyValve.img, [
CertFieldRow('检验日期', cd.safetyValve.inspectDate),
CertFieldRow('检验周期', fmtCycleMonth(cd.safetyValve.cycleMonth)),
CertFieldRow('安全阀检测有效期', cd.safetyValve.validUntil)
], '安全阀'),
CertSectionCard('压力表信息', cd.pressureGauge.img, [
CertFieldRow('检验日期', cd.pressureGauge.inspectDate),
CertFieldRow('检验周期', fmtCycleMonth(cd.pressureGauge.cycleMonth)),
CertFieldRow('压力阀检测有效期', cd.pressureGauge.valveInspectValidUntil)
], '压力表')
React.createElement('style', null, '@media (max-width:992px){.vd-lic-nav-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}'),
licenseIndexBar,
LicViewCertCard({
id: 'driverLicense',
title: '行驶证',
photoLabel: '行驶证照片',
photos: lic.driverLicense.photos,
alertTop: driverDays != null && driverDays <= 90
? React.createElement(Alert, {
type: driverDays <= 0 ? 'error' : driverDays <= 30 ? 'error' : 'warning',
showIcon: true,
style: { marginBottom: 16, borderRadius: 8 },
message: '行驶证检验临期提醒',
description: driverDays <= 0
? '检验有效期已到期,请尽快安排年检。'
: ('距离检验有效期至还剩 ' + driverDays + ' 天(系统提前 90 天感知)。')
})
: null,
fields: [
{ label: '注册日期', value: lic.driverLicense.regDate },
{ label: '发证日期', value: lic.driverLicense.issueDate },
{ label: '强制报废日期', value: lic.driverLicense.scrapDate },
{ label: '检验有效期至', value: lic.driverLicense.expireDate }
],
extraBottom: isShPlate && lic.driverLicense.shNextEvaluation
? React.createElement('div', { style: { marginTop: 16, padding: 12, background: '#f5f3ff', borderRadius: 8, border: '1px solid #ddd6fe' } },
LicViewFieldRow('下次等级评定时间', lic.driverLicense.shNextEvaluation)
)
: null
}),
LicViewCertCard({
id: 'transportLicense',
title: '道路运输证',
photoLabel: '道路运输证照片',
photos: lic.transportLicense.photos,
alertTop: transportExpDays != null && transportExpDays <= 60
? React.createElement(Alert, {
type: transportExpDays <= 0 ? 'error' : transportExpDays <= 15 ? 'error' : 'warning',
showIcon: true,
style: { marginBottom: 16, borderRadius: 8 },
message: '道路运输证临期提醒',
description: '证件有效期距今日 ' + transportExpDays + ' 天(系统提前 60 天列入年审任务)。'
})
: null,
fields: [
{ label: '经营许可证号', value: lic.transportLicense.licenseNo },
{ label: '核发时间', value: lic.transportLicense.issueDate },
{ label: '证件有效期', value: lic.transportLicense.expireDate },
{ label: '审验有效期', value: lic.transportLicense.inspectValidUntil }
]
}),
LicViewCertCard({
id: 'registrationCert',
title: '登记证',
photoLabel: '登记证照片',
photos: lic.registrationCert.photos,
fields: []
}),
LicViewCertCard({
id: 'specialEquipCert',
title: '特种设备使用登记证',
photoLabel: '使用登记证照片',
photos: lic.specialEquipCert.photos,
fields: []
}),
LicViewCertCard({
id: 'specialEquipDecal',
title: '特种设备使用标识',
photoLabel: '使用安全标识照片',
photos: lic.specialEquipDecal.photos,
alertTop: decalDays != null && decalDays <= 60
? React.createElement(Alert, {
type: decalDays <= 0 || decalDays <= 15 ? 'error' : 'warning',
showIcon: true,
style: { marginBottom: 16, borderRadius: 8 },
message: '特种设备使用标识检验提醒',
description: decalDays <= 0
? ('下次检验日期已逾期 ' + Math.abs(decalDays) + ' 天。')
: ('距离下次检验日期还剩 ' + decalDays + ' 天(提前 60 天感知)。')
})
: null,
fields: [{ label: '下次检验日期', value: lic.specialEquipDecal.nextInspectDate }]
}),
React.createElement(Card, {
id: 'vd-lic-hydrogenCard',
size: 'small',
style: { marginBottom: 16, scrollMarginTop: 72 },
title: '加氢卡'
},
React.createElement('div', { style: { display: 'flex', gap: 24, flexWrap: 'wrap' } },
React.createElement('div', {
style: {
flex: '0 0 280px',
maxWidth: 320,
padding: 20,
borderRadius: 12,
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
color: '#e2e8f0',
boxShadow: '0 8px 24px rgba(15,23,42,0.15)'
}
},
React.createElement('div', { style: { fontSize: 11, color: '#94a3b8', marginBottom: 8 } }, lic.hydrogenCard.cardType || '加氢卡'),
React.createElement('div', { style: { fontSize: 18, fontWeight: 700, letterSpacing: 2, marginBottom: 16, fontFamily: 'monospace' } },
lic.hydrogenCard.cardNo || '—'
),
React.createElement('div', { style: { fontSize: 22, fontWeight: 800, color: '#34d399' } }, fmtH2Balance(lic.hydrogenCard.balance))
),
React.createElement('div', { style: { flex: 1, minWidth: 240 } },
LicViewFieldRow('加氢卡号', lic.hydrogenCard.cardNo),
LicViewFieldRow('卡类型', lic.hydrogenCard.cardType),
LicViewFieldRow('实时余额', fmtH2Balance(lic.hydrogenCard.balance)),
LicViewFieldRow('配发时间', lic.hydrogenCard.issueDate),
LicViewFieldRow('配发经办人', lic.hydrogenCard.issueUser)
)
)
),
LicViewCertCard({
id: 'safetyValve',
title: '安全阀',
photoLabel: '安全阀校验报告照片',
photos: lic.safetyValve.photos,
fields: [
{ label: '检验日期', value: lic.safetyValve.inspectDate },
{ label: '下次检验日期', value: lic.safetyValve.nextInspectDate }
]
}),
LicViewCertCard({
id: 'pressureGauge',
title: '压力表',
photoLabel: '压力表校验报告照片',
photos: lic.pressureGauge.photos,
fields: [
{ label: '检验日期', value: lic.pressureGauge.inspectDate },
{ label: '下次检验日期', value: lic.pressureGauge.nextInspectDate }
]
})
);
var tabItems = [

View File

@@ -18,9 +18,16 @@ const Component = function () {
var Upload = antd.Upload;
var Card = antd.Card;
var Tabs = antd.Tabs;
var Form = antd.Form;
var Row = antd.Row;
var Col = antd.Col;
var message = antd.message;
var App = antd.App;
// 联调后对接权限中心;原型默认 admin 可编辑,改为 'staff' 可验证无权限隐藏
var CURRENT_USER = { id: 'u_admin', name: '系统管理员', role: 'admin' };
var isAdmin = CURRENT_USER.role === 'admin';
// 筛选项状态
var _region = useState([]);
var _vehicleType = useState(undefined);
@@ -49,6 +56,9 @@ const Component = function () {
var _requirementModalVisible = useState(false);
var _inspectImportModalVisible = useState(false);
var _inspectImportResult = useState(null);
var _editModalVisible = useState(false);
var _editRecord = useState(null);
var _editForm = useState({});
// 省-市 地区数据(示例)
var regionOptions = [
@@ -81,6 +91,49 @@ const Component = function () {
{ label: '第三方融资租赁有限公司', value: '第三方融资租赁有限公司' },
{ label: '无', value: '-' }
];
var parkingOptions = [
{ label: '天河停车场', value: '天河停车场' },
{ label: '黄埔停车场', value: '黄埔停车场' },
{ label: '朝阳停车场', value: '朝阳停车场' },
{ label: '福田停车场', value: '福田停车场' },
{ label: '浦东停车场', value: '浦东停车场' },
{ label: '南山停车场', value: '南山停车场' },
{ label: '番禺停车场', value: '番禺停车场' },
{ label: '大兴停车场', value: '大兴停车场' },
{ label: '龙岗停车场', value: '龙岗停车场' },
{ label: '白云停车场', value: '白云停车场' },
{ label: '西城停车场', value: '西城停车场' },
{ label: '虹口停车场', value: '虹口停车场' },
{ label: '昌平停车场', value: '昌平停车场' },
{ label: '-', value: '-' }
];
var operateStatusOptions = [
{ label: '租赁', value: '租赁' },
{ label: '自营', value: '自营' },
{ label: '可运营', value: '可运营' },
{ label: '待运营', value: '待运营' },
{ label: '退出运营', value: '退出运营' }
];
var vehicleStatusOptions = [
{ label: '待验车', value: '待验车' },
{ label: '未备车', value: '未备车' },
{ label: '已备车', value: '已备车' },
{ label: '待交车', value: '待交车' },
{ label: '已交车', value: '已交车' },
{ label: '待还车', value: '待还车' },
{ label: '销售中', value: '销售中' },
{ label: '替换中', value: '替换中' },
{ label: '调拨中', value: '调拨中' },
{ label: '异动中', value: '异动中' },
{ label: '三方退租中', value: '三方退租中' },
{ label: '无', value: '无' }
];
var yearOptions = (function () {
var y = new Date().getFullYear();
var opts = [];
for (var i = y; i >= y - 12; i--) opts.push({ label: String(i), value: String(i) });
return opts;
})();
// 表格数据(模拟 20 条)— 状态字段按《车辆状态》脑图2026-03
// 运营状态:租赁、自营、可运营、待运营、退出运营(原「库存」并入「可运营」)
@@ -111,13 +164,16 @@ const Component = function () {
{ id: '20', region: '北京市/北京市', vin: 'LSJA24U70PS999000', plateNo: '京H88888', vehicleNo: '-', vehicleType: '小型轿车', brand: '蔚来', model: 'ET5', color: '白色', parking: '昌平停车场', customer: '客户D', department: '华北区', manager: '孙七', operateStatus: '退出运营', vehicleStatus: '无', outStatus: '报废出库', licenseStatus: '无', insuranceStatus: '正常', ownership: '某某科技有限公司', operateCompany: '羚牛运营(上海)', vehicleSource: '外租', leaseCompany: '-', onlineStatus: '离线', year: '2022', mileage: '15600.00', purchaseDate: '2022-06-20', regDate: '2022-07-10', inspectExpire: '2024-07', lastDeliveryTime: '2024-01-10', lastDeliveryMile: '15300.00', lastReturnTime: '2024-01-28', lastReturnMile: '15600.00', scrapDate: '2037-07-31', contractNo: '-', location: '北京市昌平区回龙观西大街100号', gpsTime: '2024-02-10 12:30' }
];
var _tableData = useState(rawData);
var dataSource = useMemo(function () {
var plate = _plateFilter[0];
if (!plate || plate.trim() === '') return rawData;
return rawData.filter(function (row) {
var list = _tableData[0];
if (!plate || plate.trim() === '') return list;
return list.filter(function (row) {
return row.plateNo && row.plateNo.indexOf(plate) !== -1;
});
}, [rawData, _plateFilter[0]]);
}, [_tableData[0], _plateFilter[0]]);
var onPlateFilterChange = useCallback(function (e) {
_plateFilter[1](e.target.value);
@@ -268,6 +324,92 @@ const Component = function () {
closeConfirmModal();
}, []);
var openEditModal = useCallback(function (record) {
if (!isAdmin) {
message.warning('仅 Admin 可编辑车辆信息');
return;
}
_editRecord[1](record);
_editForm[1]({
parking: record.parking === '-' ? '-' : (record.parking || undefined),
operateStatus: record.operateStatus || undefined,
vehicleStatus: record.vehicleStatus || undefined,
ownership: record.ownership && record.ownership !== '-' ? record.ownership : '',
operateCompany: record.operateCompany || undefined,
vehicleSource: record.vehicleSource || undefined,
leaseCompany: record.leaseCompany && record.leaseCompany !== '-' ? record.leaseCompany : '',
year: record.year || undefined,
purchaseDate: record.purchaseDate || ''
});
_editModalVisible[1](true);
}, []);
var closeEditModal = useCallback(function () {
_editModalVisible[1](false);
_editRecord[1](null);
_editForm[1]({});
}, []);
var onEditFormChange = useCallback(function (field, value) {
_editForm[1](function (prev) {
var next = Object.assign({}, prev);
next[field] = value;
return next;
});
}, []);
var saveEdit = useCallback(function () {
if (!isAdmin) {
message.warning('仅 Admin 可保存');
return;
}
var record = _editRecord[0];
var form = _editForm[0];
if (!record) return;
if (!form.operateStatus || !form.vehicleStatus) {
message.warning('请选择运营状态与车辆状态');
return;
}
_tableData[1](function (prev) {
return prev.map(function (row) {
if (row.id !== record.id) return row;
var ownershipVal = (form.ownership || '').trim();
var leaseVal = (form.leaseCompany || '').trim();
return Object.assign({}, row, {
parking: form.parking || '-',
operateStatus: form.operateStatus,
vehicleStatus: form.vehicleStatus,
ownership: ownershipVal || '-',
operateCompany: form.operateCompany || row.operateCompany,
vehicleSource: form.vehicleSource || row.vehicleSource,
leaseCompany: leaseVal || '-',
year: form.year || row.year,
purchaseDate: form.purchaseDate || row.purchaseDate
});
});
});
if (_detailRecord[0] && _detailRecord[0].id === record.id) {
_detailRecord[1](function (prev) {
if (!prev || prev.id !== record.id) return prev;
var ownershipVal = (form.ownership || '').trim();
var leaseVal = (form.leaseCompany || '').trim();
return Object.assign({}, prev, {
parking: form.parking || '-',
operateStatus: form.operateStatus,
vehicleStatus: form.vehicleStatus,
ownership: ownershipVal || '-',
operateCompany: form.operateCompany || prev.operateCompany,
vehicleSource: form.vehicleSource || prev.vehicleSource,
leaseCompany: leaseVal || '-',
year: form.year || prev.year,
purchaseDate: form.purchaseDate || prev.purchaseDate
});
});
}
message.success('车辆信息已保存(原型演示)');
closeEditModal();
}, []);
// 状态类字段:枚举为「无」时界面显示为「-」
var formatStatusDisplay = function (val) {
if (val === '无') return '-';
@@ -326,15 +468,24 @@ const Component = function () {
{
title: '操作',
key: 'action',
width: 140,
width: isAdmin ? 180 : 140,
fixed: 'right',
render: function (_, record) {
return React.createElement(Space, null,
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goDetail(record); } }, '查看'),
React.createElement(Dropdown, { menu: { items: getMoreMenuItems(record) }, trigger: ['click'] },
React.createElement(Button, { type: 'link', size: 'small' }, '更多')
)
);
var actions = [
React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { goDetail(record); } }, '查看')
];
if (isAdmin) {
actions.push(React.createElement(Button, {
key: 'edit',
type: 'link',
size: 'small',
onClick: function () { openEditModal(record); }
}, '编辑'));
}
actions.push(React.createElement(Dropdown, { key: 'more', menu: { items: getMoreMenuItems(record) }, trigger: ['click'] },
React.createElement(Button, { type: 'link', size: 'small' }, '更多')
));
return React.createElement(Space, { size: 0 }, actions);
}
}
];
@@ -680,6 +831,131 @@ const Component = function () {
footer: React.createElement(Button, { onClick: function () { _requirementModalVisible[1](false); } }, '关闭')
}, React.createElement('div', { style: { maxHeight: 560, overflow: 'auto', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: 'rgba(0,0,0,0.85)' } }, requirementText)),
React.createElement(Modal, {
title: '编辑车辆信息',
open: _editModalVisible[0],
onCancel: closeEditModal,
width: 720,
destroyOnClose: true,
footer: [
React.createElement(Button, { key: 'cancel', onClick: closeEditModal }, '取消'),
React.createElement(Button, { key: 'save', type: 'primary', onClick: saveEdit }, '保存')
]
}, _editRecord[0] ? React.createElement('div', null,
React.createElement('div', { style: { marginBottom: 16, padding: '8px 12px', background: '#f5f5f5', borderRadius: 4, fontSize: 13, color: 'rgba(0,0,0,0.65)' } },
'车牌号:', React.createElement('span', { style: { fontWeight: 600, color: 'rgba(0,0,0,0.85)' } }, _editRecord[0].plateNo || '-'),
' VIN', React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)' } }, _editRecord[0].vin || '-')
),
React.createElement(Form, { layout: 'vertical' },
React.createElement(Row, { gutter: 16 },
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '车辆归属停车场', required: true },
React.createElement(Select, {
placeholder: '请选择停车场',
style: { width: '100%' },
options: parkingOptions,
value: _editForm[0].parking,
onChange: function (v) { onEditFormChange('parking', v); },
allowClear: true,
showSearch: true,
filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; }
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '运营状态', required: true },
React.createElement(Select, {
placeholder: '请选择运营状态',
style: { width: '100%' },
options: operateStatusOptions,
value: _editForm[0].operateStatus,
onChange: function (v) { onEditFormChange('operateStatus', v); }
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '车辆状态', required: true },
React.createElement(Select, {
placeholder: '请选择车辆状态',
style: { width: '100%' },
options: vehicleStatusOptions,
value: _editForm[0].vehicleStatus,
onChange: function (v) { onEditFormChange('vehicleStatus', v); }
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '登记所有权' },
React.createElement(Input, {
placeholder: '请输入登记所有权',
value: _editForm[0].ownership || '',
onChange: function (e) { onEditFormChange('ownership', e.target.value); },
allowClear: true
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '运营公司' },
React.createElement(Select, {
placeholder: '请选择运营公司',
style: { width: '100%' },
options: operateCompanyOptions,
value: _editForm[0].operateCompany,
onChange: function (v) { onEditFormChange('operateCompany', v); },
allowClear: true,
showSearch: true,
filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; }
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '车辆来源' },
React.createElement(Select, {
placeholder: '请选择车辆来源',
style: { width: '100%' },
options: vehicleSourceOptions,
value: _editForm[0].vehicleSource,
onChange: function (v) { onEditFormChange('vehicleSource', v); },
allowClear: true
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '租赁公司' },
React.createElement(Input, {
placeholder: '请输入租赁公司,无则留空',
value: _editForm[0].leaseCompany || '',
onChange: function (e) { onEditFormChange('leaseCompany', e.target.value); },
allowClear: true
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '出厂年份' },
React.createElement(Select, {
placeholder: '请选择出厂年份',
style: { width: '100%' },
options: yearOptions,
value: _editForm[0].year,
onChange: function (v) { onEditFormChange('year', v); },
allowClear: true
})
)
),
React.createElement(Col, { span: 12 },
React.createElement(Form.Item, { label: '采购入库时间', extra: '格式YYYY-MM-DD' },
React.createElement(Input, {
placeholder: '请输入采购入库时间',
value: _editForm[0].purchaseDate || '',
onChange: function (e) { onEditFormChange('purchaseDate', e.target.value); },
allowClear: true
})
)
)
)
)
) : null),
React.createElement(Modal, {
title: '批量导入等评时间',
open: _inspectImportModalVisible[0],

View File

@@ -48,22 +48,36 @@ const Component = function () {
// 交车明细列表(序号、品牌、型号、车牌号可编辑、实际交车日期、交车人、交车状态、操作)
var detailListState = useState([
{ key: 1, seq: 1, brand: '东风', model: 'DFH1180', plateNo: '京A12345', actualDate: '2025-02-28', deliveryPerson: '张三', status: '已签章' },
{ key: 2, seq: 2, brand: '东风', model: 'DFH1250', plateNo: '京C11111', actualDate: '2025-02-28', deliveryPerson: '张三', status: '已签章' },
{ key: 3, seq: 3, brand: '福田', model: 'BJ1180', plateNo: '浙A10001', actualDate: '2025-03-01', deliveryPerson: '李四', status: '签章' },
{ key: 4, seq: 4, brand: '福田', model: 'BJ1250', plateNo: '浙F80088', actualDate: '2025-03-01', deliveryPerson: '李四', status: '签章' },
{ key: 5, seq: 5, brand: '重汽', model: 'HOWO-T5G', plateNo: '沪A30003', actualDate: '2025-03-02', deliveryPerson: '王五', status: '已签章' },
{ key: 6, seq: 6, brand: '陕汽', model: '德龙X3000', plateNo: '京D22222', actualDate: '2025-03-02', deliveryPerson: '王五', status: '已完成' },
{ key: 7, seq: 7, brand: '解放', model: 'J6P', plateNo: '', actualDate: '', deliveryPerson: '', status: '待提交' },
{ key: 8, seq: 8, brand: '欧曼', model: 'EST-A', plateNo: '', actualDate: '', deliveryPerson: '', status: '待提交' },
{ key: 9, seq: 9, brand: '江淮', model: '格尔发K7', plateNo: '', actualDate: '', deliveryPerson: '', status: '待提交' },
{ key: 10, seq: 10, brand: '红岩', model: '杰狮C6', plateNo: '', actualDate: '', deliveryPerson: '', status: '待提交' }
{ key: 1, seq: 1, brand: '东风', model: 'DFH1180', plateNo: '京A12345', actualDate: '2025-02-28', deliveryPerson: '张三', status: '客户已签章' },
{ key: 2, seq: 2, brand: '东风', model: 'DFH1250', plateNo: '京C11111', actualDate: '2025-02-28', deliveryPerson: '张三', status: '客户已签章' },
{ key: 3, seq: 3, brand: '福田', model: 'BJ1180', plateNo: '浙A10001', actualDate: '2025-03-01', deliveryPerson: '李四', status: '待客户签章' },
{ key: 4, seq: 4, brand: '福田', model: 'BJ1250', plateNo: '浙F80088', actualDate: '2025-03-01', deliveryPerson: '李四', status: '待客户签章' },
{ key: 5, seq: 5, brand: '重汽', model: 'HOWO-T5G', plateNo: '沪A30003', actualDate: '2025-03-02', deliveryPerson: '王五', status: '已保存' },
{ key: 6, seq: 6, brand: '陕汽', model: '德龙X3000', plateNo: '京D22222', actualDate: '2025-03-02', deliveryPerson: '王五', status: '已保存' },
{ key: 7, seq: 7, brand: '解放', model: 'J6P', plateNo: '', actualDate: '', deliveryPerson: '', status: '未开始' },
{ key: 8, seq: 8, brand: '欧曼', model: 'EST-A', plateNo: '', actualDate: '', deliveryPerson: '', status: '未开始' },
{ key: 9, seq: 9, brand: '江淮', model: '格尔发K7', plateNo: '', actualDate: '', deliveryPerson: '', status: '未开始' },
{ key: 10, seq: 10, brand: '红岩', model: '杰狮C6', plateNo: '', actualDate: '', deliveryPerson: '', status: '未开始' }
]);
var detailList = detailListState[0];
var setDetailList = detailListState[1];
var allSigned = useMemo(function () {
return detailList.length > 0 && detailList.every(function (row) { return row.status === '已签章'; });
function isDetailHistoryStatus(status) {
return status === '客户已签章' || status === '已签章';
}
function canEditDetailRow(record) {
var s = record.status;
return s === '未开始' || s === '已保存';
}
function hasPlateSelected(record) {
var p = record.plateNo;
return p && String(p).trim() !== '' && p !== '-';
}
var allCustomerSigned = useMemo(function () {
return detailList.length > 0 && detailList.every(function (row) { return isDetailHistoryStatus(row.status); });
}, [detailList]);
var updateDetailRow = useCallback(function (index, field, value) {
@@ -76,10 +90,10 @@ const Component = function () {
}, []);
var handleSubmit = useCallback(function () {
if (!allSigned) return;
if (!allCustomerSigned) return;
message.success('提交成功(原型)');
if (typeof window !== 'undefined' && window.history) window.history.back();
}, [allSigned]);
}, [allCustomerSigned]);
var handleCancel = useCallback(function () {
if (typeof window !== 'undefined' && window.history) window.history.back();
@@ -140,12 +154,11 @@ const Component = function () {
dataIndex: 'plateNo',
key: 'plateNo',
width: 120,
render: function (v, record, index) {
var isDelivered = record.status === '已签章' || record.status === '已完成';
if (isDelivered) {
render: function (v, record) {
if (hasPlateSelected(record)) {
return React.createElement(Input, { value: v || '', disabled: true, style: { width: '100%', background: '#f5f5f5' } });
}
return React.createElement(Input, { value: '-', disabled: true, style: { width: '100%', background: '#f5f5f5' } });
return React.createElement(Input, { value: '车牌待选', disabled: true, style: { width: '100%', background: '#f5f5f5', color: '#d48806' } });
}
},
{
@@ -162,7 +175,7 @@ const Component = function () {
width: 90,
render: function (v) { return React.createElement(Input, { value: v || '', disabled: true, style: { width: '100%', background: '#f5f5f5' } }); }
},
{ title: '交车状态', dataIndex: 'status', key: 'status', width: 90, render: function (v) { return v || '-'; } },
{ title: '交车状态', dataIndex: 'status', key: 'status', width: 108, render: function (v) { return v || '-'; } },
{
title: '操作',
key: 'action',
@@ -171,8 +184,8 @@ const Component = function () {
render: function (_, record, index) {
return React.createElement(React.Fragment, null,
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleViewDetail(record); } }, '查看'),
record.status === '待提交' ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleEditDetail(record, index); } }, '编辑') : null,
record.status === '已签章' ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleDownloadSign(record); } }, '下载签章文件') : null
canEditDetailRow(record) ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleEditDetail(record, index); } }, '编辑') : null,
isDetailHistoryStatus(record.status) ? React.createElement(Button, { type: 'link', size: 'small', onClick: function () { handleDownloadSign(record); } }, '下载签章文件') : null
);
}
}
@@ -214,7 +227,7 @@ const Component = function () {
React.createElement('div', { style: { height: 60 } }),
React.createElement('div', { style: styles.footer },
React.createElement(Button, { type: 'primary', disabled: !allSigned, onClick: handleSubmit }, '提交'),
React.createElement(Button, { type: 'primary', disabled: !allCustomerSigned, onClick: handleSubmit }, '提交'),
React.createElement(Button, { onClick: handleCancel }, '取消')
),

View File

@@ -0,0 +1,503 @@
// 交车管理 - 列表内编辑抽屉(参照 交车管理-交车单-编辑.jsx
// 使用方式window.DeliveryEditDrawer(props)
function DeliveryEditDrawer(props) {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var useRef = React.useRef;
var useEffect = React.useEffect;
var open = props.open;
var record = props.record;
var onClose = props.onClose;
var onSave = props.onSave;
var onSubmit = props.onSubmit;
var antd = window.antd;
var Drawer = antd.Drawer;
var Button = antd.Button;
var Input = antd.Input;
var Select = antd.Select;
var Switch = antd.Switch;
var Modal = antd.Modal;
var Table = antd.Table;
var Tag = antd.Tag;
var message = antd.message;
function RequiredLabel(text) {
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } },
React.createElement('span', { style: { color: '#ef4444', fontWeight: 600 } }, '*'),
React.createElement('span', null, text)
);
}
function isEmpty(v) {
return v === null || v === undefined || String(v).trim() === '';
}
function filterOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function fileToDataUrl(file, cb) {
try {
var reader = new FileReader();
reader.onload = function (e) { cb(null, (e && e.target && e.target.result) || ''); };
reader.onerror = function () { cb(new Error('read error')); };
reader.readAsDataURL(file);
} catch (e) { cb(e); }
}
var reserveVehicles = useMemo(function () {
return [
{ plateNo: '京A12345', vehicleType: '牵引车', brand: '东风', model: 'DFH1180', vin: 'LJNAU1A2XK1234567', hasAd: true, adPhoto: [{ uid: 'ad1', name: '广告照片.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Ad' }], bigWordPhoto: [{ uid: 'bw1', name: '放大字.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=BigWord' }], hasTailboard: true },
{ plateNo: '浙F80088', vehicleType: '厢式车', brand: '福田', model: 'BJ1180', vin: 'LJNAU1A2XK7654321', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false },
{ plateNo: '沪A30003', vehicleType: '厢式车', brand: '重汽', model: 'HOWO-T5G', vin: 'LJNAU1A2XK9999000', hasAd: true, adPhoto: [{ uid: 'ad2', name: '广告2.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Ad2' }], bigWordPhoto: [{ uid: 'bw2', name: '放大字2.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=BW2' }], hasTailboard: true },
{ plateNo: '粤AGP4598', vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB9RR223402', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false }
];
}, []);
var plateOptions = useMemo(function () {
return reserveVehicles.map(function (v) { return { value: v.plateNo, label: v.plateNo }; });
}, [reserveVehicles]);
var vehicleByPlate = useMemo(function () {
var map = {};
reserveVehicles.forEach(function (v) { map[v.plateNo] = v; });
return map;
}, [reserveVehicles]);
function buildInitialForm(rec) {
var plate = (rec && rec.plateNo && String(rec.plateNo).trim()) || undefined;
var veh = plate ? vehicleByPlate[plate] : null;
if (!veh && rec && rec.brand) {
veh = { plateNo: plate, vehicleType: rec.vehicleType || '', brand: rec.brand || '', model: rec.model || '', vin: rec.vin || '', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false };
}
return {
plateNo: plate,
vehicleType: (veh && veh.vehicleType) || (rec && rec.vehicleType) || '',
brand: (veh && veh.brand) || (rec && rec.brand) || '',
model: (veh && veh.model) || (rec && rec.model) || '',
vin: (veh && veh.vin) || (rec && rec.vin) || '',
hasAd: !!(veh && veh.hasAd),
adPhoto: (veh && veh.adPhoto) || [],
bigWordPhoto: (veh && veh.bigWordPhoto) || [],
hasTailboard: !!(veh && veh.hasTailboard),
spareTirePhoto: [],
spareTireDepth: '',
trainingRecognized: false,
driverLicenses: [],
mileageKm: rec && rec.deliveryMileage != null ? String(rec.deliveryMileage) : '',
batteryPct: rec && rec.deliveryElec != null ? String(rec.deliveryElec) : '',
hydrogenAmount: rec && rec.deliveryH2 != null ? String(rec.deliveryH2) : '',
hydrogenUnit: (rec && rec.deliveryH2Unit === 'MPa') ? 'MPa' : '%',
serviceFee: ''
};
}
var formState = useState(buildInitialForm(null));
var form = formState[0];
var setForm = formState[1];
var activeSectionState = useState('basic');
var activeSection = activeSectionState[0];
var setActiveSection = activeSectionState[1];
var submittingState = useState(false);
var submitting = submittingState[0];
var setSubmitting = submittingState[1];
var previewState = useState({ open: false, url: '', title: '' });
var ocrModalState = useState({ open: false, photoUrl: '', depth: '6.50' });
var inspectionOpenState = useState(false);
var trainingInputRef = useRef(null);
useEffect(function () {
if (open && record) {
setForm(buildInitialForm(record));
setActiveSection('basic');
setSubmitting(false);
}
}, [open, record && record.id]);
function updateForm(patch) {
setForm(function (p) { return Object.assign({}, p, patch); });
}
function handlePlateChange(v) {
var veh = vehicleByPlate[v];
updateForm({
plateNo: v,
vehicleType: (veh && veh.vehicleType) || '',
brand: (veh && veh.brand) || '',
model: (veh && veh.model) || '',
vin: (veh && veh.vin) || '',
hasAd: !!(veh && veh.hasAd),
adPhoto: (veh && (veh.adPhoto || [])) || [],
bigWordPhoto: (veh && (veh.bigWordPhoto || [])) || [],
hasTailboard: !!(veh && veh.hasTailboard),
spareTirePhoto: [],
spareTireDepth: '',
trainingRecognized: false,
driverLicenses: []
});
}
function makeThumb(url, onPreview, onRemove) {
return React.createElement('div', { style: { width: 72, height: 72, borderRadius: 8, border: '1px solid #e2e8f0', overflow: 'hidden', position: 'relative', background: '#f8fafc' } },
React.createElement('img', { src: url, style: { width: '100%', height: '100%', objectFit: 'cover', cursor: 'pointer' }, onClick: onPreview }),
onRemove ? React.createElement('button', {
type: 'button',
'aria-label': '删除图片',
style: { position: 'absolute', right: 4, top: 4, width: 22, height: 22, borderRadius: 999, border: 'none', background: 'rgba(15,23,42,.65)', color: '#fff', cursor: 'pointer', fontSize: 12, lineHeight: '22px', padding: 0 },
onClick: function (e) { e.stopPropagation(); onRemove(); }
}, '×') : null
);
}
function UploadBox(uploadProps) {
var label = uploadProps.label;
var value = uploadProps.value || [];
var max = uploadProps.max || 1;
var onChange = uploadProps.onChange;
function handlePick(e) {
var f = e && e.target && e.target.files && e.target.files[0];
if (!f) return;
fileToDataUrl(f, function (err, url) {
if (err) { message.error('上传失败'); return; }
var next = value.slice();
next.push({ uid: String(Date.now()), name: f.name || 'image', url: url });
if (next.length > max) next = next.slice(next.length - max);
onChange && onChange(next);
});
e.target.value = '';
}
return React.createElement('div', null,
label ? React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 8, fontWeight: 500 } }, label) : null,
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' } },
value.map(function (f) {
return React.createElement('div', { key: f.uid },
makeThumb(f.url, function () { previewState[1]({ open: true, url: f.url, title: f.name }); }, function () { onChange && onChange(value.filter(function (x) { return x.uid !== f.uid; })); })
);
}),
value.length >= max ? null : React.createElement('label', { style: { width: 72, height: 72, borderRadius: 8, border: '1px dashed #cbd5e1', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#64748b', cursor: 'pointer', fontSize: 13, background: '#fff', transition: 'border-color .2s' } },
React.createElement('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: handlePick }),
'上传'
)
)
);
}
function FormItem(itemProps) {
return React.createElement('div', { style: { marginBottom: 16, minWidth: 0 } },
React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 6, fontWeight: 500 } },
itemProps.required ? RequiredLabel(itemProps.label) : itemProps.label
),
itemProps.children
);
}
var inspectionListState = useState([{ key: 'ins-1', category: '车灯', item: '大灯', checked: true, treadDepth: '', remark: '' }]);
var inspectionList = inspectionListState[0];
var sectionNav = [
{ key: 'basic', label: '车辆信息' },
{ key: 'equip', label: '设备证照' },
{ key: 'metrics', label: '交车数据' },
{ key: 'photos', label: '交车照片' }
];
function scrollToSection(key) {
setActiveSection(key);
var el = document.getElementById('dv-edit-section-' + key);
if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function validateSubmit() {
if (isEmpty(form.plateNo)) return '请选择车牌号';
if (form.hasAd) {
if (!form.adPhoto.length) return '请上传广告照片';
if (!form.bigWordPhoto.length) return '请上传放大字照片';
}
if (!form.spareTirePhoto.length) return '请上传备胎照片';
if (isEmpty(form.spareTireDepth)) return '请填写备胎胎纹深度';
if (!form.trainingRecognized) return '请上传验车码并完成识别';
if (isEmpty(form.mileageKm)) return '请填写交车里程';
if (isEmpty(form.batteryPct)) return '请填写交车电量';
if (isEmpty(form.hydrogenAmount)) return '请填写交车氢量';
return '';
}
function buildPatchFromForm() {
return {
plateNo: form.plateNo || '',
vehicleType: form.vehicleType,
brand: form.brand,
model: form.model,
vin: form.vin,
deliveryMileage: form.mileageKm === '' ? null : Number(form.mileageKm),
deliveryElec: form.batteryPct === '' ? null : Number(form.batteryPct),
deliveryH2: form.hydrogenAmount === '' ? null : Number(form.hydrogenAmount),
deliveryH2Unit: form.hydrogenUnit,
deliveryStatus: '已保存'
};
}
function handleSaveClick() {
onSave && onSave(buildPatchFromForm());
message.success('已保存');
}
function handleSubmitClick() {
var err = validateSubmit();
if (err) { message.error(err); return; }
Modal.confirm({
title: '确认交车',
content: '请确认信息填写无误,点击确认完成该车辆交车。',
okText: '确认',
cancelText: '取消',
onOk: function () {
setSubmitting(true);
setTimeout(function () {
setSubmitting(false);
onSubmit && onSubmit(buildPatchFromForm());
message.success('交车成功');
onClose && onClose();
}, 400);
}
});
}
function handleTrainingPick(e) {
var f = e && e.target && e.target.files && e.target.files[0];
if (!f) return;
setTimeout(function () {
updateForm({
trainingRecognized: true,
driverLicenses: [
{ uid: 'id1', name: '身份证正面', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=ID-F' },
{ uid: 'id2', name: '身份证反面', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=ID-B' },
{ uid: 'dl', name: '驾驶证', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=DL' },
{ uid: 'qc', name: '从业资格证', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=QC' }
]
});
message.success('验车码识别成功');
}, 500);
e.target.value = '';
}
if (!open) return null;
var drawerTitle = React.createElement('div', { style: { paddingRight: 24 } },
React.createElement('div', { style: { fontSize: 16, fontWeight: 700, color: '#0f172a', lineHeight: 1.4 } }, '编辑交车单'),
record ? React.createElement('div', { style: { marginTop: 6, fontSize: 13, color: '#64748b', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } },
React.createElement(Tag, { style: { margin: 0, border: 'none', background: '#eff6ff', color: '#2563eb', fontWeight: 600 } }, record.customerName || '-'),
React.createElement('span', null, record.contractCode || '-'),
React.createElement(Tag, { style: { margin: 0, border: 'none', background: '#f1f5f9', color: '#475569' } }, record.deliveryStatus || '未开始')
) : null
);
var summaryCard = record ? React.createElement('div', { style: { marginBottom: 16, padding: '12px 14px', borderRadius: 10, background: 'linear-gradient(135deg,#f8fafc 0%,#fff 100%)', border: '1px solid #e2e8f0', display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '8px 16px', fontSize: 13 } },
React.createElement('div', null, React.createElement('span', { style: { color: '#94a3b8' } }, '项目:'), record.projectName || '-'),
React.createElement('div', null, React.createElement('span', { style: { color: '#94a3b8' } }, '交车地点:'), record.deliveryAddress || '-'),
React.createElement('div', null, React.createElement('span', { style: { color: '#94a3b8' } }, '品牌型号:'), (record.brand || '-') + ' / ' + (record.model || '-')),
React.createElement('div', null, React.createElement('span', { style: { color: '#94a3b8' } }, '任务来源:'), record.taskSource || '-')
) : null;
var sectionNavEl = React.createElement('div', { style: { position: 'sticky', top: 0, zIndex: 3, background: '#fff', padding: '8px 0 12px', marginBottom: 8, borderBottom: '1px solid #f1f5f9', display: 'flex', gap: 8, flexWrap: 'wrap' } },
sectionNav.map(function (s) {
var active = activeSection === s.key;
return React.createElement('button', {
key: s.key,
type: 'button',
onClick: function () { scrollToSection(s.key); },
style: {
border: 'none',
cursor: 'pointer',
padding: '6px 14px',
borderRadius: 999,
fontSize: 13,
fontWeight: active ? 600 : 500,
background: active ? '#2563eb' : '#f1f5f9',
color: active ? '#fff' : '#475569',
transition: 'background .2s,color .2s'
}
}, s.label);
})
);
var sectionBasic = React.createElement('div', { id: 'dv-edit-section-basic', style: { scrollMarginTop: 56 } },
React.createElement('div', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a', marginBottom: 12, paddingLeft: 10, borderLeft: '3px solid #2563eb' } }, '车辆信息'),
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '0 16px' } },
React.createElement(FormItem, { label: '车牌号', required: true },
React.createElement(Select, { value: form.plateNo, options: plateOptions, showSearch: true, filterOption: filterOption, placeholder: '请输入或选择车牌号', allowClear: true, style: { width: '100%' }, onChange: handlePlateChange })
),
React.createElement(FormItem, { label: '车辆类型' }, React.createElement(Input, { value: form.vehicleType, disabled: true })),
React.createElement(FormItem, { label: '品牌' }, React.createElement(Input, { value: form.brand, disabled: true })),
React.createElement(FormItem, { label: '型号' }, React.createElement(Input, { value: form.model, disabled: true })),
React.createElement(FormItem, { label: '车辆识别代码' }, React.createElement(Input, { value: form.vin, disabled: true }))
)
);
var sectionEquip = React.createElement('div', { id: 'dv-edit-section-equip', style: { scrollMarginTop: 56, marginTop: 24 } },
React.createElement('div', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a', marginBottom: 12, paddingLeft: 10, borderLeft: '3px solid #2563eb' } }, '设备与证照'),
React.createElement(FormItem, { label: '车身广告及放大字', required: true },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
React.createElement(Switch, { checked: !!form.hasAd, onChange: function (v) { updateForm({ hasAd: !!v }); } }),
React.createElement('span', { style: { fontSize: 13, color: '#64748b' } }, form.hasAd ? '有车身广告' : '无车身广告')
)
),
form.hasAd ? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 8 } },
React.createElement(UploadBox, { label: RequiredLabel('广告照片'), value: form.adPhoto, max: 1, onChange: function (l) { updateForm({ adPhoto: l }); } }),
React.createElement(UploadBox, { label: RequiredLabel('放大字照片'), value: form.bigWordPhoto, max: 1, onChange: function (l) { updateForm({ bigWordPhoto: l }); } })
) : null,
React.createElement(FormItem, { label: '尾板', required: true },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
React.createElement(Switch, { checked: !!form.hasTailboard, onChange: function (v) { updateForm({ hasTailboard: !!v }); } }),
React.createElement('span', { style: { fontSize: 13, color: '#64748b' } }, form.hasTailboard ? '有尾板' : '无尾板')
)
),
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
React.createElement(UploadBox, { label: RequiredLabel('备胎照片'), value: form.spareTirePhoto, max: 1, onChange: function (l) { updateForm({ spareTirePhoto: l }); if (l && l.length) ocrModalState[1]({ open: true, photoUrl: l[0].url, depth: '6.50' }); } }),
React.createElement(FormItem, { label: '备胎胎纹深度', required: true },
React.createElement(Input, { value: form.spareTireDepth, placeholder: '请输入', addonAfter: 'mm', onChange: function (e) { updateForm({ spareTireDepth: e.target.value }); } })
)
),
React.createElement(FormItem, { label: '驾驶培训', required: true },
form.trainingRecognized
? React.createElement('span', { style: { color: '#16a34a', fontWeight: 600, fontSize: 13 } }, '已完成视频培训')
: React.createElement(React.Fragment, null,
React.createElement('input', { type: 'file', ref: trainingInputRef, accept: 'image/*', style: { display: 'none' }, onChange: handleTrainingPick }),
React.createElement(Button, { onClick: function () { trainingInputRef.current && trainingInputRef.current.click(); } }, '上传验车码')
)
),
form.driverLicenses && form.driverLicenses.length ? React.createElement(FormItem, { label: '司机证照' },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4,minmax(0,1fr))', gap: 12 } },
form.driverLicenses.map(function (f) {
return React.createElement('div', { key: f.uid },
makeThumb(f.url, function () { previewState[1]({ open: true, url: f.url, title: f.name }); }),
React.createElement('div', { style: { marginTop: 4, fontSize: 12, color: '#64748b', textAlign: 'center' } }, f.name)
);
})
)
) : null,
React.createElement(FormItem, { label: '车辆检查' },
React.createElement(Button, { onClick: function () { inspectionOpenState[1](true); } }, '交车检查单')
)
);
var sectionMetrics = React.createElement('div', { id: 'dv-edit-section-metrics', style: { scrollMarginTop: 56, marginTop: 24 } },
React.createElement('div', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a', marginBottom: 12, paddingLeft: 10, borderLeft: '3px solid #2563eb' } }, '交车数据'),
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '0 16px' } },
React.createElement(FormItem, { label: '交车里程', required: true },
React.createElement(Input, { value: form.mileageKm, placeholder: '请输入', addonAfter: 'km', onChange: function (e) { updateForm({ mileageKm: e.target.value }); } })
),
React.createElement(FormItem, { label: '交车电量', required: true },
React.createElement(Input, { value: form.batteryPct, placeholder: '请输入', addonAfter: '%', onChange: function (e) { updateForm({ batteryPct: e.target.value }); } })
),
React.createElement(FormItem, { label: '交车氢量', required: true },
React.createElement(Input, { value: form.hydrogenAmount, placeholder: '请输入', addonAfter: form.hydrogenUnit, onChange: function (e) { updateForm({ hydrogenAmount: e.target.value }); } })
),
React.createElement(FormItem, { label: '送车服务费' },
React.createElement(Input, { value: form.serviceFee, placeholder: '选填', addonAfter: '元', onChange: function (e) { updateForm({ serviceFee: e.target.value }); } })
)
)
);
var photoKeys = ['仪表盘', '车辆正面', '车辆左前方'];
var photoState = useState({ vehicle: { '仪表盘': [], '车辆正面': [], '车辆左前方': [] } });
var photos = photoState[0];
var setPhotos = photoState[1];
var sectionPhotos = React.createElement('div', { id: 'dv-edit-section-photos', style: { scrollMarginTop: 56, marginTop: 24, marginBottom: 24 } },
React.createElement('div', { style: { fontSize: 14, fontWeight: 700, color: '#0f172a', marginBottom: 12, paddingLeft: 10, borderLeft: '3px solid #2563eb' } }, '交车照片'),
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3,minmax(0,1fr))', gap: 16 } },
photoKeys.map(function (k) {
return React.createElement(UploadBox, {
key: k,
label: RequiredLabel(k),
value: photos.vehicle[k] || [],
max: 1,
onChange: function (l) {
setPhotos(function (p) {
var n = Object.assign({}, p);
n.vehicle = Object.assign({}, p.vehicle);
n.vehicle[k] = l;
return n;
});
}
});
})
),
React.createElement('div', { style: { marginTop: 8, fontSize: 12, color: '#94a3b8' } }, '原型:完整照片清单见交车单编辑页需求说明')
);
return React.createElement(React.Fragment, null,
React.createElement(Drawer, {
open: open,
onClose: onClose,
width: Math.min(960, typeof window !== 'undefined' ? window.innerWidth - 24 : 960),
title: drawerTitle,
destroyOnClose: true,
styles: {
body: { padding: '16px 20px 88px', background: '#f8fafc' },
footer: { borderTop: '1px solid #e2e8f0', padding: '12px 20px' }
},
footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 10 } },
React.createElement(Button, { onClick: onClose, disabled: submitting }, '取消'),
React.createElement(Button, { onClick: handleSaveClick, disabled: submitting }, '保存'),
React.createElement(Button, { type: 'primary', loading: submitting, onClick: handleSubmitClick }, '提交')
)
},
summaryCard,
sectionNavEl,
React.createElement('div', { style: { background: '#fff', borderRadius: 12, border: '1px solid #e2e8f0', padding: '16px 16px 8px' } },
sectionBasic,
sectionEquip,
sectionMetrics,
sectionPhotos
)
),
React.createElement(Modal, {
open: !!previewState[0].open,
title: previewState[0].title || '预览',
footer: null,
onCancel: function () { previewState[1]({ open: false, url: '', title: '' }); },
width: 860
}, previewState[0].url ? React.createElement('img', { src: previewState[0].url, alt: previewState[0].title, style: { width: '100%', maxHeight: '70vh', objectFit: 'contain' } }) : null),
React.createElement(Modal, {
open: !!ocrModalState[0].open,
title: '备胎识别',
onCancel: function () { ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false })); },
onOk: function () { updateForm({ spareTireDepth: ocrModalState[0].depth }); ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false })); message.success('已反写胎纹深度'); },
okText: '确认',
cancelText: '取消'
},
React.createElement(Input, { value: ocrModalState[0].depth, addonAfter: 'mm', onChange: function (e) { ocrModalState[1](Object.assign({}, ocrModalState[0], { depth: e.target.value })); } })
),
React.createElement(Drawer, {
open: inspectionOpenState[0],
title: '交车检查单',
width: 720,
onClose: function () { inspectionOpenState[1](false); },
footer: React.createElement(Button, { type: 'primary', onClick: function () { message.success('检查单已保存'); inspectionOpenState[1](false); } }, '确定')
},
React.createElement(Table, {
rowKey: 'key',
size: 'small',
pagination: false,
bordered: true,
dataSource: inspectionList,
columns: [
{ title: '类别', dataIndex: 'category', width: 100 },
{ title: '检查项目', dataIndex: 'item', width: 140 },
{ title: '检查情况', dataIndex: 'checked', width: 100, render: function (v) { return React.createElement(Switch, { checked: !!v, disabled: true }); } },
{ title: '备注', dataIndex: 'remark' }
]
})
)
);
}
if (typeof window !== 'undefined') {
window.DeliveryEditDrawer = DeliveryEditDrawer;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,267 @@
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web - 运维管理 - 车辆业务 - 故障管理-编辑(已回填数据,可修改)
const Component = function () {
var antd = window.antd;
var Button = antd.Button;
var Input = antd.Input;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Form = antd.Form;
var Row = antd.Row;
var Col = antd.Col;
var Breadcrumb = antd.Breadcrumb;
var Layout = antd.Layout;
var message = antd.message;
var Card = antd.Card;
var Upload = antd.Upload;
var _form = Form.useForm();
var faultForm = _form[0];
// 故障等级枚举
var faultLevelOptions = [
{ label: '特急', value: '特急' },
{ label: '紧急', value: '紧急' },
{ label: '一般', value: '一般' },
{ label: '提示', value: '提示' }
];
// 故障类型枚举
var faultTypeOptions = [
{ label: '底盘故障', value: '底盘故障' },
{ label: '三电故障', value: '三电故障' },
{ label: '整车系统', value: '整车系统' },
{ label: '燃料电池系统故障', value: '燃料电池系统故障' },
{ label: '供氢系统故障', value: '供氢系统故障' },
{ label: '空调系统故障', value: '空调系统故障' },
{ label: '冷机故障', value: '冷机故障' },
{ label: '其他故障', value: '其他故障' }
];
// 故障来源枚举
var faultSourceOptions = [
{ label: '客户报告', value: '客户报告' },
{ label: '定期保养', value: '定期保养' },
{ label: '司机操作问题', value: '司机操作问题' }
];
// 解决情况枚举
var resolveStatusOptions = [
{ label: '未解决', value: '未解决' },
{ label: '临时排故', value: '临时排故' },
{ label: '已解决', value: '已解决' }
];
var plateOptions = [
{ label: '沪A12345', value: '沪A12345', brand: '一汽解放', model: 'J6P', company: '上海羚牛', vin: 'LNW1234567890ABCD' },
{ label: '浙B88888', value: '浙B88888', brand: '东风商用车', model: '天龙', company: '浙江羚牛', vin: 'LNW0987654321EFGH' },
{ label: '苏C66666', value: '苏C66666', brand: '福田欧曼', model: 'EST', company: '苏州冷链速运有限公司', vin: 'LNW1357924680IJKL' },
{ label: '沪D99999', value: '沪D99999', brand: '陕汽重卡', model: '德龙', company: '上海城配物流有限公司', vin: 'LNW2468013579MNOP' }
];
var PlusIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z' }), React.createElement('path', { d: 'M192 474h672q8 0 8 8v60q0 8-8 8H192q-8 0-8-8v-60q0-8 8-8z' })); };
var handleBack = function () {
message.info('返回列表');
};
// 编辑态:模拟从列表带入的已存在数据(实际接入路由/接口后替换为 record
React.useEffect(function () {
var initial = {
code: 'F-2024-001',
plate: '沪A12345',
brand: '一汽解放',
model: 'J6P',
company: '上海羚牛',
vin: 'LNW1234567890ABCD',
type: '底盘故障',
source: '客户报告',
level: '紧急',
reportTime: '2024-05-10 08:30:00',
status: '未解决',
desc: '车辆重载下制动踏板偏软,制动距离明显变长'
};
if (initial.reportTime) {
if (typeof window.dayjs === 'function') {
initial.reportTime = window.dayjs(initial.reportTime);
} else if (typeof window.moment === 'function') {
initial.reportTime = window.moment(initial.reportTime);
}
}
faultForm.setFieldsValue(initial);
}, []);
return React.createElement(Layout, { className: 'arco-theme-overrides', style: { minHeight: '100vh', background: '#f2f3f5', fontFamily: 'Inter, Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif' } },
React.createElement('style', null, `
.arco-theme-overrides .ant-btn { border-radius: 4px; }
.arco-theme-overrides .ant-btn-primary { background-color: #165dff; border-color: #165dff; }
.arco-theme-overrides .ant-btn-primary:hover { background-color: #4080ff; border-color: #4080ff; }
.arco-grouped-form-page { display: flex; flex-direction: column; min-height: 100vh; }
.arco-grouped-form-page-content { flex: 1; padding: 16px 20px 24px; }
.arco-grouped-form-page .ant-card { margin-bottom: 16px; border-radius: 4px; border: none; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
.arco-grouped-form-page .ant-card-head { border-bottom: none; padding: 20px 24px 0; min-height: auto; }
.arco-grouped-form-page .ant-card-head-title { font-size: 16px; font-weight: 500; color: #1d2129; padding: 0; }
.arco-grouped-form-page .ant-card-body { padding: 24px; }
.arco-grouped-form-page .ant-form-vertical .ant-form-item-label { padding-bottom: 8px; height: auto; line-height: 1.5715; }
.arco-grouped-form-page .ant-form-item { margin-bottom: 24px; }
.arco-grouped-form-page .ant-input,
.arco-grouped-form-page .ant-select-selector,
.arco-grouped-form-page .ant-picker,
.arco-grouped-form-page .ant-input-affix-wrapper { background-color: #f2f3f5; border: 1px solid #e5e6eb; border-radius: 2px; transition: all 0.1s cubic-bezier(0, 0, 1, 1); }
.arco-grouped-form-page .ant-input:hover,
.arco-grouped-form-page .ant-select:not(.ant-select-disabled):hover .ant-select-selector,
.arco-grouped-form-page .ant-picker:hover,
.arco-grouped-form-page .ant-input-affix-wrapper:hover { background-color: #f2f3f5; border-color: #165dff; }
.arco-grouped-form-page .ant-input:focus,
.arco-grouped-form-page .ant-input-focused,
.arco-grouped-form-page .ant-select-focused .ant-select-selector,
.arco-grouped-form-page .ant-picker-focused,
.arco-grouped-form-page .ant-input-affix-wrapper-focused { background-color: #fff; border: 1px solid #165dff !important; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2) !important; outline: 0; }
.arco-grouped-form-page .ant-input[disabled],
.arco-grouped-form-page .ant-select-disabled .ant-select-selector,
.arco-grouped-form-page .ant-picker-disabled { color: #86909c; background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper[disabled] { background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper > input.ant-input { background-color: transparent; }
.arco-grouped-form-page .ant-input-affix-wrapper > input.ant-input:focus { background-color: transparent; box-shadow: none !important; border: none !important; }
.arco-grouped-form-footer { background: #fff; padding: 16px 24px; border-top: 1px solid #e5e6eb; display: flex; justify-content: flex-end; align-items: center; gap: 12px; position: sticky; bottom: 0; z-index: 100; box-shadow: 0 -2px 10px rgba(0,0,0,0.05); }
.arco-grouped-form-footer .ant-btn { border-radius: 5px; height: 32px; padding: 4px 16px; font-size: 14px; }
.arco-theme-overrides .ant-breadcrumb { color: #86909c; font-size: 14px; white-space: nowrap; flex-shrink: 0; margin-bottom: 20px; }
.arco-theme-overrides .ant-breadcrumb a { color: #4e5969; }
.arco-theme-overrides .ant-breadcrumb a:hover { color: #165dff; background-color: transparent; }
.arco-theme-overrides .ant-form-item-label > label { color: #4e5969; white-space: nowrap; }
.arco-theme-overrides .ant-form-item-label > label::after { display: none !important; content: "" !important; margin: 0 !important; }
`),
React.createElement('div', { className: 'arco-grouped-form-page' },
React.createElement('div', { className: 'arco-grouped-form-page-content' },
React.createElement(Breadcrumb, {
separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'),
items: [
{ title: '首页' },
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: React.createElement('a', { onClick: function(e) { e.preventDefault(); handleBack(); } }, '故障管理') },
{ title: React.createElement('span', { style: { color: '#1d2129' } }, '编辑故障单') }
]
}),
React.createElement(Form, { form: faultForm, layout: 'vertical' },
React.createElement(Card, { title: '车辆信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障编码', name: 'code' },
React.createElement(Input, { disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车牌号', name: 'plate', rules: [{ required: true, message: '请选择车牌号' }] },
React.createElement(Select, {
placeholder: '请选择车牌号',
options: plateOptions,
showSearch: true,
onChange: function(val, option) {
if (option) {
faultForm.setFieldsValue({
brand: option.brand,
model: option.model,
company: option.company,
vin: option.vin
});
} else {
faultForm.setFieldsValue({ brand: undefined, model: undefined, company: undefined, vin: undefined });
}
}
})
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆品牌', name: 'brand' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆型号', name: 'model' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '运营公司', name: 'company' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆识别代码', name: 'vin' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
)
)
),
React.createElement(Card, { title: '故障信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障类型', name: 'type', rules: [{ required: true, message: '请选择故障类型' }] },
React.createElement(Select, { placeholder: '请选择故障类型', options: faultTypeOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障来源', name: 'source', rules: [{ required: true, message: '请选择故障来源' }] },
React.createElement(Select, { placeholder: '请选择故障来源', options: faultSourceOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障等级', name: 'level', rules: [{ required: true, message: '请选择故障等级' }] },
React.createElement(Select, { placeholder: '请选择故障等级', options: faultLevelOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障上报时间', name: 'reportTime', rules: [{ required: true, message: '请选择故障上报时间' }] },
React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择上报时间', showTime: true })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '解决情况', name: 'status', rules: [{ required: true, message: '请选择故障解决情况' }] },
React.createElement(Select, { placeholder: '请选择故障解决情况', options: resolveStatusOptions })
)
)
)
),
React.createElement(Card, { title: '故障证据与描述', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, { label: '故障描述', name: 'desc', rules: [{ required: true, message: '请填写故障描述' }] },
React.createElement(Input.TextArea, {
placeholder: '在何种状态下,产生何种现象,导致何种事故',
style: { height: 80, minHeight: 80, resize: 'none' }
})
)
),
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, { label: '故障证据', name: 'evidence' },
React.createElement(Upload, { listType: 'picture-card' },
React.createElement('div', null,
React.createElement(PlusIcon, null),
React.createElement('div', { style: { marginTop: 8 } }, '上传文件')
)
),
React.createElement('div', { style: { fontSize: 12, color: '#86909c', marginTop: 8 } }, '支持上传照片、视频、录音')
)
)
)
)
)
),
React.createElement('div', { className: 'arco-grouped-form-footer' },
React.createElement(Button, { onClick: handleBack, style: { borderRadius: 5 } }, '取消'),
React.createElement(Button, { type: 'primary', onClick: function () { faultForm.submit(); message.success('保存成功'); }, style: { borderRadius: 5 } }, '保存')
)
)
);
};
if (typeof module !== 'undefined' && module.exports) module.exports = Component;

View File

@@ -0,0 +1,503 @@
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web - 运维管理 - 车辆业务 - 故障管理(列表与表单)
const Component = function () {
var useState = React.useState;
var useEffect = React.useEffect;
var antd = window.antd;
var Table = antd.Table;
var Button = antd.Button;
var Space = antd.Space;
var Input = antd.Input;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Form = antd.Form;
var Row = antd.Row;
var Col = antd.Col;
var Divider = antd.Divider;
var Breadcrumb = antd.Breadcrumb;
var Layout = antd.Layout;
var message = antd.message;
var Tag = antd.Tag;
var Card = antd.Card;
var Modal = antd.Modal;
var Tabs = antd.Tabs;
var Tooltip = antd.Tooltip;
var RangePicker = DatePicker.RangePicker;
var _filterFormInst = Form.useForm();
var filterForm = _filterFormInst[0];
var _specOpen = useState(false);
var specModalOpen = _specOpen[0];
var setSpecModalOpen = _specOpen[1];
var _listTab = useState('pending');
var listTab = _listTab[0];
var setListTab = _listTab[1];
var _tableRows = useState(null);
var tableRowsOverride = _tableRows[0];
var setTableRowsOverride = _tableRows[1];
var faultLevelOptions = [
{ label: 'L1-特急', value: '特急' },
{ label: 'L2-紧急', value: '紧急' },
{ label: 'L3-一般', value: '一般' },
{ label: 'L4-提示', value: '提示' }
];
var lastOperatorOptions = [
{ label: '王婷婷', value: '王婷婷' },
{ label: '刘若楠', value: '刘若楠' },
{ label: '陈嘉豪', value: '陈嘉豪' },
{ label: '赵海峰', value: '赵海峰' }
];
// 故障类型枚举
var faultTypeOptions = [
{ label: '底盘故障', value: '底盘故障' },
{ label: '三电故障', value: '三电故障' },
{ label: '整车系统', value: '整车系统' },
{ label: '燃料电池系统故障', value: '燃料电池系统故障' },
{ label: '供氢系统故障', value: '供氢系统故障' },
{ label: '空调系统故障', value: '空调系统故障' },
{ label: '冷机故障', value: '冷机故障' },
{ label: '其他故障', value: '其他故障' }
];
// 解决情况枚举
var resolveStatusOptions = [
{ label: '未解决', value: '未解决' },
{ label: '临时排故', value: '临时排故' },
{ label: '已解决', value: '已解决' }
];
var SearchIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z' })); };
var FileTextIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 16, height: 16, fill: 'currentColor' }, React.createElement('path', { d: 'M854.6 288.7L639.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216c0 22.1 17.9 40 40 40h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM702 458H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h382c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z' })); };
var ResetIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M793 242H366v-74c0-6.7-7.7-10.4-12.9-6.3l-142 112a8 8 0 000 12.6l142 112c5.2 4.1 12.9.4 12.9-6.3v-74h415v470H175c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h618c35.3 0 64-28.7 64-64V306c0-35.3-28.7-64-64-64z' })); };
var PlusIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z' }), React.createElement('path', { d: 'M192 474h672q8 0 8 8v60q0 8-8 8H192q-8 0-8-8v-60q0-8 8-8z' })); };
var DownloadIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 16, height: 16, fill: 'currentColor' }, React.createElement('path', { d: 'M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z' })); };
var ListIcon = function() { return React.createElement('svg', { viewBox: '0 0 48 48', width: 14, height: 14, fill: 'none', stroke: 'currentColor', strokeWidth: 4, strokeLinecap: 'butt', strokeLinejoin: 'miter' }, React.createElement('path', { d: 'M17 12H42' }), React.createElement('path', { d: 'M17 24H42' }), React.createElement('path', { d: 'M17 36H42' }), React.createElement('path', { d: 'M8 12H9' }), React.createElement('path', { d: 'M8 24H9' }), React.createElement('path', { d: 'M8 36H9' })); };
var UploadIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M512 256l-256 256h160v256h192V512h160L512 256zM256 832v64h512v-64H256z' })); };
var levelLabelMap = { 特急: 'L1-特急', 紧急: 'L2-紧急', 一般: 'L3-一般', 提示: 'L4-提示' };
var renderFaultLevel = function (text) {
var color = 'default';
if (text === '特急') color = 'red';
else if (text === '紧急') color = 'orange';
else if (text === '一般') color = 'blue';
else if (text === '提示') color = 'green';
var label = levelLabelMap[text] || text;
return React.createElement(Tag, { color: color }, label);
};
var renderResolveStatus = function (text) {
var dot = '#86909c';
if (text === '已解决') dot = '#00b42a';
else if (text === '临时排故') dot = '#ff7d00';
else if (text === '未解决') dot = '#f53f3f';
return React.createElement(Space, { size: 6 },
React.createElement('span', { style: { display: 'inline-block', width: 6, height: 6, borderRadius: '50%', backgroundColor: dot } }),
React.createElement('span', null, text)
);
};
var columns = [
{ title: '故障编码', dataIndex: 'code', key: 'code', width: 132, fixed: 'left' },
{ title: '解决情况', dataIndex: 'status', key: 'status', width: 120, render: renderResolveStatus },
{ title: '车牌号码', dataIndex: 'plate', key: 'plate', width: 112 },
{ title: '车辆品牌', dataIndex: 'brand', key: 'brand', width: 100, ellipsis: true },
{ title: '车辆型号', dataIndex: 'model', key: 'model', width: 100, ellipsis: true },
{ title: '运营公司', dataIndex: 'company', key: 'company', width: 140, ellipsis: true },
{ title: '故障等级', dataIndex: 'level', key: 'level', width: 108, render: renderFaultLevel },
{ title: '故障类型', dataIndex: 'type', key: 'type', width: 128, ellipsis: true },
{
title: '故障描述',
dataIndex: 'desc',
key: 'desc',
width: 200,
ellipsis: { showTitle: false },
render: function (text) {
return React.createElement(Tooltip, { placement: 'topLeft', title: text },
React.createElement('span', { style: { cursor: 'default' } }, text)
);
}
},
{ title: '故障上报时间', dataIndex: 'reportTime', key: 'reportTime', width: 176, ellipsis: true, className: 'fault-col-report-time' },
{ title: '最后操作时间', dataIndex: 'lastOperationTime', key: 'lastOperationTime', width: 176, ellipsis: true },
{ title: '最后操作人', dataIndex: 'lastOperator', key: 'lastOperator', width: 104, ellipsis: true },
{
title: '操作',
key: 'action',
fixed: 'right',
width: listTab === 'history' ? 72 : 104,
render: function (_, record) {
if (listTab === 'history') {
return React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleView(record); } }, '查看');
}
return React.createElement(Space, { size: 0, split: React.createElement('span', { style: { color: '#e5e6eb' } }, '|') },
React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleView(record); } }, '查看'),
React.createElement(Button, { type: 'link', size: 'small', style: { padding: '0 4px' }, onClick: function () { handleEdit(record); } }, '编辑')
);
}
}
];
var ALL_DATA = [
{ key: '1', code: 'F-2024-001', plate: '沪A12345', brand: '一汽解放', model: 'J6P', company: '上海羚牛', type: '底盘故障', level: '紧急', source: '客户报告', status: '未解决', reportTime: '2024-05-10 08:30:00', lastOperator: '王婷婷', lastOperationTime: '2024-05-10 09:00:00', desc: '车辆重载下制动踏板偏软,制动距离明显变长' },
{ key: '2', code: 'F-2024-002', plate: '浙B88888', brand: '东风商用车', model: '天龙', company: '浙江羚牛', type: '三电故障', level: '特急', source: '司机操作问题', status: '临时排故', reportTime: '2024-05-11 14:15:00', lastOperator: '刘若楠', lastOperationTime: '2024-05-11 16:20:00', desc: '高压系统绝缘报警频繁触发,车辆进入限扭模式' },
{ key: '3', code: 'F-2024-003', plate: '苏C66666', brand: '福田欧曼', model: 'EST', company: '苏州冷链速运有限公司', type: '整车系统', level: '一般', source: '定期保养', status: '已解决', reportTime: '2024-05-12 10:00:00', lastOperator: '陈嘉豪', lastOperationTime: '2024-05-12 11:30:00', desc: '后桥轮胎偏磨,建议更换并做四轮定位' },
{ key: '4', code: 'F-2024-004', plate: '沪D99999', brand: '陕汽重卡', model: '德龙', company: '上海城配物流有限公司', type: '空调系统故障', level: '提示', source: '客户报告', status: '已解决', reportTime: '2024-05-13 09:45:00', lastOperator: '赵海峰', lastOperationTime: '2024-05-13 10:10:00', desc: '空调制冷效果差,出风口温度偏高' }
];
var parseReportTime = function (str) {
if (!str) return null;
var p = str.replace(/-/g, '/');
var t = new Date(p);
return isNaN(t.getTime()) ? null : t;
};
var applyListFilter = function () {
var v = filterForm.getFieldsValue();
var rows = ALL_DATA.filter(function (r) {
if (listTab === 'pending') return r.status !== '已解决';
return r.status === '已解决';
});
if (v.filterPlate && String(v.filterPlate).trim()) {
var q = String(v.filterPlate).trim();
rows = rows.filter(function (r) { return (r.plate || '').indexOf(q) !== -1; });
}
if (v.filterLevels && v.filterLevels.length) {
rows = rows.filter(function (r) { return v.filterLevels.indexOf(r.level) !== -1; });
}
if (v.filterTypes && v.filterTypes.length) {
rows = rows.filter(function (r) { return v.filterTypes.indexOf(r.type) !== -1; });
}
if (v.filterLastOperator) {
rows = rows.filter(function (r) { return r.lastOperator === v.filterLastOperator; });
}
if (v.filterDateRange && v.filterDateRange.length === 2 && v.filterDateRange[0] && v.filterDateRange[1]) {
var start = v.filterDateRange[0];
var end = v.filterDateRange[1];
var startMs = start.valueOf ? start.valueOf() : new Date(start).getTime();
var endMs = end.valueOf ? end.valueOf() : new Date(end).getTime();
var startDay = new Date(startMs);
startDay.setHours(0, 0, 0, 0);
startMs = startDay.getTime();
var endDay = new Date(endMs);
endDay.setHours(23, 59, 59, 999);
endMs = endDay.getTime();
rows = rows.filter(function (r) {
var rt = parseReportTime(r.reportTime);
if (!rt) return false;
var m = rt.getTime();
return m >= startMs && m <= endMs;
});
}
setTableRowsOverride(rows);
};
useEffect(function () {
applyListFilter();
}, [listTab]);
var displayData = tableRowsOverride !== null && tableRowsOverride !== undefined
? tableRowsOverride
: ALL_DATA.filter(function (r) {
if (listTab === 'pending') return r.status !== '已解决';
return r.status === '已解决';
});
var handleCreate = function () {
message.info('跳转到新增故障页面');
};
var handleEdit = function (record) {
message.info('跳转到编辑故障页面');
};
var handleView = function (record) {
message.info('跳转到查看故障页面');
};
var formItemLayout = {
labelAlign: 'left',
colon: false,
labelCol: { flex: '0 0 100px' },
wrapperCol: { flex: '1 1 0' }
};
var specSection = function (title, children) {
return React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('div', { style: { fontWeight: 600, color: '#1d2129', marginBottom: 8, fontSize: 14 } }, title),
children
);
};
var specLine = function (text) {
return React.createElement('div', { style: { fontSize: 13, color: '#4e5969', lineHeight: 1.75, paddingLeft: 0 } }, text);
};
var renderSpecModal = function () {
return React.createElement(Modal, {
title: '故障列表页 — 需求说明',
open: specModalOpen,
onCancel: function () { setSpecModalOpen(false); },
footer: React.createElement(Button, { type: 'primary', onClick: function () { setSpecModalOpen(false); } }, '知道了'),
width: 720,
centered: true,
destroyOnClose: true
},
React.createElement('div', { style: { maxHeight: '70vh', overflowY: 'auto', paddingRight: 4 } },
specSection('2. 故障列表页', React.createElement(React.Fragment, null,
specLine('用于展示待处理与历史记录,支持多条件检索、查看、编辑、删除、导入导出。')
)),
specSection('2.1 顶部筛选区(支持单条件或多条件组合)', React.createElement(React.Fragment, null,
specLine('2.1.1 车牌号:输入框,支持模糊搜索;'),
specLine('2.1.2 故障等级多选下拉选项L1-特急、L2-紧急、L3-一般、L4-提示;'),
specLine('2.1.3 上报时间:日期范围选择(开始~结束);'),
specLine('2.1.4 故障类型:多选下拉,选项为故障类型枚举;'),
specLine('2.1.5 最后操作人:下拉选择器;'),
specLine('2.1.6 查询按钮:执行筛选;'),
specLine('2.1.7 重置按钮:清空筛选条件。')
)),
specSection('2.2 列表工具栏', React.createElement(React.Fragment, null,
specLine('2.2.1 新建:进入故障表单页;'),
specLine('2.2.2 导出:导出当前筛选结果;'),
specLine('2.2.3 导入:批量导入故障数据;'),
specLine('2.2.4 展示设置:字段展示配置(当前版本可预留)。')
)),
specSection('2.3 Tab分区', React.createElement(React.Fragment, null,
specLine('2.3.1 待处理;'),
specLine('2.3.2 历史记录。')
)),
specSection('2.4 列表字段展示(当前版本)', React.createElement(React.Fragment, null,
specLine('故障编码、解决情况、车牌号码、车辆品牌、车辆型号、运营公司、故障等级、故障类型、故障描述(省略展示,悬停显示完整)、故障上报时间、最后操作时间、最后操作人、操作(待处理:查看/编辑;历史记录:仅查看)。'),
specLine('说明:车辆识别代码不在列表展示,仅在表单车辆信息中展示。')
)),
specSection('2.5 状态与操作规则', React.createElement(React.Fragment, null,
specLine('2.5.1 解决情况状态值:临时排故、已解决、未解决;'),
specLine('2.5.2 操作列:待处理 Tab 为查看、编辑(无删除);历史记录 Tab 仅查看;'),
specLine('2.5.3 分页每页10条固定分页。')
))
)
);
};
return React.createElement(Layout, { className: 'arco-theme-overrides', style: { minHeight: '100vh', background: '#f2f3f5', fontFamily: 'Inter, Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif' } },
React.createElement('style', null, `
.arco-theme-overrides .ant-btn { border-radius: 4px; }
.arco-theme-overrides .ant-btn-primary { background-color: #165dff; border-color: #165dff; }
.arco-theme-overrides .ant-btn-primary:hover { background-color: #4080ff; border-color: #4080ff; }
.arco-theme-overrides .ant-btn-link { color: #165dff; }
.arco-theme-overrides .ant-btn-link:hover { background-color: transparent; color: #4080ff; }
.arco-theme-overrides .ant-table-thead > tr > th {
background-color: #f2f3f5;
color: #909399;
font-weight: 400;
font-size: 14px;
height: 40px;
padding: 9px 16px;
line-height: 22px;
box-sizing: border-box;
border-bottom: 1px solid #e5e6eb;
border-top: none;
vertical-align: middle;
}
.arco-theme-overrides .ant-table-thead > tr > th .ant-table-column-title {
font-size: 14px;
color: #909399;
line-height: 22px;
min-height: 22px;
white-space: nowrap;
}
.arco-theme-overrides .ant-table-thead > tr > th::before { display: none !important; }
.arco-theme-overrides .ant-table-tbody > tr > td { border-bottom: 1px solid #e5e6eb; padding: 13px 16px; color: #4e5969; vertical-align: middle; line-height: 22px; }
.arco-theme-overrides .ant-table-tbody > tr { height: 52px; }
.arco-theme-overrides .customer-table-scroll-wrap { width: 100%; min-width: 0; max-width: 100%; }
.arco-theme-overrides .ant-table-wrapper { border: none; }
.arco-theme-overrides .ant-table { border: none; }
.arco-theme-overrides .ant-table-container { border: none; }
.arco-theme-overrides .ant-table-pagination.ant-pagination { margin: 16px 0 0 0; }
.arco-theme-overrides .ant-card { border: none; border-radius: 4px; }
.arco-theme-overrides .customer-page-card.ant-card { border: 1px solid #e5e6eb !important; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,0.06); background: #fff; }
.arco-theme-overrides .customer-page-card .ant-card-body { padding: 20px 24px 24px; }
.arco-theme-overrides .ant-input, .arco-theme-overrides .ant-select-selector, .arco-theme-overrides .ant-picker, .arco-theme-overrides .ant-input-affix-wrapper { border-radius: 2px; border: 1px solid #e5e6eb; background-color: #fff; transition: all 0.1s cubic-bezier(0, 0, 1, 1); }
.arco-theme-overrides .ant-input:hover, .arco-theme-overrides .ant-select:not(.ant-select-disabled):hover .ant-select-selector, .arco-theme-overrides .ant-picker:hover, .arco-theme-overrides .ant-input-affix-wrapper:hover { background-color: #fff; border-color: #165dff; }
.arco-theme-overrides .ant-input:focus, .arco-theme-overrides .ant-input-focused, .arco-theme-overrides .ant-select-focused .ant-select-selector, .arco-theme-overrides .ant-picker-focused, .arco-theme-overrides .ant-input-affix-wrapper-focused { background-color: #fff; border: 1px solid #165dff !important; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2) !important; outline: 0; }
.arco-theme-overrides .ant-breadcrumb { color: #86909c; font-size: 14px; white-space: nowrap; flex-shrink: 0; }
.arco-theme-overrides .customer-page-header-row { display: flex; align-items: center; flex-wrap: nowrap; gap: 0; margin-bottom: 20px; }
.arco-theme-overrides .ant-breadcrumb a { color: #4e5969; }
.arco-theme-overrides .ant-breadcrumb a:hover { color: #165dff; background-color: transparent; }
.arco-theme-overrides .ant-form-item-label { padding: 0 16px 0 0 !important; line-height: 32px; height: 32px; display: flex; align-items: center; }
.arco-theme-overrides .ant-form-item-control { display: flex; align-items: center; line-height: 32px; min-height: 32px; }
.arco-theme-overrides .ant-form-item-control-input { min-height: 32px; width: 100%; display: flex; align-items: center; }
.arco-theme-overrides .ant-form-item-control-input-content { display: flex; align-items: center; width: 100%; height: 100%; }
.arco-theme-overrides .ant-select { width: 100%; height: 32px; }
.arco-theme-overrides .ant-select-selector { height: 32px !important; min-height: 32px !important; display: flex; align-items: center; width: 100%; padding: 0 12px; }
.arco-theme-overrides .ant-input { height: 32px; padding: 4px 12px; }
.arco-theme-overrides .ant-input-affix-wrapper { height: 32px; padding: 0 12px; display: flex; align-items: center; }
.arco-theme-overrides .ant-input-affix-wrapper > input.ant-input,
.arco-theme-overrides .ant-input-affix-wrapper > input.ant-input:focus,
.arco-theme-overrides .ant-input-affix-wrapper > input.ant-input:hover { height: 100%; padding: 0; border: none !important; box-shadow: none !important; outline: none !important; background-color: transparent !important; }
.arco-theme-overrides .ant-picker { height: 32px !important; min-height: 32px !important; display: flex; align-items: center; padding: 4px 12px; width: 100%; }
.arco-theme-overrides .ant-form-item-label > label { color: #4e5969; white-space: nowrap; }
.arco-theme-overrides .ant-form-item-label > label::after { display: none !important; content: "" !important; margin: 0 !important; }
.arco-theme-overrides .ant-select-multiple .ant-select-selector { height: auto !important; min-height: 32px !important; align-items: center; padding-top: 2px; padding-bottom: 2px; }
.arco-theme-overrides .fault-col-report-time { white-space: nowrap; }
`),
React.createElement('div', { style: { padding: '16px 20px 24px', display: 'flex', flexDirection: 'column', flex: 1 } },
React.createElement(Card, { className: 'customer-page-card', bordered: false },
React.createElement('div', { className: 'customer-page-header-row', style: { marginBottom: 16, justifyContent: 'space-between', width: '100%' } },
React.createElement(Breadcrumb, {
separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'),
items: [
{ title: React.createElement(ListIcon, { style: { display: 'inline-flex', alignItems: 'center', fontSize: 14, transform: 'translate(-2px, 1px)' } }) },
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: React.createElement('span', { style: { color: '#1d2129', fontWeight: 700, fontSize: 16, lineHeight: '22px' } }, '故障管理') }
]
}),
React.createElement(Button, {
type: 'link',
icon: React.createElement(FileTextIcon, null),
style: { display: 'flex', alignItems: 'center', gap: 4, padding: '0 4px', color: '#165dff', fontWeight: 500 },
onClick: function () { setSpecModalOpen(true); }
}, '查看需求说明')
),
React.createElement('div', { style: { fontSize: 18, fontWeight: 600, color: '#1d2129', lineHeight: '26px', marginBottom: 20 } }, '故障管理'),
// 搜索表单区域
React.createElement('div', { style: { marginBottom: 0 } },
React.createElement(Row, { style: { flexWrap: 'nowrap', alignItems: 'stretch' } },
React.createElement(Col, { flex: 1, style: { minWidth: 0, paddingRight: 40 } },
React.createElement(Form, Object.assign({ layout: 'horizontal', form: filterForm }, formItemLayout),
React.createElement(Row, { gutter: 24, style: { rowGap: 0 } },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { name: 'filterPlate', label: '车牌号', style: { marginBottom: 16 } },
React.createElement(Input, { placeholder: '支持模糊搜索', allowClear: true })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { name: 'filterLevels', label: '故障等级', style: { marginBottom: 16 } },
React.createElement(Select, {
mode: 'multiple',
allowClear: true,
placeholder: '多选L1~L4',
options: faultLevelOptions,
maxTagCount: 'responsive'
})
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { name: 'filterDateRange', label: '上报时间', style: { marginBottom: 16 } },
React.createElement(RangePicker, { style: { width: '100%' }, showTime: false, placeholder: ['开始', '结束'] })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { name: 'filterTypes', label: '故障类型', style: { marginBottom: 16 } },
React.createElement(Select, {
mode: 'multiple',
allowClear: true,
placeholder: '多选故障类型',
options: faultTypeOptions,
maxTagCount: 'responsive'
})
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { name: 'filterLastOperator', label: '最后操作人', style: { marginBottom: 16 } },
React.createElement(Select, {
allowClear: true,
placeholder: '请选择',
options: lastOperatorOptions
})
)
)
)
)
),
React.createElement(Divider, {
type: 'vertical',
style: {
alignSelf: 'stretch',
height: 'auto',
minHeight: 100,
borderLeftColor: 'rgb(229, 230, 235)',
borderLeftStyle: 'dashed',
marginLeft: 20,
marginRight: 20
}
}),
React.createElement(Col, { flex: 'none', style: { textAlign: 'right', display: 'flex', flexDirection: 'column', alignItems: 'flex-end', minWidth: 100, maxWidth: 168 } },
React.createElement(Space, { direction: 'vertical', size: 12, style: { width: '100%', alignItems: 'flex-end' } },
React.createElement(Button, { type: 'primary', icon: React.createElement(SearchIcon, null), style: { display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'center', width: 86 }, onClick: function () { applyListFilter(); message.success('已按条件筛选'); } }, '查询'),
React.createElement(Button, {
icon: React.createElement(ResetIcon, null),
style: { display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'center', width: 86 },
onClick: function () {
filterForm.resetFields();
applyListFilter();
message.info('已清空筛选条件');
}
}, '重置')
)
)
)
),
React.createElement(Divider, { style: { margin: '16px 0 20px', borderColor: '#e5e6eb' } }),
// 工具栏 + 表格
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 } },
React.createElement(Row, { justify: 'space-between', align: 'middle', style: { marginBottom: 16 } },
React.createElement(Col, null,
React.createElement(Space, { size: 12 },
React.createElement(Button, { type: 'primary', icon: React.createElement(PlusIcon, null), style: { display: 'flex', alignItems: 'center', gap: 6 }, onClick: handleCreate }, '新建'),
React.createElement(Button, {
style: { padding: '4px 10px', height: 32, display: 'flex', alignItems: 'center', gap: 6, color: '#4e5969' },
onClick: function () { message.info('已导出当前筛选结果'); }
}, React.createElement(DownloadIcon, null), React.createElement('span', null, '导出')),
React.createElement(Button, { icon: React.createElement(UploadIcon, null), style: { display: 'flex', alignItems: 'center', gap: 6 }, onClick: function () { message.info('批量导入故障数据'); } }, '导入'),
React.createElement(Button, { style: { color: '#4e5969' }, onClick: function () { message.info('字段展示配置(当前版本预留)'); } }, '展示设置')
)
),
React.createElement(Col, null)
),
React.createElement(Tabs, {
activeKey: listTab,
onChange: function (k) {
setListTab(k);
},
style: { marginBottom: 12 },
items: [
{ key: 'pending', label: '待处理' },
{ key: 'history', label: '历史记录' }
]
}),
React.createElement('div', { className: 'customer-table-scroll-wrap', style: { flex: 1, minHeight: 0, minWidth: 0 } },
React.createElement(Table, {
columns: columns,
dataSource: displayData,
pagination: {
pageSize: 10,
showSizeChanger: false,
showTotal: function (total) { return '共 ' + total + ' 条'; }
},
scroll: { x: 2100 }
})
)
)
)
),
renderSpecModal()
);
};
if (typeof module !== 'undefined' && module.exports) module.exports = Component;

View File

@@ -0,0 +1,417 @@
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web - 运维管理 - 车辆业务 - 新增故障
const Component = function () {
var useState = React.useState;
var useEffect = React.useEffect;
var antd = window.antd;
var Button = antd.Button;
var Input = antd.Input;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Form = antd.Form;
var Row = antd.Row;
var Col = antd.Col;
var Breadcrumb = antd.Breadcrumb;
var Layout = antd.Layout;
var message = antd.message;
var Card = antd.Card;
var Upload = antd.Upload;
var Modal = antd.Modal;
var _form = Form.useForm();
var faultForm = _form[0];
var _specOpen = useState(false);
var specModalOpen = _specOpen[0];
var setSpecModalOpen = _specOpen[1];
var _formDirty = useState(false);
var formDirty = _formDirty[0];
var setFormDirty = _formDirty[1];
var faultLevelOptions = [
{ label: 'L1-特急', value: '特急' },
{ label: 'L2-紧急', value: '紧急' },
{ label: 'L3-一般', value: '一般' },
{ label: 'L4-提示', value: '提示' }
];
var faultTypeOptions = [
{ label: '底盘故障', value: '底盘故障' },
{ label: '三电故障', value: '三电故障' },
{ label: '整车系统', value: '整车系统' },
{ label: '燃料电池系统故障', value: '燃料电池系统故障' },
{ label: '供氢系统故障', value: '供氢系统故障' },
{ label: '空调系统故障', value: '空调系统故障' },
{ label: '冷机故障', value: '冷机故障' },
{ label: '其他故障', value: '其他故障' }
];
var faultSourceOptions = [
{ label: '客户报告', value: '客户报告' },
{ label: '定期保养', value: '定期保养' },
{ label: '周期性维护', value: '周期性维护' },
{ label: '预防性维护', value: '预防性维护' },
{ label: '整备', value: '整备' }
];
var resolveStatusOptions = [
{ label: '未解决', value: '未解决' },
{ label: '临时排故', value: '临时排故' },
{ label: '已解决', value: '已解决' }
];
/** 运营公司枚举:浙江羚牛、上海羚牛、广东羚牛 */
var plateOptions = [
{ label: '沪A12345', value: '沪A12345', brand: '一汽解放', model: 'J6P', company: '上海羚牛', vin: 'LNW1234567890ABCD' },
{ label: '浙B88888', value: '浙B88888', brand: '东风商用车', model: '天龙', company: '浙江羚牛', vin: 'LNW0987654321EFGH' },
{ label: '粤A00001', value: '粤A00001', brand: '福田欧曼', model: 'EST', company: '广东羚牛', vin: 'LNW1357924680IJKL' },
{ label: '沪D99999', value: '沪D99999', brand: '陕汽重卡', model: '德龙', company: '上海羚牛', vin: 'LNW2468013579MNOP' }
];
var PlusIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 14, height: 14, fill: 'currentColor' }, React.createElement('path', { d: 'M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z' }), React.createElement('path', { d: 'M192 474h672q8 0 8 8v60q0 8-8 8H192q-8 0-8-8v-60q0-8 8-8z' })); };
var FileTextIcon = function() { return React.createElement('svg', { viewBox: '0 0 1024 1024', width: 16, height: 16, fill: 'currentColor' }, React.createElement('path', { d: 'M854.6 288.7L639.4 73.4c-6-6-14.2-9.4-22.7-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216c0 22.1 17.9 40 40 40h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM702 458H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h382c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z' })); };
var DESC_PLACEHOLDER = '在何种状态下\n产生何种现象\n导致何种事故';
var goBackList = function () {
message.info('返回列表');
};
var requestExit = function () {
if (formDirty) {
Modal.confirm({
title: '确定要退出吗?',
content: '未保存的数据将丢失',
okText: '确定',
cancelText: '取消',
onOk: function () {
setFormDirty(false);
goBackList();
}
});
} else {
goBackList();
}
};
var openResultModal = function (title, okText, cancelText, onPrimary) {
Modal.success({
title: title,
content: title === '提交成功' ? '故障已提交并加入历史记录。' : '单据已保存在待处理,可继续编辑或稍后提交。',
okText: okText,
cancelText: cancelText,
okCancel: true,
centered: true,
onOk: function () {
if (onPrimary) onPrimary();
},
onCancel: function () {}
});
};
var handleSave = function () {
// 弱校验:不触发表单校验;落库时解决情况空值按「未解决」与列表一致
faultForm.getFieldsValue();
setFormDirty(false);
openResultModal('保存成功', '返回列表', '确定', goBackList);
};
var runSubmit = function () {
faultForm.validateFields(['plate', 'type', 'source', 'level', 'reportTime', 'desc']).then(function () {
var list = faultForm.getFieldValue('evidence') || [];
if (!list.length) {
message.error('请上传故障证据');
return;
}
setFormDirty(false);
openResultModal('提交成功', '返回列表', '确定', goBackList);
}).catch(function () {});
};
var handleSubmitClick = function () {
Modal.confirm({
title: '确认提交',
content: '故障提交后无法修改,如未完成所有步骤填写请先进行保存。点击确认加入历史记录',
okText: '确认',
cancelText: '取消',
centered: true,
onOk: function () {
runSubmit();
}
});
};
useEffect(function () {
var fn = function (e) {
if (formDirty) {
e.preventDefault();
e.returnValue = '';
}
};
window.addEventListener('beforeunload', fn);
return function () { window.removeEventListener('beforeunload', fn); };
}, [formDirty]);
var specSection = function (title, lines) {
return React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('div', { style: { fontWeight: 600, color: '#1d2129', marginBottom: 8, fontSize: 14 } }, title),
lines.map(function (text, i) {
return React.createElement('div', { key: i, style: { fontSize: 13, color: '#4e5969', lineHeight: 1.75 } }, text);
})
);
};
var renderSpecModal = function () {
return React.createElement(Modal, {
title: '故障信息表单页 — 需求说明',
open: specModalOpen,
onCancel: function () { setSpecModalOpen(false); },
footer: React.createElement(Button, { type: 'primary', onClick: function () { setSpecModalOpen(false); } }, '知道了'),
width: 720,
centered: true,
destroyOnClose: true
},
React.createElement('div', { style: { maxHeight: '70vh', overflowY: 'auto', paddingRight: 4 } },
specSection('3. 故障信息表单页', ['用于新增/编辑故障记录,保存到待处理或提交到历史记录。']),
specSection('3.1 车辆信息卡片(自动带出)', [
'3.1.1 车牌号:必填,下拉可搜索;',
'3.1.2 车辆品牌:禁用,随车牌自动反填;',
'3.1.3 车辆型号:禁用,随车牌自动反填;',
'3.1.4 车辆识别代码:禁用,随车牌自动反填;',
'3.1.5 运营公司:禁用,随车牌自动反填。',
'3.1.6 运营公司枚举统一为:浙江羚牛、上海羚牛、广东羚牛。'
]),
specSection('3.2 故障信息卡片', [
'3.2.1 故障类型:必填,下拉选择;',
'3.2.2 故障来源:必填,下拉选择,选项:客户报告、定期保养、周期性维护、预防性维护、整备;',
'3.2.3 故障等级:必填,下拉选择;',
'3.2.4 故障上报时间:必填,日期时间选择器;',
'3.2.5 解决情况:选填,下拉选择,选项:临时排故、已解决、未解决。'
]),
specSection('3.3 故障描述与证据卡片(顺序已调整)', [
'3.3.1 故障描述:必填,多行文本,默认引导语:在何种状态下 / 产生何种现象 / 导致何种事故;',
'3.3.2 故障证据:提交时必填,上传控件,支持照片/视频/录音;',
'提示文案:支持上传照片、视频、录音。'
]),
specSection('4. 底部操作按钮组', [
'控制保存与提交流程。',
'4.1 保存按钮:保存当前已填写内容,不做严格必填校验,单据留在待处理;',
'4.2 提交按钮:执行严格校验(含带*字段及故障证据);',
'4.3 点击提交先弹确认提示:故障提交后无法修改… 按钮:取消 / 确认;',
'4.4 提交成功后提示提交成功,并提供确定/返回列表;',
'4.5 保存成功后提示保存成功,并提供确定/返回列表。'
]),
specSection('5. 联动与校验规则', [
'5.1 车牌联动:自动反填车辆品牌、车辆型号、车辆识别代码、运营公司;',
'5.2 保存校验:弱校验(允许未填必填项);',
'5.3 提交校验:强校验(必填项+故障证据);',
'5.4 解决情况默认值兜底:未填写时按未解决处理(列表展示与保存一致)。'
]),
specSection('6. 退出与返回拦截规则', [
'6.1 表单发生修改且未保存时,触发返回/关闭/刷新拦截;',
'6.2 弹窗文案:确定要退出吗?未保存的数据将丢失;',
'6.3 按钮:确定 / 取消;',
'6.4 已保存后再退出,不触发该拦截。'
])
)
);
};
return React.createElement(Layout, { className: 'arco-theme-overrides', style: { minHeight: '100vh', background: '#f2f3f5', fontFamily: 'Inter, Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif' } },
React.createElement('style', null, `
.arco-theme-overrides .ant-btn { border-radius: 4px; }
.arco-theme-overrides .ant-btn-primary { background-color: #165dff; border-color: #165dff; }
.arco-theme-overrides .ant-btn-primary:hover { background-color: #4080ff; border-color: #4080ff; }
.arco-grouped-form-page { display: flex; flex-direction: column; min-height: 100vh; }
.arco-grouped-form-page-content { flex: 1; padding: 16px 20px 24px; }
.arco-grouped-form-page .ant-card { margin-bottom: 16px; border-radius: 4px; border: none; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
.arco-grouped-form-page .ant-card-head { border-bottom: none; padding: 20px 24px 0; min-height: auto; }
.arco-grouped-form-page .ant-card-head-title { font-size: 16px; font-weight: 500; color: #1d2129; padding: 0; }
.arco-grouped-form-page .ant-card-body { padding: 24px; }
.arco-grouped-form-page .ant-form-vertical .ant-form-item-label { padding-bottom: 8px; height: auto; line-height: 1.5715; }
.arco-grouped-form-page .ant-form-item { margin-bottom: 24px; }
.arco-grouped-form-page .ant-input,
.arco-grouped-form-page .ant-select-selector,
.arco-grouped-form-page .ant-picker,
.arco-grouped-form-page .ant-input-affix-wrapper { background-color: #f2f3f5; border: 1px solid #e5e6eb; border-radius: 2px; transition: all 0.1s cubic-bezier(0, 0, 1, 1); }
.arco-grouped-form-page .ant-input:hover,
.arco-grouped-form-page .ant-select:not(.ant-select-disabled):hover .ant-select-selector,
.arco-grouped-form-page .ant-picker:hover,
.arco-grouped-form-page .ant-input-affix-wrapper:hover { background-color: #f2f3f5; border-color: #165dff; }
.arco-grouped-form-page .ant-input:focus,
.arco-grouped-form-page .ant-input-focused,
.arco-grouped-form-page .ant-select-focused .ant-select-selector,
.arco-grouped-form-page .ant-picker-focused,
.arco-grouped-form-page .ant-input-affix-wrapper-focused { background-color: #fff; border: 1px solid #165dff !important; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.2) !important; outline: 0; }
.arco-grouped-form-page .ant-input[disabled],
.arco-grouped-form-page .ant-select-disabled .ant-select-selector,
.arco-grouped-form-page .ant-picker-disabled { color: #86909c; background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper[disabled] { background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper > input.ant-input { background-color: transparent; }
.arco-grouped-form-page .ant-input-affix-wrapper > input.ant-input:focus { background-color: transparent; box-shadow: none !important; border: none !important; }
.arco-grouped-form-footer { background: #fff; padding: 16px 24px; border-top: 1px solid #e5e6eb; display: flex; justify-content: flex-end; align-items: center; gap: 12px; position: sticky; bottom: 0; z-index: 100; box-shadow: 0 -2px 10px rgba(0,0,0,0.05); }
.arco-grouped-form-footer .ant-btn { border-radius: 5px; height: 32px; padding: 4px 16px; font-size: 14px; }
.arco-theme-overrides .ant-breadcrumb { color: #86909c; font-size: 14px; white-space: nowrap; flex-shrink: 0; margin-bottom: 0; }
.arco-theme-overrides .ant-breadcrumb a { color: #4e5969; }
.arco-theme-overrides .ant-breadcrumb a:hover { color: #165dff; background-color: transparent; }
.arco-theme-overrides .ant-form-item-label > label { color: #4e5969; white-space: nowrap; }
.arco-theme-overrides .ant-form-item-label > label::after { display: none !important; content: "" !important; margin: 0 !important; }
`),
React.createElement('div', { className: 'arco-grouped-form-page' },
React.createElement('div', { className: 'arco-grouped-form-page-content' },
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, flexWrap: 'nowrap', gap: 12 } },
React.createElement(Breadcrumb, {
separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'),
items: [
{ title: '首页' },
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: React.createElement('a', { href: '#', onClick: function (e) { e.preventDefault(); requestExit(); } }, '故障管理') },
{ title: React.createElement('span', { style: { color: '#1d2129' } }, '新建故障单') }
]
}),
React.createElement(Button, {
type: 'link',
icon: React.createElement(FileTextIcon, null),
style: { display: 'flex', alignItems: 'center', gap: 4, padding: '0 4px', color: '#165dff', fontWeight: 500, flexShrink: 0 },
onClick: function () { setSpecModalOpen(true); }
}, '查看需求说明')
),
React.createElement(Form, {
form: faultForm,
layout: 'vertical',
onValuesChange: function () {
setFormDirty(true);
}
},
React.createElement(Card, { title: '车辆信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车牌号', name: 'plate', rules: [{ required: true, message: '请选择车牌号' }] },
React.createElement(Select, {
placeholder: '请选择车牌号(可搜索)',
options: plateOptions,
showSearch: true,
optionFilterProp: 'label',
onChange: function (val, option) {
if (option) {
faultForm.setFieldsValue({
brand: option.brand,
model: option.model,
company: option.company,
vin: option.vin
});
} else {
faultForm.setFieldsValue({ brand: undefined, model: undefined, company: undefined, vin: undefined });
}
}
})
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆品牌', name: 'brand' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆型号', name: 'model' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆识别代码', name: 'vin' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '运营公司', name: 'company' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
)
)
),
React.createElement(Card, { title: '故障信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障类型', name: 'type', rules: [{ required: true, message: '请选择故障类型' }] },
React.createElement(Select, { placeholder: '请选择故障类型', options: faultTypeOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障来源', name: 'source', rules: [{ required: true, message: '请选择故障来源' }] },
React.createElement(Select, { placeholder: '请选择故障来源', options: faultSourceOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障等级', name: 'level', rules: [{ required: true, message: '请选择故障等级' }] },
React.createElement(Select, { placeholder: '请选择故障等级', options: faultLevelOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障上报时间', name: 'reportTime', rules: [{ required: true, message: '请选择故障上报时间' }] },
React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择上报时间', showTime: true })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '解决情况', name: 'status', extra: '选填;保存时未填按「未解决」处理' },
React.createElement(Select, { allowClear: true, placeholder: '请选择(可选)', options: resolveStatusOptions })
)
)
)
),
React.createElement(Card, { title: '故障描述与证据', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, { label: '故障描述', name: 'desc', rules: [{ required: true, message: '请填写故障描述' }] },
React.createElement(Input.TextArea, {
placeholder: DESC_PLACEHOLDER,
style: { minHeight: 104, height: 104, resize: 'none' }
})
)
),
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, {
label: '故障证据',
name: 'evidence',
valuePropName: 'fileList',
getValueFromEvent: function (e) { return (e && e.fileList) ? e.fileList : []; }
},
React.createElement(Upload, {
listType: 'picture-card',
beforeUpload: function () { return false; },
multiple: true,
accept: 'image/*,video/*,audio/*'
},
React.createElement('div', null,
React.createElement(PlusIcon, null),
React.createElement('div', { style: { marginTop: 8 } }, '上传文件')
)
),
React.createElement('div', { style: { fontSize: 12, color: '#86909c', marginTop: 8 } }, '支持上传照片、视频、录音')
)
)
)
)
)
),
React.createElement('div', { className: 'arco-grouped-form-footer' },
React.createElement(Button, { onClick: requestExit, style: { borderRadius: 5 } }, '取消'),
React.createElement(Button, { onClick: handleSave, style: { borderRadius: 5 } }, '保存'),
React.createElement(Button, { type: 'primary', onClick: handleSubmitClick, style: { borderRadius: 5 } }, '提交')
)
),
renderSpecModal()
);
};
if (typeof module !== 'undefined' && module.exports) module.exports = Component;

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// 【重要】必须使用 const Component 作为组件变量名
// 运维管理 - 车辆业务 - 替换车管理 - 查看2026年3月3日版本
// 运维管理 - 车辆业务 - 替换车管理 - 查看
const Component = function () {
var useState = React.useState;
@@ -10,61 +10,290 @@ const Component = function () {
var Input = antd.Input;
var Button = antd.Button;
var Modal = antd.Modal;
var Tag = antd.Tag;
var Steps = antd.Steps;
var EMPTY_PROJECT = {
contractId: '',
projectId: '',
projectName: '',
projectType: '',
customerName: '',
contractCode: '',
deliveryRegion: ''
};
var activeContracts = [
{
contractId: 'c1',
contractStatus: '合同进行中',
projectId: 'p1',
projectName: '嘉兴氢能示范项目',
projectType: '租赁',
contractCode: 'HT-ZL-2025-001',
customerName: '嘉兴某某物流有限公司',
deliveryRegion: '浙江省-嘉兴市'
}
];
var MOCK_PAIRS = [
{
id: 'pair_1',
replaceType: '永久替换',
replaceReason: '车辆原因',
replaceReasonDesc: '原车故障需维修,临时用替换车保障客户用车。',
originalPlate: '浙A12345',
originalBrand: '东风',
originalModel: 'DFH1180',
contractId: 'c1',
replacePlate: '浙A67890',
replaceBrand: '福田',
replaceModel: 'BJ1180'
},
{
id: 'pair_2',
replaceType: '临时替换',
replaceReason: '客户原因',
replaceReasonDesc: '',
originalPlate: '浙A55555',
originalBrand: '重汽',
originalModel: 'ZZ1160',
contractId: 'c1',
replacePlate: '浙A66666',
replaceBrand: '江淮',
replaceModel: 'HFC1190'
}
];
var contractById = (function () {
var map = {};
activeContracts.forEach(function (c) { map[c.contractId] = c; });
return map;
})();
var pairs = MOCK_PAIRS;
var requirementModalVisible = useState(false);
var setRequirementModalVisible = requirementModalVisible[1];
// 模拟:根据已填信息反查的一条替换车记录(实际由路由参数或接口拉取)
var detail = useState({
projectName: '嘉兴氢能示范项目',
contractCode: 'HT-ZL-2025-001',
customerName: '嘉兴某某物流有限公司',
contactPerson: '张三',
signDate: '2025-01-15',
contactPhone: '13800138001',
businessDept: '业务1部',
businessPerson: '张经理',
replaceDate: '2026-02-18',
replaceType: '永久替换',
replaceReason: '车辆原因',
replaceReasonDesc: '原车故障需维修,临时用替换车保障客户用车。',
originalPlate: '浙A12345',
originalVin: 'LGHXCAE28M1234567',
originalBrand: '东风',
originalModel: 'DFH1180',
replacePlate: '浙A67890',
replaceVin: 'LGHXCAE28M6789012',
replaceBrand: '福田',
replaceModel: 'BJ1180'
});
var data = detail[0];
var projectInfo = (function () {
var anchor = pairs.find(function (p) { return p.originalPlate && p.contractId; });
if (!anchor || !anchor.contractId) return EMPTY_PROJECT;
var c = contractById[anchor.contractId];
if (!c) return EMPTY_PROJECT;
return {
contractId: c.contractId,
projectId: c.projectId,
projectName: c.projectName,
projectType: c.projectType,
customerName: c.customerName,
contractCode: c.contractCode,
deliveryRegion: c.deliveryRegion
};
})();
var handleBack = function () {
// 返回替换车管理列表页(实际为路由或平台跳转)
if (window.__replaceCarBack) {
window.__replaceCarBack();
} else {
antd.message.info('返回替换车管理列表(原型)');
}
};
var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' };
var cardStyle = { marginBottom: 16 };
var labelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' };
var formRowStyle = { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', marginBottom: 16 };
var formItemStyle = { marginBottom: 12 };
// 审批情况(竖排步骤条,参照提车应收款-审核)
var approvalSteps = [
{ title: '业务部主管', person: '姚守涛', status: 'finish', approveTime: '2026-02-20 09:30' },
{ title: '事业部主管', person: '尚建华', status: 'finish', approveTime: '2026-02-20 10:15' },
{ title: '运维主管', person: '王运维', status: 'finish', approveTime: '2026-02-20 11:00' }
];
var requirementContent = '替换车管理-查看2026年3月3日版本\n一个「数字化资产ONEOS运管平台」中的「运维管理」「车辆业务」「替换车管理」「查看」模块\n1.面包屑:\n#运维管理-车辆业务-替换车管理-查看\n页面由选择项目、替换车详情、两个单独卡片组成\n\n2.选择项目:\n#可通过选择进行中的车辆租赁合同,拉取租赁合同中对应车辆进行替换;\n2.1.项目名称:根据已填信息反查,不可编辑;\n2.2.合同编码:根据项目名称自动反查,不可编辑;\n2.3.客户名称:根据项目名称自动反查,不可编辑;\n2.4.对接人:根据项目名称自动反查,不可编辑;\n2.5.合同签订时间:根据项目名称自动反查,不可编辑;\n2.6.客户联系电话:根据项目名称自动反查,不可编辑;\n2.7.业务部门:根据项目名称自动反查,不可编辑;\n2.8.业务人员:根据项目名称自动反查,不可编辑;\n\n3.替换车详情:\n3.1.替换时间日期选择器禁用显示退换时间格式为YYYY-MM-DD\n3.2.替换类型:选择器,根据已填信息反查,不可编辑;\n 3.2.1.类型为永久替换时,该申请通过审核后替换车进行交车(交车时间为流程结束当天),由运维手动将被替换车进行还车;\n 3.2.2.类型为临时替换时,该申请通过审核后替换车进行交车(交车时间为流程结束当天),被替换车不用还车,在被替换车重新交付客户时,由运维手动将替换车进行还车;\n 重新生成交车任务时,交车地点会自动继承自合同,由对应区域运维人员才能操作;\n 交车任务完成后,所有涉及到被替换车辆显示(例如车辆租赁合同、租赁账单、提车应收款等功能)会替换为新替换车的对应信息,如果是临时替换,在新替换车完成还车后,对应车辆记录会恢复为原有车辆数据。如果是永久替换,则由运维自主进行被替换车辆还车;\n3.3.替换原因:选择器,根据已填信息反查,不可编辑;\n3.4.替换原因说明:文本域,根据已填信息反查,不可编辑;\n3.5.被替换车牌号:选择器,根据已填信息反查,不可编辑;\n3.6.被替换车识别代码:输入框(禁用),选择被替换车车牌号后自动反写该车识别代码;\n3.7.被替换车品牌:输入框(禁用),选择被替换车车牌号后自动反写该车品牌;\n3.8.被替换车型号:输入框(禁用),选择被替换车车牌号后自动反写该车型号;\n3.9.替换车车牌号:选择器,根据已填信息反查,不可编辑;\n3.10.替换车识别代码:输入框(禁用),选择替换车车牌号后自动反写该车识别代码;\n3.11.替换车品牌:输入框(禁用),选择替换车车牌号后自动反写该车品牌;\n3.12.替换车型号:输入框(禁用),选择替换车车牌号后自动反写该车型号;\n\n下方为返回按钮\n4.1.点击返回,返回替换车管理列表页;';
return React.createElement('div', { style: layoutStyle },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
var handleBack = function () {
if (window.__replaceCarBack) window.__replaceCarBack();
else antd.message.info('返回替换车管理列表(原型)');
};
var pageCss =
'.vr-add-page{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}' +
'.vr-add-page .vr-page-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px}' +
'.vr-add-page .vr-main-card{border-radius:16px;border:none;box-shadow:0 4px 24px -6px rgba(15,23,42,0.08),0 0 0 1px rgba(15,23,42,0.05)}' +
'.vr-add-page .vr-main-card>.ant-card-head{border-bottom:1px solid #f1f5f9;padding:16px 24px;min-height:auto}' +
'.vr-add-page .vr-main-card>.ant-card-head .ant-card-head-title{font-size:16px;font-weight:600;color:#0f172a;padding:0}' +
'.vr-add-page .vr-main-card>.ant-card-body{padding:20px 24px 24px}' +
'.vr-add-page .vr-approval-card{border-radius:16px;border:none;box-shadow:0 4px 24px -6px rgba(15,23,42,0.08),0 0 0 1px rgba(15,23,42,0.05);margin-top:16px}' +
'.vr-add-page .vr-pair-list{display:flex;flex-direction:column;gap:16px}' +
'.vr-add-page .vr-pair-card{border-radius:12px;border:1px solid #e2e8f0;background:#f8fafc;overflow:hidden}' +
'.vr-add-page .vr-pair-card__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;background:#f0f9ff;border-bottom:1px solid #e0f2fe}' +
'.vr-add-page .vr-pair-card__title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:600;color:#0f172a;flex-wrap:wrap}' +
'.vr-add-page .vr-pair-card__arrow{display:inline-flex;align-items:center;justify-content:center;color:#1677ff;font-size:16px;font-weight:600;line-height:1;padding:0 2px;flex-shrink:0}' +
'.vr-add-page .vr-pair-card__index{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;padding:0 8px;border-radius:6px;background:#1677ff;color:#fff;font-size:12px;font-weight:700}' +
'.vr-add-page .vr-pair-card__body{padding:16px}' +
'.vr-add-page .vr-block{margin-bottom:14px}' +
'.vr-add-page .vr-block:last-child{margin-bottom:0}' +
'.vr-add-page .vr-block-label{font-size:12px;font-weight:600;color:#475569;margin-bottom:10px}' +
'.vr-add-page .vr-block-label--new{color:#047857}' +
'.vr-add-page .vr-form-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 16px}' +
'.vr-add-page .vr-form-grid--reason .vr-field:last-child{grid-column:1/-1}' +
'@media(max-width:900px){.vr-add-page .vr-form-grid{grid-template-columns:1fr}}' +
'.vr-add-page .vr-field{display:flex;flex-direction:column;gap:6px;min-width:0}' +
'.vr-add-page .vr-field__label{font-size:13px;font-weight:500;color:#334155}' +
'.vr-add-page .vr-swap-divider{display:flex;align-items:center;gap:12px;margin:14px 0;color:#94a3b8;font-size:12px;font-weight:500}' +
'.vr-add-page .vr-swap-divider::before,.vr-add-page .vr-swap-divider::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,transparent,#cbd5e1,transparent)}' +
'.vr-add-page .vr-swap-divider__icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background:#eff6ff;color:#1677ff;font-size:14px}' +
'.vr-add-page .vr-vehicle-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 16px;padding:12px 14px;margin-bottom:14px;border-radius:8px;background:#fffbeb;border:1px solid #fde68a}' +
'.vr-add-page .vr-project-panel{margin-top:20px;padding:16px 18px;border-radius:12px;background:#f8fafc;border:1px solid #e2e8f0}' +
'.vr-add-page .vr-project-panel__title{font-size:14px;font-weight:600;color:#0f172a;margin-bottom:14px}' +
'.vr-add-page .vr-project-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 20px}' +
'.vr-add-page .vr-readonly{display:flex;flex-direction:column;gap:4px}' +
'.vr-add-page .vr-readonly__label{font-size:12px;color:#64748b;font-weight:500}' +
'.vr-add-page .vr-readonly__value{font-size:14px;color:#0f172a;font-weight:500}' +
'.vr-add-page .vr-footer{display:flex;gap:10px;margin-top:24px;padding-top:20px;border-top:1px solid #f1f5f9}' +
'.vr-req-doc{padding:4px 2px 8px}' +
'.vr-req-doc__meta{font-size:12px;color:#64748b;line-height:1.6;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #f1f5f9}' +
'.vr-req-doc__section{margin-bottom:20px}' +
'.vr-req-doc__title{font-size:15px;font-weight:600;color:#0f172a;margin:0 0 10px}' +
'.vr-req-doc__line{font-size:13px;color:#475569;line-height:1.75;margin:0 0 6px}' +
'.vr-req-doc__line--sub{padding-left:14px;color:#64748b}';
function specSection(title, lines) {
return React.createElement(
'section',
{ className: 'vr-req-doc__section' },
React.createElement('h3', { className: 'vr-req-doc__title' }, title),
(lines || []).map(function (text, i) {
var isSub = text.indexOf(' ') === 0;
return React.createElement('p', { key: i, className: 'vr-req-doc__line' + (isSub ? ' vr-req-doc__line--sub' : '') }, text);
})
);
}
function renderRequirementDoc() {
return React.createElement(
'div',
{ className: 'vr-req-doc' },
React.createElement('div', { className: 'vr-req-doc__meta' }, '数字化资产 ONEOS 运管平台 · 运维管理 · 车辆业务 · 替换车管理 · 查看'),
specSection('1. 页面定位', ['只读查看替换车申请详情,布局与新增/编辑一致,不可修改任何字段。']),
specSection('2. 展示内容', [
'2.1 车辆替换明细:每条被替换车一张卡片,含被替换车信息、替换说明、替换车辆;卡片标题展示被替换与替换车牌。',
'2.2 项目信息:客户名称、项目名称、项目类型(全单一份)。',
'2.3 审批情况:竖向步骤条展示审批节点、审批人、审批时间。'
]),
specSection('3. 操作', ['底部仅「返回」按钮,返回替换车管理列表。'])
);
}
function renderField(label, node) {
return React.createElement(
'div',
{ className: 'vr-field' },
React.createElement('div', { className: 'vr-field__label' }, label),
node
);
}
function renderPairCard(pair, index) {
return React.createElement(
'article',
{ key: pair.id, className: 'vr-pair-card' },
React.createElement(
'div',
{ className: 'vr-pair-card__head' },
React.createElement(
'div',
{ className: 'vr-pair-card__title' },
React.createElement('span', { className: 'vr-pair-card__index' }, index + 1),
React.createElement('span', null, '车辆替换'),
React.createElement(Tag, { style: { margin: 0 } }, pair.originalPlate),
pair.replacePlate
? React.createElement('span', { className: 'vr-pair-card__arrow', 'aria-hidden': true }, '→')
: null,
pair.replacePlate
? React.createElement(Tag, { color: 'processing', style: { margin: 0 } }, pair.replacePlate)
: null
)
),
React.createElement(
'div',
{ className: 'vr-pair-card__body' },
React.createElement(
'div',
{ className: 'vr-vehicle-summary' },
renderField('车牌号', React.createElement(Input, { value: pair.originalPlate, disabled: true })),
renderField('品牌', React.createElement(Input, { value: pair.originalBrand, disabled: true })),
renderField('型号', React.createElement(Input, { value: pair.originalModel, disabled: true }))
),
React.createElement(
'div',
{ className: 'vr-block' },
React.createElement('div', { className: 'vr-block-label' }, '替换说明'),
React.createElement(
'div',
{ className: 'vr-form-grid vr-form-grid--reason' },
renderField('替换类型', React.createElement(Input, { value: pair.replaceType, disabled: true })),
renderField('替换原因', React.createElement(Input, { value: pair.replaceReason, disabled: true })),
renderField(
'替换原因说明',
React.createElement(Input.TextArea, {
value: pair.replaceReasonDesc || '—',
disabled: true,
rows: 2,
style: { width: '100%' }
})
)
)
),
React.createElement(
'div',
{ className: 'vr-swap-divider' },
React.createElement('span', { className: 'vr-swap-divider__icon' }, '↓'),
React.createElement('span', null, '替换为')
),
React.createElement(
'div',
{ className: 'vr-block' },
React.createElement('div', { className: 'vr-block-label vr-block-label--new' }, '替换车辆'),
React.createElement(
'div',
{ className: 'vr-form-grid' },
renderField('新车', React.createElement(Input, { value: pair.replacePlate, disabled: true })),
renderField('品牌', React.createElement(Input, { value: pair.replaceBrand, disabled: true })),
renderField('型号', React.createElement(Input, { value: pair.replaceModel, disabled: true }))
)
)
)
);
}
function renderProjectPanel() {
return React.createElement(
'section',
{ className: 'vr-project-panel' },
React.createElement('div', { className: 'vr-project-panel__title' }, '项目信息'),
React.createElement(
'div',
{ className: 'vr-project-grid' },
React.createElement(
'div',
{ className: 'vr-readonly' },
React.createElement('span', { className: 'vr-readonly__label' }, '客户名称'),
React.createElement('span', { className: 'vr-readonly__value' }, projectInfo.customerName)
),
React.createElement(
'div',
{ className: 'vr-readonly' },
React.createElement('span', { className: 'vr-readonly__label' }, '项目名称'),
React.createElement('span', { className: 'vr-readonly__value' }, projectInfo.projectName)
),
React.createElement(
'div',
{ className: 'vr-readonly' },
React.createElement('span', { className: 'vr-readonly__label' }, '项目类型'),
React.createElement(
'span',
{ className: 'vr-readonly__value' },
React.createElement(Tag, { color: projectInfo.projectType === '自营' ? 'purple' : 'blue', style: { margin: 0 } }, projectInfo.projectType)
)
)
)
);
}
return React.createElement(
'div',
{ className: 'vr-add-page', style: { padding: '20px 24px 32px', minHeight: '100vh', background: 'linear-gradient(165deg,#eef4ff 0%,#f5f7fa 42%,#f0f2f5 100%)' } },
React.createElement('style', null, pageCss),
React.createElement(
'header',
{ className: 'vr-page-header' },
React.createElement(Breadcrumb, {
items: [
{ title: '运维管理' },
@@ -75,126 +304,49 @@ const Component = function () {
}),
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { setRequirementModalVisible(true); } }, '查看需求说明')
),
React.createElement(Card, { title: '选择项目', style: cardStyle },
React.createElement('div', { style: formRowStyle },
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '项目名称'),
React.createElement(Input, { value: data.projectName || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '合同编码'),
React.createElement(Input, { value: data.contractCode || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '客户名称'),
React.createElement(Input, { value: data.customerName || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '对接人'),
React.createElement(Input, { value: data.contactPerson || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '合同签订时间'),
React.createElement(Input, { value: data.signDate || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '客户联系电话'),
React.createElement(Input, { value: data.contactPhone || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '业务部门'),
React.createElement(Input, { value: data.businessDept || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '业务人员'),
React.createElement(Input, { value: data.businessPerson || '', disabled: true })
React.createElement(
Card,
{
className: 'vr-main-card',
title: React.createElement(
'span',
null,
'查看替换车 ',
React.createElement(Tag, { style: { marginLeft: 8, fontWeight: 400 } }, pairs.length + ' 辆车')
)
)
},
React.createElement('div', { className: 'vr-pair-list' }, pairs.map(function (pair, index) { return renderPairCard(pair, index); })),
renderProjectPanel(),
React.createElement('div', { className: 'vr-footer' }, React.createElement(Button, { size: 'large', onClick: handleBack }, '返回'))
),
React.createElement(Card, { title: '替换车详情', style: cardStyle },
React.createElement('div', { style: formRowStyle },
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换时间'),
React.createElement(Input, { value: data.replaceDate || '', disabled: true, placeholder: 'YYYY-MM-DD' })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换类型'),
React.createElement(Input, { value: data.replaceType || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换原因'),
React.createElement(Input, { value: data.replaceReason || '', disabled: true })
),
React.createElement('div', { style: Object.assign({}, formItemStyle, { gridColumn: '1 / -1' }) },
React.createElement('div', { style: labelStyle }, '替换原因说明'),
React.createElement(Input.TextArea, { value: data.replaceReasonDesc || '', disabled: true, rows: 3, style: { width: '100%' } })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '被替换车牌号'),
React.createElement(Input, { value: data.originalPlate || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '被替换车识别代码'),
React.createElement(Input, { value: data.originalVin || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '被替换车品牌'),
React.createElement(Input, { value: data.originalBrand || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '被替换车型号'),
React.createElement(Input, { value: data.originalModel || '', disabled: true })
),
React.createElement('div', { style: { gridColumn: '1 / -1', width: '100%' } }),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换车车牌号'),
React.createElement(Input, { value: data.replacePlate || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换车识别代码'),
React.createElement(Input, { value: data.replaceVin || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换车品牌'),
React.createElement(Input, { value: data.replaceBrand || '', disabled: true })
),
React.createElement('div', { style: formItemStyle },
React.createElement('div', { style: labelStyle }, '替换车型号'),
React.createElement(Input, { value: data.replaceModel || '', disabled: true })
)
),
React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 24 } },
React.createElement(Button, { onClick: handleBack }, '返回')
)
),
React.createElement(Card, { title: '审批情况', style: cardStyle },
React.createElement(
Card,
{ className: 'vr-approval-card', title: '审批情况' },
React.createElement(Steps, {
direction: 'vertical',
current: approvalSteps.length,
items: approvalSteps.map(function (s) {
var statusText = s.status === 'finish' ? '审批通过' : '待审批';
var desc = React.createElement('div', { style: { fontSize: 13, color: 'rgba(0,0,0,0.65)', marginTop: 4 } },
React.createElement('div', null, '审批状态:', statusText),
React.createElement('div', null, '审批人:', s.person || '—'),
s.approveTime ? React.createElement('div', null, '审批时间:', s.approveTime) : null
);
return {
title: s.title,
description: desc,
status: s.status === 'finish' ? 'finish' : 'wait'
status: s.status === 'finish' ? 'finish' : 'wait',
description: React.createElement(
'div',
{ style: { fontSize: 13, color: 'rgba(0,0,0,0.65)', marginTop: 4 } },
React.createElement('div', null, '审批状态:', s.status === 'finish' ? '审批通过' : '待审批'),
React.createElement('div', null, '审批人:', s.person || '—'),
s.approveTime ? React.createElement('div', null, '审批时间:', s.approveTime) : null
)
};
})
})
),
React.createElement(Modal, {
title: '需求说明',
title: '替换车管理 - 查看 · 需求说明',
open: requirementModalVisible[0],
onCancel: function () { setRequirementModalVisible(false); },
width: 720,
footer: React.createElement(Button, { onClick: function () { setRequirementModalVisible(false); } }, '关闭'),
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
}, React.createElement('div', { style: { padding: '8px 0' } },
React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent))
)
width: 760,
footer: React.createElement(Button, { type: 'primary', onClick: function () { setRequirementModalVisible(false); } }, '关闭'),
bodyStyle: { maxHeight: '72vh', overflow: 'auto', paddingTop: 8 }
}, renderRequirementDoc())
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,11 @@ const Component = function () {
var Modal = antd.Modal;
var Input = antd.Input;
var message = antd.message;
var Tag = antd.Tag;
var Empty = antd.Empty;
var App = antd.App;
var Badge = antd.Badge;
var Popover = antd.Popover;
var RangePicker = DatePicker.RangePicker;
@@ -50,8 +55,29 @@ const Component = function () {
var _withdrawModalRecord = useState(null);
var _toPermanentModalVisible = useState(false);
var _toPermanentModalRecord = useState(null);
var _deleteModalVisible = useState(false);
var _deleteModalRecord = useState(null);
var _deletedIds = useState([]);
var _requirementModalVisible = useState(false);
function ensureReplaceDateTime(dateStr, fallbackTime) {
if (!dateStr) return '';
if (dateStr.length > 10) return dateStr;
return dateStr + ' ' + (fallbackTime || '09:00:00');
}
function enrichListRow(row, timeSuffix) {
var next = Object.assign({}, row);
next.replaceDate = ensureReplaceDateTime(row.replaceDate, timeSuffix);
if (!row.currentApprover) {
if (row.approvalStatus === '审批中') next.currentApprover = '姚守涛';
else if (row.approvalStatus === '待审批') next.currentApprover = '业务部主管';
else if (row.approvalStatus === '审批驳回') next.currentApprover = '尚建华';
else next.currentApprover = '—';
}
return next;
}
var replaceTypeOptions = [
{ value: '永久替换', label: '永久替换' },
{ value: '临时替换', label: '临时替换' }
@@ -89,12 +115,12 @@ const Component = function () {
];
// 进行中:未结束、暂存,审批状态为 待审批、审批中、审批驳回、未提交、撤回
var ongoingList = [
{ id: 'o1', replaceDate: '2025-03-05', replaceType: '临时替换', projectName: '嘉兴氢能示范项目', approvalStatus: '待审批', originalPlate: '浙A12345', originalBrand: '东风', originalModel: 'DFH1180', replacePlate: '浙A67890', replaceBrand: '福田', replaceModel: 'BJ1180', replaceReason: '车辆原因', replaceReasonDesc: '原车维修', creator: '张三', createTime: '2025-03-01 10:00' },
{ id: 'o2', replaceDate: '2025-03-06', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '审批中', originalPlate: '浙B11111', originalBrand: '江淮', originalModel: 'HFC1180', replacePlate: '浙B22222', replaceBrand: '重汽', replaceModel: 'ZZ1180', replaceReason: '客户原因', replaceReasonDesc: '客户要求换型', creator: '李四', createTime: '2025-03-01 14:30' },
{ id: 'o3', replaceDate: '2025-03-07', replaceType: '临时替换', projectName: '杭州城配租赁项目', approvalStatus: '审批驳回', originalPlate: '浙C33333', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙C44444', replaceBrand: '福田', replaceModel: 'BJ1190', replaceReason: '车辆原因', replaceReasonDesc: '事故替换', creator: '王五', createTime: '2025-03-02 09:15' },
{ id: 'o4', replaceDate: '2025-03-08', replaceType: '永久替换', projectName: '嘉兴氢能示范项目', approvalStatus: '未提交', originalPlate: '浙A55555', originalBrand: '重汽', originalModel: 'ZZ1160', replacePlate: '浙A66666', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '车辆原因', replaceReasonDesc: '保养替换', creator: '赵六', createTime: '2025-03-02 16:00' },
{ id: 'o5', replaceDate: '2025-03-09', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '撤回', originalPlate: '浙F77777', originalBrand: '福田', originalModel: 'BJ1180', replacePlate: '浙F88888', replaceBrand: '东风', replaceModel: 'DFH1180', replaceReason: '客户原因', replaceReasonDesc: '年检替换', creator: '张三', createTime: '2025-03-03 11:20' },
var ongoingListRaw = [
{ id: 'o1', replaceDate: '2025-03-05', replaceType: '临时替换', projectName: '嘉兴氢能示范项目', approvalStatus: '待审批', currentApprover: '业务部主管', originalPlate: '浙A12345', originalBrand: '东风', originalModel: 'DFH1180', replacePlate: '浙A67890', replaceBrand: '福田', replaceModel: 'BJ1180', replaceReason: '车辆原因', replaceReasonDesc: '原车维修', creator: '张三', createTime: '2025-03-01 10:00:00' },
{ id: 'o2', replaceDate: '2025-03-06', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '审批中', currentApprover: '姚守涛', originalPlate: '浙B11111', originalBrand: '江淮', originalModel: 'HFC1180', replacePlate: '浙B22222', replaceBrand: '重汽', replaceModel: 'ZZ1180', replaceReason: '客户原因', replaceReasonDesc: '客户要求换型', creator: '李四', createTime: '2025-03-01 14:30:00' },
{ id: 'o3', replaceDate: '2025-03-07', replaceType: '临时替换', projectName: '杭州城配租赁项目', approvalStatus: '审批驳回', currentApprover: '尚建华', originalPlate: '浙C33333', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙C44444', replaceBrand: '福田', replaceModel: 'BJ1190', replaceReason: '车辆原因', replaceReasonDesc: '事故替换', creator: '王五', createTime: '2025-03-02 09:15:00' },
{ id: 'o4', replaceDate: '2025-03-08', replaceType: '永久替换', projectName: '嘉兴氢能示范项目', approvalStatus: '未提交', currentApprover: '—', originalPlate: '浙A55555', originalBrand: '重汽', originalModel: 'ZZ1160', replacePlate: '浙A66666', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '车辆原因', replaceReasonDesc: '保养替换', creator: '赵六', createTime: '2025-03-02 16:00:00' },
{ id: 'o5', replaceDate: '2025-03-09', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '撤回', currentApprover: '—', originalPlate: '浙F77777', originalBrand: '福田', originalModel: 'BJ1180', replacePlate: '浙F88888', replaceBrand: '东风', replaceModel: 'DFH1180', replaceReason: '客户原因', replaceReasonDesc: '年检替换', creator: '张三', createTime: '2025-03-03 11:20:00' },
{ id: 'o6', replaceDate: '2025-03-10', replaceType: '永久替换', projectName: '杭州城配租赁项目', approvalStatus: '待审批', originalPlate: '浙A11201', originalBrand: '江淮', originalModel: 'HFC1160', replacePlate: '浙A11202', replaceBrand: '东风', replaceModel: 'DFH1160', replaceReason: '车辆原因', replaceReasonDesc: '发动机故障', creator: '李四', createTime: '2025-03-04 08:45' },
{ id: 'o7', replaceDate: '2025-03-11', replaceType: '临时替换', projectName: '嘉兴氢能示范项目', approvalStatus: '审批中', originalPlate: '浙B22301', originalBrand: '重汽', originalModel: 'ZZ1160', replacePlate: '浙B22302', replaceBrand: '福田', replaceModel: 'BJ1160', replaceReason: '客户原因', replaceReasonDesc: '临时增运力', creator: '王五', createTime: '2025-03-04 13:00' },
{ id: 'o8', replaceDate: '2025-03-12', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '审批驳回', originalPlate: '浙C33401', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙C33402', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '车辆原因', replaceReasonDesc: '底盘大修', creator: '赵六', createTime: '2025-03-05 10:20' },
@@ -109,12 +135,16 @@ const Component = function () {
{ id: 'o17', replaceDate: '2025-03-21', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '审批中', originalPlate: '浙F30301', originalBrand: '福田', originalModel: 'BJ1190', replacePlate: '浙F30302', replaceBrand: '重汽', replaceModel: 'ZZ1190', replaceReason: '客户原因', replaceReasonDesc: '区域调配', creator: '张三', createTime: '2025-03-09 15:00' },
{ id: 'o18', replaceDate: '2025-03-22', replaceType: '永久替换', projectName: '杭州城配租赁项目', approvalStatus: '审批驳回', originalPlate: '浙A40401', originalBrand: '江淮', originalModel: 'HFC1160', replacePlate: '浙A40402', replaceBrand: '东风', replaceModel: 'DFH1160', replaceReason: '车辆原因', replaceReasonDesc: '车身锈蚀', creator: '李四', createTime: '2025-03-10 09:45' },
{ id: 'o19', replaceDate: '2025-03-23', replaceType: '临时替换', projectName: '嘉兴氢能示范项目', approvalStatus: '未提交', originalPlate: '浙B50501', originalBrand: '重汽', originalModel: 'ZZ1160', replacePlate: '浙B50502', replaceBrand: '福田', replaceModel: 'BJ1160', replaceReason: '客户原因', replaceReasonDesc: '试运行换车', creator: '王五', createTime: '2025-03-10 14:30' },
{ id: 'o20', replaceDate: '2025-03-24', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '撤回', originalPlate: '浙C60601', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙C60602', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '车辆原因', replaceReasonDesc: '排放升级', creator: '赵六', createTime: '2025-03-11 11:15' }
{ id: 'o20', replaceDate: '2025-03-24', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '撤回', currentApprover: '—', originalPlate: '浙C60601', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙C60602', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '车辆原因', replaceReasonDesc: '排放升级', creator: '赵六', createTime: '2025-03-11 11:15:00' }
];
function pad2(n) { return n < 10 ? '0' + n : String(n); }
var ongoingList = ongoingListRaw.map(function (r, i) {
return enrichListRow(r, pad2(8 + (i % 12)) + ':' + pad2((i * 7) % 60) + ':00');
});
// 历史记录:审批完成,审批状态均为审批完成
var historyList = [
{ id: 'h1', replaceDate: '2025-02-15', replaceType: '永久替换', projectName: '嘉兴氢能示范项目', approvalStatus: '审批完成', originalPlate: '浙A10001', originalBrand: '东风', originalModel: 'DFH1180', replacePlate: '浙A10002', replaceBrand: '福田', replaceModel: 'BJ1180', replaceReason: '车辆原因', replaceReasonDesc: '原车报废', creator: '张三', createTime: '2025-02-10 09:00' },
var historyListRaw = [
{ id: 'h1', replaceDate: '2025-02-15', replaceType: '永久替换', projectName: '嘉兴氢能示范项目', approvalStatus: '审批完成', currentApprover: '—', originalPlate: '浙A10001', originalBrand: '东风', originalModel: 'DFH1180', replacePlate: '浙A10002', replaceBrand: '福田', replaceModel: 'BJ1180', replaceReason: '车辆原因', replaceReasonDesc: '原车报废', creator: '张三', createTime: '2025-02-10 09:00:00' },
{ id: 'h2', replaceDate: '2025-02-14', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '审批完成', originalPlate: '浙B20001', originalBrand: '江淮', originalModel: 'HFC1180', replacePlate: '浙B20002', replaceBrand: '重汽', replaceModel: 'ZZ1180', replaceReason: '客户原因', replaceReasonDesc: '客户临时需求', creator: '李四', createTime: '2025-02-09 14:00' },
{ id: 'h3', replaceDate: '2025-02-13', replaceType: '永久替换', projectName: '杭州城配租赁项目', approvalStatus: '审批完成', originalPlate: '浙C30001', originalBrand: '重汽', originalModel: 'ZZ1160', replacePlate: '浙C30002', replaceBrand: '东风', replaceModel: 'DFH1160', replaceReason: '车辆原因', replaceReasonDesc: '使用年限到期', creator: '王五', createTime: '2025-02-08 10:30' },
{ id: 'h4', replaceDate: '2025-02-12', replaceType: '临时替换', projectName: '嘉兴氢能示范项目', approvalStatus: '审批完成', originalPlate: '浙A40001', originalBrand: '福田', originalModel: 'BJ1180', replacePlate: '浙A40002', replaceBrand: '江淮', replaceModel: 'HFC1180', replaceReason: '客户原因', replaceReasonDesc: '旺季加车', creator: '赵六', createTime: '2025-02-07 15:20' },
@@ -133,18 +163,24 @@ const Component = function () {
{ id: 'h17', replaceDate: '2025-01-30', replaceType: '永久替换', projectName: '上海物流租赁项目', approvalStatus: '审批完成', originalPlate: '浙B08001', originalBrand: '东风', originalModel: 'DFH1190', replacePlate: '浙B08002', replaceBrand: '重汽', replaceModel: 'ZZ1190', replaceReason: '车辆原因', replaceReasonDesc: '油耗过高', creator: '张三', createTime: '2025-01-25 14:30' },
{ id: 'h18', replaceDate: '2025-01-29', replaceType: '临时替换', projectName: '杭州城配租赁项目', approvalStatus: '审批完成', originalPlate: '浙C09001', originalBrand: '江淮', originalModel: 'HFC1160', replacePlate: '浙C09002', replaceBrand: '福田', replaceModel: 'BJ1160', replaceReason: '客户原因', replaceReasonDesc: '活动保障', creator: '李四', createTime: '2025-01-24 10:20' },
{ id: 'h19', replaceDate: '2025-01-28', replaceType: '永久替换', projectName: '嘉兴氢能示范项目', approvalStatus: '审批完成', originalPlate: '浙A10003', originalBrand: '重汽', originalModel: 'ZZ1180', replacePlate: '浙A10004', replaceBrand: '东风', replaceModel: 'DFH1180', replaceReason: '车辆原因', replaceReasonDesc: '配件停产', creator: '王五', createTime: '2025-01-23 16:45' },
{ id: 'h20', replaceDate: '2025-01-27', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '审批完成', originalPlate: '浙B11001', originalBrand: '福田', originalModel: 'BJ1190', replacePlate: '浙B11002', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '客户原因', replaceReasonDesc: '新业务启动', creator: '赵六', createTime: '2025-01-22 08:30' }
{ id: 'h20', replaceDate: '2025-01-27', replaceType: '临时替换', projectName: '上海物流租赁项目', approvalStatus: '审批完成', currentApprover: '—', originalPlate: '浙B11001', originalBrand: '福田', originalModel: 'BJ1190', replacePlate: '浙B11002', replaceBrand: '江淮', replaceModel: 'HFC1190', replaceReason: '客户原因', replaceReasonDesc: '新业务启动', creator: '赵六', createTime: '2025-01-22 08:30:00' }
];
var historyList = historyListRaw.map(function (r, i) {
return enrichListRow(r, pad2(10 + (i % 10)) + ':' + pad2((i * 5) % 60) + ':00');
});
var deletedIds = _deletedIds[0];
var appliedFilter = _appliedFilter[0];
var filteredOngoing = useMemo(function () {
var list = ongoingList.filter(function (r) {
if (deletedIds.indexOf(r.id) !== -1) return false;
if (appliedFilter.replaceDateRange && appliedFilter.replaceDateRange.length === 2) {
var start = appliedFilter.replaceDateRange[0] && appliedFilter.replaceDateRange[0].format ? appliedFilter.replaceDateRange[0].format('YYYY-MM-DD') : '';
var end = appliedFilter.replaceDateRange[1] && appliedFilter.replaceDateRange[1].format ? appliedFilter.replaceDateRange[1].format('YYYY-MM-DD') : '';
if (start && (r.replaceDate || '') < start) return false;
if (end && (r.replaceDate || '') > end) return false;
var rd = (r.replaceDate || '').slice(0, 10);
if (start && rd < start) return false;
if (end && rd > end) return false;
}
if (appliedFilter.replaceType && r.replaceType !== appliedFilter.replaceType) return false;
if (appliedFilter.projectName && r.projectName !== appliedFilter.projectName) return false;
@@ -164,15 +200,17 @@ const Component = function () {
return true;
});
return list;
}, [appliedFilter]);
}, [appliedFilter, deletedIds]);
var filteredHistory = useMemo(function () {
var list = historyList.filter(function (r) {
if (deletedIds.indexOf(r.id) !== -1) return false;
if (appliedFilter.replaceDateRange && appliedFilter.replaceDateRange.length === 2) {
var start = appliedFilter.replaceDateRange[0] && appliedFilter.replaceDateRange[0].format ? appliedFilter.replaceDateRange[0].format('YYYY-MM-DD') : '';
var end = appliedFilter.replaceDateRange[1] && appliedFilter.replaceDateRange[1].format ? appliedFilter.replaceDateRange[1].format('YYYY-MM-DD') : '';
if (start && (r.replaceDate || '') < start) return false;
if (end && (r.replaceDate || '') > end) return false;
var rd = (r.replaceDate || '').slice(0, 10);
if (start && rd < start) return false;
if (end && rd > end) return false;
}
if (appliedFilter.replaceType && r.replaceType !== appliedFilter.replaceType) return false;
if (appliedFilter.projectName && r.projectName !== appliedFilter.projectName) return false;
@@ -190,7 +228,7 @@ const Component = function () {
return true;
});
return list;
}, [appliedFilter]);
}, [appliedFilter, deletedIds]);
var handleQuery = useCallback(function () {
_appliedFilter[1]({
@@ -240,10 +278,163 @@ const Component = function () {
_approvalStatus[1](v);
}, []);
var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' };
var filterItemStyle = { marginBottom: 12 };
var filterLabelStyle = { marginBottom: 6, fontSize: 13, fontWeight: 500, color: '#475569', lineHeight: 1.4 };
var filterItemStyle = { marginBottom: 0 };
var filterControlStyle = { width: '100%' };
var pageStyles =
'.vr-list-page{font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}' +
'.vr-list-page .vr-page-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:20px;flex-wrap:wrap}' +
'.vr-list-page .vr-filter-card,.vr-list-page .vr-list-card{border-radius:16px;border:none;box-shadow:0 4px 24px -6px rgba(15,23,42,0.08),0 0 0 1px rgba(15,23,42,0.05);margin-bottom:16px}' +
'.vr-list-page .vr-filter-card>.ant-card-head,.vr-list-page .vr-list-card>.ant-card-head{border-bottom:1px solid #f1f5f9;min-height:auto;padding:14px 20px}' +
'.vr-list-page .vr-filter-card>.ant-card-head .ant-card-head-title,.vr-list-page .vr-list-card>.ant-card-head .ant-card-head-title{font-size:15px;font-weight:600;color:#0f172a;padding:0}' +
'.vr-list-page .vr-filter-card>.ant-card-body{padding:16px 20px 20px}' +
'.vr-list-page .vr-list-card>.ant-card-body{padding:12px 16px 16px}' +
'.vr-list-page .vr-filter-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px 20px;align-items:start}' +
'@media(max-width:900px){.vr-list-page .vr-filter-grid{grid-template-columns:1fr}}' +
'.vr-list-page .vr-filter-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;padding-top:16px;border-top:1px solid #f1f5f9}' +
'.vr-list-page .vr-swap-arrow{color:#94a3b8;font-size:12px;margin:0 4px}' +
'.vr-list-page .vr-reason-text{display:block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#475569}' +
'.vr-list-page .vr-list-table .ant-table-thead>tr>th{background:#f8fafc!important;color:#475569;font-weight:600;font-size:13px}' +
'.vr-list-page .vr-list-table .ant-table-tbody>tr:hover>td{background:#f0f9ff!important}' +
'.vr-list-page .vr-list-table .ant-table-thead th,.vr-list-page .vr-list-table .ant-table-tbody td{white-space:nowrap}' +
'.vr-list-page .vr-list-table .ant-table-tbody>tr.ant-table-row-selected>td{background:#eff6ff!important}' +
'.vr-list-page .vr-tabs .ant-tabs-nav{margin-bottom:0}' +
'.vr-list-page .vr-empty{padding:48px 16px}' +
'.vr-approval-flow-popover .ant-popover-inner{padding:14px 16px;border-radius:8px}' +
'.vr-approval-flow{width:300px;max-width:min(340px,92vw)}' +
'.vr-approval-flow__item{display:flex;gap:12px;position:relative;padding-bottom:22px}' +
'.vr-approval-flow__item:last-child{padding-bottom:0}' +
'.vr-approval-flow__item:not(:last-child) .vr-approval-flow__line{position:absolute;left:15px;top:34px;bottom:0;width:2px;background:#e5e7eb}' +
'.vr-approval-flow__avatar-wrap{position:relative;flex-shrink:0;z-index:1}' +
'.vr-approval-flow__avatar{width:32px;height:32px;border-radius:50%;background:#1677ff;color:#fff;font-size:12px;font-weight:600;display:inline-flex;align-items:center;justify-content:center;line-height:1}' +
'.vr-approval-flow__body{flex:1;min-width:0;padding-top:2px}' +
'.vr-approval-flow__head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:4px}' +
'.vr-approval-flow__role{font-size:14px;font-weight:600;color:rgba(0,0,0,0.88);line-height:1.4}' +
'.vr-approval-flow__meta{font-size:12px;color:rgba(0,0,0,0.45);line-height:1.5}' +
'.vr-list-page .vr-approval-status-trigger{display:inline-flex;cursor:pointer;border-radius:4px;transition:opacity .15s ease}' +
'.vr-list-page .vr-approval-status-trigger:hover{opacity:.88}';
function formatFlowTime(timeStr) {
if (!timeStr) return '—';
var s = String(timeStr).trim();
if (s.length >= 19) return s.slice(0, 19);
if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}$/.test(s)) return s + ':00';
return s;
}
function offsetFlowTime(timeStr, minutes) {
if (!timeStr) return '—';
var s = String(timeStr).trim().replace(/-/g, '/');
var d = new Date(s);
if (isNaN(d.getTime())) return formatFlowTime(timeStr);
d.setMinutes(d.getMinutes() + (minutes || 0));
function p2(n) { return n < 10 ? '0' + n : '' + n; }
return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate()) + ' ' + p2(d.getHours()) + ':' + p2(d.getMinutes()) + ':' + p2(d.getSeconds());
}
function getApproverRoleTitle(approverName) {
if (approverName === '姚守涛') return '业务部主管';
if (approverName === '尚建华') return '事业部主管';
if (approverName === '业务部主管') return '业务部主管';
return '运维主管';
}
function getApprovalFlowSteps(record) {
var creator = record.creator || '张三';
var createTime = formatFlowTime(record.createTime);
var approver = record.currentApprover || '姚守涛';
var approverPerson = approver === '业务部主管' ? '姚守涛' : approver;
var roleTitle = getApproverRoleTitle(approver);
var avatarFromName = function (name) {
var n = String(name || '').trim();
if (!n || n === '—') return '用户';
return n.length >= 2 ? n.slice(-2) : n;
};
var steps = [
{
role: roleTitle,
actionLabel: '审批中',
tagColor: 'processing',
person: approverPerson,
time: '待处理',
avatarText: avatarFromName(approverPerson)
},
{
role: '发起审批',
actionLabel: '通过',
tagColor: 'success',
person: creator,
time: createTime,
avatarText: avatarFromName(creator)
}
];
if (roleTitle === '运维主管') {
steps.splice(1, 0, {
role: '业务部主管',
actionLabel: '通过',
tagColor: 'success',
person: '尚建华',
time: offsetFlowTime(record.createTime, 1),
avatarText: '建华'
});
}
return steps;
}
function renderApprovalFlowContent(record) {
var steps = getApprovalFlowSteps(record);
return React.createElement('div', { className: 'vr-approval-flow' },
steps.map(function (step, idx) {
return React.createElement('div', { key: idx, className: 'vr-approval-flow__item' },
React.createElement('div', { className: 'vr-approval-flow__avatar-wrap' },
React.createElement('span', { className: 'vr-approval-flow__avatar', title: step.person }, step.avatarText),
idx < steps.length - 1 ? React.createElement('span', { className: 'vr-approval-flow__line' }) : null
),
React.createElement('div', { className: 'vr-approval-flow__body' },
React.createElement('div', { className: 'vr-approval-flow__head' },
React.createElement('span', { className: 'vr-approval-flow__role' }, step.role),
React.createElement(Tag, { color: step.tagColor, style: { margin: 0, fontSize: 12, lineHeight: '20px' } }, step.actionLabel)
),
React.createElement('div', { className: 'vr-approval-flow__meta' },
step.person + ' ' + step.time
)
)
);
})
);
}
function renderApprovalStatusCell(status, record) {
var tag = renderApprovalTag(status);
if (status !== '审批中' || !record) return tag;
return React.createElement(Popover, {
content: renderApprovalFlowContent(record),
trigger: 'hover',
placement: 'rightTop',
overlayClassName: 'vr-approval-flow-popover',
mouseEnterDelay: 0.15,
mouseLeaveDelay: 0.12,
destroyTooltipOnHide: true
}, React.createElement('span', { className: 'vr-approval-status-trigger' }, tag));
}
function renderApprovalTag(status) {
var color = 'default';
if (status === '待审批') color = 'processing';
else if (status === '审批中') color = 'blue';
else if (status === '审批驳回') color = 'error';
else if (status === '未提交') color = 'default';
else if (status === '撤回') color = 'warning';
else if (status === '审批完成') color = 'success';
return React.createElement(Tag, { color: color, style: { margin: 0, fontWeight: 500 } }, status || '—');
}
function renderReplaceTypeTag(type) {
var color = type === '永久替换' ? 'geekblue' : type === '临时替换' ? 'gold' : 'default';
return React.createElement(Tag, { color: color, style: { margin: 0 } }, type || '—');
}
function getOperationButtons(record, isHistory) {
if (isHistory) {
var viewBtn = React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { message.info('查看(跳转替换车管理-查看)'); } }, '查看');
@@ -258,6 +449,18 @@ const Component = function () {
if (['未提交', '审批驳回', '撤回'].indexOf(status) !== -1) {
items.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { message.info('编辑(跳转替换车管理-编辑)'); } }, '编辑'));
}
if (['撤回', '审批驳回'].indexOf(status) !== -1) {
items.push(React.createElement(Button, {
key: 'delete',
type: 'link',
size: 'small',
danger: true,
onClick: function () {
_deleteModalRecord[1](record);
_deleteModalVisible[1](true);
}
}, '删除'));
}
if (status === '审批中') {
items.push(React.createElement(Button, { key: 'withdraw', type: 'link', size: 'small', danger: true, onClick: function () { _withdrawModalRecord[1](record); _withdrawModalVisible[1](true); } }, '撤回'));
}
@@ -265,21 +468,50 @@ const Component = function () {
}
var tableColumns = [
{ title: '替换日期', dataIndex: 'replaceDate', key: 'replaceDate', width: 110, fixed: 'left' },
{ title: '替换类型', dataIndex: 'replaceType', key: 'replaceType', width: 100, fixed: 'left' },
{ title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, fixed: 'left' },
{ title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 },
{ title: '被替换车车牌号', dataIndex: 'originalPlate', key: 'originalPlate', width: 120 },
{ title: '被替换车品牌', dataIndex: 'originalBrand', key: 'originalBrand', width: 100 },
{ title: '被替换车型号', dataIndex: 'originalModel', key: 'originalModel', width: 110 },
{ title: '替换车车牌号', dataIndex: 'replacePlate', key: 'replacePlate', width: 120 },
{ title: '替换车品牌', dataIndex: 'replaceBrand', key: 'replaceBrand', width: 100 },
{ title: '替换车型号', dataIndex: 'replaceModel', key: 'replaceModel', width: 110 },
{ title: '替换日期', dataIndex: 'replaceDate', key: 'replaceDate', width: 168, fixed: 'left' },
{
title: '审批状态',
dataIndex: 'approvalStatus',
key: 'approvalStatus',
width: 108,
render: function (v, record) { return renderApprovalStatusCell(v, record); }
},
{
title: '当前审批人',
dataIndex: 'currentApprover',
key: 'currentApprover',
width: 110,
render: function (v) {
return React.createElement('span', { style: { color: v && v !== '—' ? '#334155' : '#94a3b8' } }, v || '—');
}
},
{ title: '被替换车(旧车)', dataIndex: 'originalPlate', key: 'originalPlate', width: 130 },
{ title: '品牌', dataIndex: 'originalBrand', key: 'originalBrand', width: 88 },
{ title: '型号', dataIndex: 'originalModel', key: 'originalModel', width: 100 },
{ title: '新车', dataIndex: 'replacePlate', key: 'replacePlate', width: 110 },
{ title: '品牌', dataIndex: 'replaceBrand', key: 'replaceBrandNew', width: 88 },
{ title: '型号', dataIndex: 'replaceModel', key: 'replaceModelNew', width: 100 },
{
title: '替换类型',
dataIndex: 'replaceType',
key: 'replaceType',
width: 108,
render: function (v) { return renderReplaceTypeTag(v); }
},
{ title: '替换原因', dataIndex: 'replaceReason', key: 'replaceReason', width: 100 },
{ title: '替换原因说明', dataIndex: 'replaceReasonDesc', key: 'replaceReasonDesc', width: 120, ellipsis: true },
{
title: '替换原因说明',
dataIndex: 'replaceReasonDesc',
key: 'replaceReasonDesc',
width: 140,
ellipsis: true,
render: function (v) {
return React.createElement('span', { className: 'vr-reason-text', title: v || '' }, v || '—');
}
},
{ title: '创建人', dataIndex: 'creator', key: 'creator', width: 90 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 150 },
{ title: '操作', key: 'action', width: 160, fixed: 'right', render: function (_, record) { return getOperationButtons(record, _activeTab[0] === 'history'); } }
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 168 },
{ title: '操作', key: 'action', width: 200, fixed: 'right', render: function (_, record) { return getOperationButtons(record, _activeTab[0] === 'history'); } }
];
var filterItems = [
@@ -296,10 +528,10 @@ const Component = function () {
React.createElement('div', { style: filterLabelStyle }, '审批状态'),
React.createElement(Select, { mode: 'multiple', placeholder: '请选择', style: filterControlStyle, value: _approvalStatus[0], onChange: handleApprovalStatusChange, options: approvalStatusOptions })),
React.createElement('div', { key: 'originalPlate', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '被替换车车牌号'),
React.createElement('div', { style: filterLabelStyle }, '被替换车(旧车)'),
React.createElement(Select, { placeholder: '请输入或选择车牌号', style: filterControlStyle, value: _originalPlate[0], onChange: function (v) { _originalPlate[1](v); }, allowClear: true, showSearch: true, options: plateOptions, filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } })),
React.createElement('div', { key: 'replacePlate', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '替换车车牌号'),
React.createElement('div', { style: filterLabelStyle }, '新车'),
React.createElement(Select, { placeholder: '请输入或选择车牌号', style: filterControlStyle, value: _replacePlate[0], onChange: function (v) { _replacePlate[1](v); }, allowClear: true, showSearch: true, options: plateOptions, filterOption: function (input, opt) { return (opt.label || '').toString().toLowerCase().indexOf((input || '').toLowerCase()) !== -1; } })),
React.createElement('div', { key: 'replaceReason', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '替换原因'),
@@ -330,12 +562,49 @@ const Component = function () {
return currentList.slice(start, start + pageSize);
}, [currentList, page, pageSize]);
var listStats = useMemo(function () {
return {
ongoing: filteredOngoing.length,
history: filteredHistory.length,
selected: (_selectedRowKeys[0] || []).length
};
}, [filteredOngoing.length, filteredHistory.length, _selectedRowKeys[0]]);
var rowSelection = {
selectedRowKeys: _selectedRowKeys[0],
onChange: function (keys) { _selectedRowKeys[1](keys); },
fixed: true
};
var tablePagination = {
current: page,
pageSize: pageSize,
total: currentList.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: function (t) { return '共 ' + t + ' 条'; },
onChange: function (p, ps) { setPage(p); if (ps) setPageSize(ps); }
};
function renderTableBody() {
if (displayList.length === 0) {
return React.createElement(Empty, {
className: 'vr-empty',
image: Empty.PRESENTED_IMAGE_SIMPLE,
description: '暂无符合条件的替换车记录,请调整筛选条件后重试'
});
}
return React.createElement(Table, {
rowKey: 'id',
rowSelection: rowSelection,
columns: tableColumns,
dataSource: displayList,
size: 'small',
scroll: { x: 1900 },
pagination: tablePagination
});
}
var requirementContent = `替换车管理2026年3月3日版本
一个「数字化资产ONEOS运管平台」中的「运维管理」「车辆业务」「替换车管理」模块
@@ -351,39 +620,27 @@ const Component = function () {
2.2.替换类型:选择器,分为永久替换、临时替换两种方式;
2.3.项目名称:选择器,支持输入框中输入关键内容进行搜索,下拉匹配相应项;
2.4.审批状态:选择器,分为全部、待审批、审批中、审批驳回、未提交、撤回;
2.5.被替换车车牌号:选择器,支持输入框中输入关键内容进行搜索,下拉匹配相应项;
2.6.替换车车牌号:选择器,支持输入框中输入关键内容进行搜索,下拉匹配相应项;
2.5.被替换车(旧车):选择器,支持输入框中输入关键内容进行搜索,下拉匹配相应项;
2.6.新车:选择器,支持输入框中输入关键内容进行搜索,下拉匹配相应项;
2.7.替换原因:选择器,分为全部、客户原因、车辆原因;
2.8.创建人:选择器,下拉选择所有创建人;
2.9.创建时间:日期选择器,支持单输入框内双日历选择开始-结束时间,默认提示文本为:请选择开始时间、请选择结束时间;
3.列表:列表右上角为新增、导出,首列为多选,支持多选后导出对应条目;
列表展示所有替换车记录分为进行中、历史记录两个tab字段依次为替换日期、替换类型、项目名称、审批状态、被替换车车牌号、被替换车品牌、被替换车型号、替换车车牌号、替换车品牌、替换车型号、替换原因、替换原因说明、创建人、创建时间、操作;
列表展示所有替换车记录分为进行中、历史记录两个tab字段依次为替换日期、审批状态、当前审批人、被替换车(旧车)、品牌、型号、新车、品牌、型号、替换类型、替换原因、替换原因说明、创建人、创建时间、操作;
3.1.进行中:显示替换车申请流程未结束、暂存的记录;
3.1.1.替换日期显示格式为YYYY-MM-DD,显示替换车申请表单中设置的替换日期
3.1.2.替换类型:分为:临时替换、永久替换两种,根据替换车申请表单中设置的替换类型显示
3.1.3.项目名称:显示替换车申请表单中设置的项目名称
3.1.4.审批状态:显示替换车申请当前审批状态,分为待审批、审批中、审批驳回、未提交、撤回
3.1.4.1.待审批:发起人已提交,但还没有任何流程节点完成审批
3.1.4.2.审批中发起人已提交已有1个以上节点完成审批但未完成最终节点审批
3.1.4.3.审批驳回:发起人已提交,任意流程节点驳回,该状态下操作列支持编辑和重新提交
3.1.4.4.未提交:发起人仅保存,但未提交审批
3.1.4.5.撤回:发起人主动撤回审批流程
3.1.5.被替换车车牌号:显示替换车申请表单中被替换车车牌号
3.1.6.被替换车品牌:显示替换车申请表单中被替换车品牌
3.1.7.被替换车型号:显示替换车申请表单中被替换车型号;
3.1.8.替换车车牌号:显示替换车申请表单中替换车车牌号;
3.1.9.替换车品牌:显示替换车申请表单中替换车品牌;
3.1.10.替换车型号:显示替换车申请表单中替换车型号;
3.1.11.替换原因:显示替换车申请表单中替换原因;
3.1.12.替换原因说明:显示替换车申请表单中替换原因说明;
3.1.13.创建人:显示替换车申请表单中创建人;
3.1.14.创建时间显示替换车申请表单中创建时间显示格式为YYYY-MM-DD HH:MM
3.1.15.操作:查看、编辑、撤回;
3.1.15.1.查看:当「审批状态」为「待审批」「审批中」「审批驳回」「未提交」「撤回」时显示,点击跳转替换车管理-查看页面;
3.1.15.2.编辑:当「审批状态」为「未提交」「审批驳回」「撤回」时显示,点击跳转替换车管理-编辑页面;
3.1.15.3.撤回:当「审批状态」为「审批中」时显示,点击撤回合同时进行二次确认,提示语:是否确认撤回该替换车申请;
3.1.1.替换日期显示格式为YYYY-MM-DD HH:MM:SS
3.1.2.审批状态:分为待审批、审批中、审批驳回、未提交、撤回
3.1.3.当前审批人:显示当前待审批节点审批人,未提交/撤回等为「—」
3.1.4.被替换车(旧车)、品牌、型号、新车、品牌、型号:展示申请表单车辆信息
3.1.5.替换类型:临时替换、永久替换
3.1.6.替换原因、替换原因说明、创建人、创建时间YYYY-MM-DD HH:MM:SS
3.1.7.操作:查看、编辑、撤回、删除(逻辑删除)
3.1.7.1.查看:审批状态为待审批/审批中/审批驳回/未提交/撤回时显示
3.1.7.2.编辑:审批状态为未提交/审批驳回/撤回时显示
3.1.7.3.撤回:审批状态为审批中时显示,二次确认
3.1.7.4.删除:审批状态为撤回/审批驳回时显示,逻辑删除,二次确认
3.2.历史记录:显示替换车申请流程已结束的记录;
3.2.1.替换日期显示格式为YYYY-MM-DD显示替换车申请表单中设置的替换日期
@@ -406,88 +663,99 @@ const Component = function () {
列表右下方为分页符。`;
var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' };
var reqTitleStyle = { fontSize: 18, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.85)' };
var reqSectionStyle = { fontSize: 15, fontWeight: 600, marginTop: 16, marginBottom: 8, color: 'rgba(0,0,0,0.85)' };
var reqItemStyle = { fontSize: 13, marginLeft: 32, marginTop: 4, marginBottom: 2, lineHeight: 1.6, color: 'rgba(0,0,0,0.75)' };
var activeTab = _activeTab[0];
var selectedCount = (_selectedRowKeys[0] || []).length;
return React.createElement('div', { style: layoutStyle },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
React.createElement(Breadcrumb, {
items: [
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: '替换车管理' }
]
}),
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { _requirementModalVisible[1](true); } }, '查看需求说明')
),
React.createElement(Card, { style: { marginBottom: 16 } },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } }, filterNodes),
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'),
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '展开')
)
),
React.createElement(Card, null,
React.createElement('div', { style: { marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
React.createElement(Tabs, {
activeKey: _activeTab[0],
onChange: function (k) { _activeTab[1](k); _selectedRowKeys[1]([]); setPage(1); },
return React.createElement(App, null,
React.createElement('div', { className: 'vr-list-page', style: { minHeight: '100vh', padding: '20px 24px 32px', background: 'linear-gradient(180deg,#f8fafc 0%,#f1f5f9 100%)' } },
React.createElement('style', null, pageStyles),
React.createElement('div', { className: 'vr-page-header' },
React.createElement(Breadcrumb, {
items: [
{ key: 'ongoing', label: '进行中' },
{ key: 'history', label: '历史记录' }
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: '替换车管理' }
]
}),
React.createElement('div', { style: { display: 'flex', gap: 8 } },
React.createElement(Button, { type: 'primary', onClick: function () { message.info('新增替换车申请(原型)'); } }, '新增'),
React.createElement(Button, { onClick: function () { message.info('导出选中记录(原型)'); } }, '导出')
React.createElement(Button, { type: 'link', style: { padding: 0, color: '#2563eb', fontWeight: 500 }, onClick: function () { _requirementModalVisible[1](true); } }, '查看需求说明')
),
React.createElement(Card, { className: 'vr-filter-card', title: '筛选条件' },
React.createElement('div', { className: 'vr-filter-grid' }, filterNodes),
React.createElement('div', { className: 'vr-filter-actions' },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'),
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { _filterExpanded[1](!_filterExpanded[0]); } }, _filterExpanded[0] ? '收起' : '展开')
)
),
React.createElement(Table, {
rowKey: 'id',
rowSelection: rowSelection,
columns: tableColumns,
dataSource: displayList,
size: 'small',
scroll: { x: 1600 },
pagination: {
current: page,
pageSize: pageSize,
total: currentList.length,
showSizeChanger: true,
showQuickJumper: true,
showTotal: function (t) { return '共 ' + t + ' 条'; },
onChange: function (p, ps) { setPage(p); if (ps) setPageSize(ps); }
}
})
),
React.createElement(Modal, {
title: '是否确认撤回该替换车申请',
open: _withdrawModalVisible[0],
onCancel: function () { _withdrawModalVisible[1](false); _withdrawModalRecord[1](null); },
onOk: function () { message.success('已撤回(原型)'); _withdrawModalVisible[1](false); _withdrawModalRecord[1](null); },
okText: '确定',
cancelText: '取消'
}),
React.createElement(Modal, {
title: '是否确认转永久替换',
open: _toPermanentModalVisible[0],
onCancel: function () { _toPermanentModalVisible[1](false); _toPermanentModalRecord[1](null); },
onOk: function () { message.success('已转为永久替换(原型)'); _toPermanentModalVisible[1](false); _toPermanentModalRecord[1](null); },
okText: '提交',
cancelText: '取消'
}),
React.createElement(Modal, {
title: '需求说明',
open: _requirementModalVisible[0],
onCancel: function () { _requirementModalVisible[1](false); },
width: 720,
footer: React.createElement(Button, { onClick: function () { _requirementModalVisible[1](false); } }, '关闭'),
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
}, React.createElement('div', { style: { padding: '8px 0' } },
React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent))
React.createElement(Card, { className: 'vr-list-card', title: '替换车列表' },
React.createElement('div', { className: 'vr-list-table' },
React.createElement(Tabs, {
className: 'vr-tabs',
activeKey: activeTab,
onChange: function (k) { _activeTab[1](k); _selectedRowKeys[1]([]); setPage(1); },
tabBarExtraContent: React.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
React.createElement(Button, { type: 'primary', onClick: function () { message.info('新增替换车申请(原型)'); } }, '新增'),
selectedCount > 0
? React.createElement(Badge, { count: selectedCount, size: 'small', offset: [-4, 4] },
React.createElement(Button, { onClick: function () { message.info('导出选中 ' + selectedCount + ' 条(原型)'); } }, '导出')
)
: React.createElement(Button, { onClick: function () { message.info('请先勾选需要导出的记录'); } }, '导出')
),
destroyInactiveTabPane: true,
items: [
{ key: 'ongoing', label: '进行中 (' + listStats.ongoing + ')', children: activeTab === 'ongoing' ? renderTableBody() : null },
{ key: 'history', label: '历史记录 (' + listStats.history + ')', children: activeTab === 'history' ? renderTableBody() : null }
]
})
)
),
React.createElement(Modal, {
title: '是否确认撤回该替换车申请',
open: _withdrawModalVisible[0],
onCancel: function () { _withdrawModalVisible[1](false); _withdrawModalRecord[1](null); },
onOk: function () { message.success('已撤回(原型)'); _withdrawModalVisible[1](false); _withdrawModalRecord[1](null); },
okText: '确定',
cancelText: '取消'
}),
React.createElement(Modal, {
title: '是否确认转永久替换',
open: _toPermanentModalVisible[0],
onCancel: function () { _toPermanentModalVisible[1](false); _toPermanentModalRecord[1](null); },
onOk: function () { message.success('已转为永久替换(原型)'); _toPermanentModalVisible[1](false); _toPermanentModalRecord[1](null); },
okText: '提交',
cancelText: '取消'
}),
React.createElement(Modal, {
title: '是否确认逻辑删除该替换车申请?',
open: _deleteModalVisible[0],
onCancel: function () { _deleteModalVisible[1](false); _deleteModalRecord[1](null); },
onOk: function () {
var rec = _deleteModalRecord[0];
if (rec && rec.id) {
_deletedIds[1](function (prev) {
if (prev.indexOf(rec.id) !== -1) return prev;
return prev.concat(rec.id);
});
_selectedRowKeys[1](function (prev) { return prev.filter(function (k) { return k !== rec.id; }); });
}
message.success('已逻辑删除(原型)');
_deleteModalVisible[1](false);
_deleteModalRecord[1](null);
},
okText: '确定',
cancelText: '取消',
okButtonProps: { danger: true }
}),
React.createElement(Modal, {
title: '需求说明',
open: _requirementModalVisible[0],
onCancel: function () { _requirementModalVisible[1](false); },
width: 720,
footer: React.createElement(Button, { onClick: function () { _requirementModalVisible[1](false); } }, '关闭'),
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
}, React.createElement('div', { style: { padding: '8px 0' } },
React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6, color: '#334155' } }, requirementContent))
)
)
);
};

View File

@@ -0,0 +1,226 @@
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web - 运维管理 - 车辆业务 - 查看故障
const Component = function () {
var useState = React.useState;
var antd = window.antd;
var Button = antd.Button;
var Input = antd.Input;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Form = antd.Form;
var Row = antd.Row;
var Col = antd.Col;
var Breadcrumb = antd.Breadcrumb;
var Layout = antd.Layout;
var message = antd.message;
var Card = antd.Card;
var Upload = antd.Upload;
var _form = Form.useForm();
var faultForm = _form[0];
// 故障等级枚举
var faultLevelOptions = [
{ label: '特急', value: '特急' },
{ label: '紧急', value: '紧急' },
{ label: '一般', value: '一般' },
{ label: '提示', value: '提示' }
];
// 故障类型枚举
var faultTypeOptions = [
{ label: '底盘故障', value: '底盘故障' },
{ label: '三电故障', value: '三电故障' },
{ label: '整车系统', value: '整车系统' },
{ label: '燃料电池系统故障', value: '燃料电池系统故障' },
{ label: '供氢系统故障', value: '供氢系统故障' },
{ label: '空调系统故障', value: '空调系统故障' },
{ label: '冷机故障', value: '冷机故障' },
{ label: '其他故障', value: '其他故障' }
];
// 故障来源枚举
var faultSourceOptions = [
{ label: '客户报告', value: '客户报告' },
{ label: '定期保养', value: '定期保养' },
{ label: '司机操作问题', value: '司机操作问题' }
];
// 解决情况枚举
var resolveStatusOptions = [
{ label: '未解决', value: '未解决' },
{ label: '临时排故', value: '临时排故' },
{ label: '已解决', value: '已解决' }
];
var plateOptions = [
{ label: '沪A12345', value: '沪A12345', brand: '一汽解放', model: 'J6P', company: '上海羚牛', vin: 'LNW1234567890ABCD' },
{ label: '浙B88888', value: '浙B88888', brand: '东风商用车', model: '天龙', company: '浙江羚牛', vin: 'LNW0987654321EFGH' },
{ label: '苏C66666', value: '苏C66666', brand: '福田欧曼', model: 'EST', company: '苏州冷链速运有限公司', vin: 'LNW1357924680IJKL' },
{ label: '沪D99999', value: '沪D99999', brand: '陕汽重卡', model: '德龙', company: '上海城配物流有限公司', vin: 'LNW2468013579MNOP' }
];
var handleBack = function () {
message.info('返回列表');
};
// 模拟数据加载
React.useEffect(function() {
var mockData = { key: '1', code: 'F-2024-001', plate: '沪A12345', brand: '一汽解放', model: 'J6P', company: '上海羚牛', type: '底盘故障', level: '紧急', source: '客户报告', status: '未解决', reportTime: '2024-05-10 08:30:00', lastOperator: '王婷婷', lastOperationTime: '2024-05-10 09:00:00', desc: '车辆重载下制动踏板偏软,制动距离明显变长' };
if (mockData.reportTime) {
if (typeof window.dayjs === 'function') {
mockData.reportTime = window.dayjs(mockData.reportTime);
} else if (typeof window.moment === 'function') {
mockData.reportTime = window.moment(mockData.reportTime);
}
}
faultForm.setFieldsValue(mockData);
}, []);
return React.createElement(Layout, { className: 'arco-theme-overrides', style: { minHeight: '100vh', background: '#f2f3f5', fontFamily: 'Inter, Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif' } },
React.createElement('style', null, `
.arco-theme-overrides .ant-btn { border-radius: 4px; }
.arco-theme-overrides .ant-btn-primary { background-color: #165dff; border-color: #165dff; }
.arco-theme-overrides .ant-btn-primary:hover { background-color: #4080ff; border-color: #4080ff; }
.arco-grouped-form-page { display: flex; flex-direction: column; min-height: 100vh; }
.arco-grouped-form-page-content { flex: 1; padding: 16px 20px 24px; }
.arco-grouped-form-page .ant-card { margin-bottom: 16px; border-radius: 4px; border: none; box-shadow: 0 1px 2px rgba(0,0,0,0.06); }
.arco-grouped-form-page .ant-card-head { border-bottom: none; padding: 20px 24px 0; min-height: auto; }
.arco-grouped-form-page .ant-card-head-title { font-size: 16px; font-weight: 500; color: #1d2129; padding: 0; }
.arco-grouped-form-page .ant-card-body { padding: 24px; }
.arco-grouped-form-page .ant-form-vertical .ant-form-item-label { padding-bottom: 8px; height: auto; line-height: 1.5715; }
.arco-grouped-form-page .ant-form-item { margin-bottom: 24px; }
.arco-grouped-form-page .ant-input,
.arco-grouped-form-page .ant-select-selector,
.arco-grouped-form-page .ant-picker,
.arco-grouped-form-page .ant-input-affix-wrapper { background-color: #f2f3f5; border: 1px solid #e5e6eb; border-radius: 2px; transition: all 0.1s cubic-bezier(0, 0, 1, 1); }
.arco-grouped-form-page .ant-input[disabled],
.arco-grouped-form-page .ant-select-disabled .ant-select-selector,
.arco-grouped-form-page .ant-picker-disabled { color: #86909c; background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper[disabled] { background-color: #f2f3f5; border-color: #e5e6eb; cursor: not-allowed; }
.arco-grouped-form-page .ant-input-affix-wrapper > input.ant-input { background-color: transparent; }
.arco-grouped-form-footer { background: #fff; padding: 16px 24px; border-top: 1px solid #e5e6eb; display: flex; justify-content: flex-end; align-items: center; gap: 12px; position: sticky; bottom: 0; z-index: 100; box-shadow: 0 -2px 10px rgba(0,0,0,0.05); }
.arco-grouped-form-footer .ant-btn { border-radius: 5px; height: 32px; padding: 4px 16px; font-size: 14px; }
.arco-theme-overrides .ant-breadcrumb { color: #86909c; font-size: 14px; white-space: nowrap; flex-shrink: 0; margin-bottom: 20px; }
.arco-theme-overrides .ant-breadcrumb a { color: #4e5969; }
.arco-theme-overrides .ant-breadcrumb a:hover { color: #165dff; background-color: transparent; }
.arco-theme-overrides .ant-form-item-label > label { color: #4e5969; white-space: nowrap; }
.arco-theme-overrides .ant-form-item-label > label::after { display: none !important; content: "" !important; margin: 0 !important; }
`),
React.createElement('div', { className: 'arco-grouped-form-page' },
React.createElement('div', { className: 'arco-grouped-form-page-content' },
React.createElement(Breadcrumb, {
separator: React.createElement('span', { style: { color: '#c9cdd4' } }, '/'),
items: [
{ title: '首页' },
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: React.createElement('a', { onClick: function(e) { e.preventDefault(); handleBack(); } }, '故障管理') },
{ title: React.createElement('span', { style: { color: '#1d2129' } }, '查看故障单') }
]
}),
React.createElement(Form, { form: faultForm, layout: 'vertical', disabled: true },
// 车辆信息
React.createElement(Card, { title: '车辆信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车牌号', name: 'plate', rules: [{ required: true, message: '请选择车牌号' }] },
React.createElement(Select, {
placeholder: '请选择车牌号',
options: plateOptions,
showSearch: true
})
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆品牌', name: 'brand' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆型号', name: 'model' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '运营公司', name: 'company' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '车辆识别代码', name: 'vin' },
React.createElement(Input, { placeholder: '自动带入', disabled: true, style: { backgroundColor: '#f2f3f5', color: '#86909c' } })
)
)
)
),
// 故障信息
React.createElement(Card, { title: '故障信息', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障类型', name: 'type', rules: [{ required: true, message: '请选择故障类型' }] },
React.createElement(Select, { placeholder: '请选择故障类型', options: faultTypeOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障来源', name: 'source', rules: [{ required: true, message: '请选择故障来源' }] },
React.createElement(Select, { placeholder: '请选择故障来源', options: faultSourceOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障等级', name: 'level', rules: [{ required: true, message: '请选择故障等级' }] },
React.createElement(Select, { placeholder: '请选择故障等级', options: faultLevelOptions })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '故障上报时间', name: 'reportTime', rules: [{ required: true, message: '请选择故障上报时间' }] },
React.createElement(DatePicker, { style: { width: '100%' }, placeholder: '请选择上报时间', showTime: true })
)
),
React.createElement(Col, { span: 8 },
React.createElement(Form.Item, { label: '解决情况', name: 'status', rules: [{ required: true, message: '请选择故障解决情况' }] },
React.createElement(Select, { placeholder: '请选择故障解决情况', options: resolveStatusOptions })
)
)
)
),
// 故障证据与描述
React.createElement(Card, { title: '故障证据与描述', bordered: false },
React.createElement(Row, { gutter: 24 },
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, { label: '故障描述', name: 'desc', rules: [{ required: true, message: '请填写故障描述' }] },
React.createElement(Input.TextArea, {
placeholder: '在何种状态下,产生何种现象,导致何种事故',
style: { height: 80, minHeight: 80, resize: 'none' }
})
)
),
React.createElement(Col, { span: 24 },
React.createElement(Form.Item, { label: '故障证据', name: 'evidence' },
React.createElement(Upload, { listType: 'picture-card' }),
React.createElement('div', { style: { fontSize: 12, color: '#86909c', marginTop: 8 } }, '支持上传照片、视频、录音')
)
)
)
)
)
),
// 底部操作栏
React.createElement('div', { className: 'arco-grouped-form-footer' },
React.createElement(Button, { onClick: handleBack, style: { borderRadius: 5 } }, '返回列表')
)
)
);
};
if (typeof module !== 'undefined' && module.exports) module.exports = Component;

BIN
web端/需求说明.zip Normal file

Binary file not shown.