feat(web): 同步 web 端目录更新至 Gitea
包含加氢站站点信息、运维交车/故障、台账与数据分析等页面新增与改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
5327
web端/业务管理/保险采购.jsx
Normal file
5327
web端/业务管理/保险采购.jsx
Normal file
File diff suppressed because it is too large
Load Diff
187
web端/业务管理/文档/_build_租赁账单手册.py
Normal file
187
web端/业务管理/文档/_build_租赁账单手册.py
Normal 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")
|
||||
3353
web端/加氢站管理/站点信息.jsx
Normal file
3353
web端/加氢站管理/站点信息.jsx
Normal file
File diff suppressed because it is too large
Load Diff
515
web端/台账数据/保险分摊明细.jsx
Normal file
515
web端/台账数据/保险分摊明细.jsx
Normal 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
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
199
web端/台账数据/氢费采购端汇总报表-需求文档.md
Normal file
199
web端/台账数据/氢费采购端汇总报表-需求文档.md
Normal 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 年起**(与车辆氢费明细、主数据生效规则一致) |
|
||||
| 未付(行) | 应付总金额 − 已付总金额 |
|
||||
| 未付(合计条) | 各站未付之和,等价于应付合计 − 已付合计 |
|
||||
| 未开票(行) | 应付总金额 − 已开票金额 |
|
||||
| 未开票(合计条) | 各站未开票之和,等价于应付合计 − 已开票合计 |
|
||||
| 已欠费 | 仅 **当前余额 < 0** 时展示标签 |
|
||||
| 钻取范围 | 均限定为 **当前汇总行对应加氢站** |
|
||||
|
||||
---
|
||||
|
||||
## 五、验收要点(业务)
|
||||
|
||||
1. 查询、重置、筛选摘要、合计条与列表数据一致。
|
||||
2. 各可点击金额/加氢量钻取弹窗字段与上文一致,合计正确。
|
||||
3. 未付、已欠费展示规则符合第四节。
|
||||
4. 付款凭据可预览;发票可预览与下载。
|
||||
5. 列宽可拖动;导出字段完整。
|
||||
6. 「查看需求说明」可打开本 PRD 全文。
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
2329
web端/台账数据/氢费采购端汇总报表.jsx
Normal file
2329
web端/台账数据/氢费采购端汇总报表.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
| 待保存 | 草稿,未进入对账流程 |
|
||||
| 未对账 | 已保存,等待业务确认并完成对账 |
|
||||
| 已对账 | 对账完成,业务员不可随意改动 |
|
||||
| 客户承担 | 氢费由客户结算 |
|
||||
| 我司承担 | 氢费由公司承担 |
|
||||
| 未付款 / 已付款 / 部分付款 | 系统带出的付款进度,业务只读 |
|
||||
| 客户承担 | 氢费由客户承担 |
|
||||
| 我司承担 | 氢费由我司承担 |
|
||||
| 客户自行结算 | 由客户自行与加氢站等方结算 |
|
||||
| 其他结算 | 其他结算方式 |
|
||||
| 加氢站付款状态 | 加氢站侧是否已付款,已付款 / 未付款 |
|
||||
| 客户收款状态 | 客户侧是否已收款,已付款 / 未付款 |
|
||||
| 收票日期 | 财务收票日期,系统带出 |
|
||||
| 标准价 | 公司维护的、按加氢站与生效时段确定的参考单价 |
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
| 待保存 | 草稿,未进入对账流程 |
|
||||
| 未对账 | 已保存,等待业务确认并完成对账 |
|
||||
| 已对账 | 对账完成,业务员不可随意改动 |
|
||||
| 客户承担 | 氢费由客户结算 |
|
||||
| 我司承担 | 氢费由公司承担 |
|
||||
| 未付款 / 已付款 / 部分付款 | 系统带出的付款进度,业务只读 |
|
||||
| 客户承担 | 氢费由客户承担 |
|
||||
| 我司承担 | 氢费由我司承担 |
|
||||
| 客户自行结算 | 由客户自行与加氢站等方结算 |
|
||||
| 其他结算 | 其他结算方式 |
|
||||
| 加氢站付款状态 | 加氢站侧是否已付款,已付款 / 未付款 |
|
||||
| 客户收款状态 | 客户侧是否已收款,已付款 / 未付款 |
|
||||
| 收票日期 | 财务收票日期,系统带出 |
|
||||
| 标准价 | 公司维护的、按加氢站与生效时段确定的参考单价 |
|
||||
|
||||
---
|
||||
|
||||
1823
web端/台账数据/车辆维修明细.jsx
Normal file
1823
web端/台账数据/车辆维修明细.jsx
Normal file
File diff suppressed because it is too large
Load Diff
688
web端/工作台.jsx
688
web端/工作台.jsx
@@ -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: '当前项目暂无已交车车辆' }
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
232
web端/帮助中心/功能说明书.jsx
Normal file
232
web端/帮助中心/功能说明书.jsx
Normal 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)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
839
web端/数据分析/业务台账.jsx
Normal file
839
web端/数据分析/业务台账.jsx
Normal 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;
|
||||
}
|
||||
|
||||
/** 演示数据:2026;1 月氢气/申办/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
|
||||
)
|
||||
);
|
||||
};
|
||||
2468
web端/数据分析/业务部台账.jsx
Normal file
2468
web端/数据分析/业务部台账.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
752
web端/数据分析/客户服务部业务统计报表.jsx
Normal file
752
web端/数据分析/客户服务部业务统计报表.jsx
Normal 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
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
271
web端/财务管理/文档/_build_还车应结款手册.py
Normal file
271
web端/财务管理/文档/_build_还车应结款手册.py
Normal 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()
|
||||
BIN
web端/财务管理/文档/还车应结款-用户操作说明.docx
Normal file
BIN
web端/财务管理/文档/还车应结款-用户操作说明.docx
Normal file
Binary file not shown.
BIN
web端/财务管理/文档/配图-还车应结款-查看页-原型结构.png
Normal file
BIN
web端/财务管理/文档/配图-还车应结款-查看页-原型结构.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web端/财务管理/文档/配图-还车应结款-费用明细-原型结构.png
Normal file
BIN
web端/财务管理/文档/配图-还车应结款-费用明细-原型结构.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -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('取消'); } }, '取消'))
|
||||
|
||||
@@ -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 },
|
||||
|
||||
485
web端/车辆管理-查看.jsx
485
web端/车辆管理-查看.jsx
@@ -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 = [
|
||||
|
||||
296
web端/车辆管理.jsx
296
web端/车辆管理.jsx
@@ -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],
|
||||
|
||||
@@ -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 }, '取消')
|
||||
),
|
||||
|
||||
|
||||
503
web端/运维管理/车辆业务/交车管理-编辑抽屉.jsx
Normal file
503
web端/运维管理/车辆业务/交车管理-编辑抽屉.jsx
Normal 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
267
web端/运维管理/车辆业务/故障管理-编辑.jsx
Normal file
267
web端/运维管理/车辆业务/故障管理-编辑.jsx
Normal 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;
|
||||
503
web端/运维管理/车辆业务/故障管理.jsx
Normal file
503
web端/运维管理/车辆业务/故障管理.jsx
Normal 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;
|
||||
417
web端/运维管理/车辆业务/新增故障.jsx
Normal file
417
web端/运维管理/车辆业务/新增故障.jsx
Normal 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
@@ -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
@@ -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))
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
226
web端/运维管理/车辆业务/查看故障.jsx
Normal file
226
web端/运维管理/车辆业务/查看故障.jsx
Normal 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
BIN
web端/需求说明.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user