feat(web): 车辆租赁合同合同审批类型、法务上传盖章合同;新增数据分析页面;交车任务查看调整
- 租赁合同列表:合同审批类型筛选与列、需求说明;审批通过且未上传盖章件时法务可更多上传 - 新增/续签/转正式/查看等页同步合同审批类型及需求说明 - 新增数据分析:业务部业绩明细、物流业务月度统计、租赁客户氢费台账、租赁车辆收入明细 - 查看交车任务页面更新 Made-with: Cursor
This commit is contained in:
@@ -1,15 +1,59 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型
|
||||
// 数字化资产ONEOS运管平台 - 查看交车任务模块(只读,布局参照新增交车任务)
|
||||
// 数字化资产ONEOS运管平台 - 查看交车任务模块(只读表单 + 车辆列表还车操作)
|
||||
|
||||
const Component = function () {
|
||||
var useState = React.useState;
|
||||
var useCallback = React.useCallback;
|
||||
|
||||
const Component = function() {
|
||||
var antd = window.antd;
|
||||
var Input = antd.Input;
|
||||
var Button = antd.Button;
|
||||
var Modal = antd.Modal;
|
||||
var Drawer = antd.Drawer;
|
||||
var DatePicker = antd.DatePicker;
|
||||
var RangePicker = DatePicker.RangePicker;
|
||||
var Cascader = antd.Cascader;
|
||||
var Space = antd.Space;
|
||||
var Checkbox = antd.Checkbox;
|
||||
var message = antd.message;
|
||||
|
||||
var reqModalOpen = React.useState(false);
|
||||
var reqModalOpen = useState(false);
|
||||
/** 交车单是否已点击「车辆到达」(到达后不可撤销还车) */
|
||||
var vehicleArrivedPair = useState(false);
|
||||
var vehicleArrived = vehicleArrivedPair[0];
|
||||
var setVehicleArrived = vehicleArrivedPair[1];
|
||||
|
||||
var returnRegionOptions = [
|
||||
{ value: '浙江省', label: '浙江省', children: [{ value: '杭州市', label: '杭州市' }, { value: '嘉兴市', label: '嘉兴市' }, { value: '宁波市', label: '宁波市' }] },
|
||||
{ value: '上海市', label: '上海市', children: [{ value: '上海市', label: '上海市' }] },
|
||||
{ value: '江苏省', label: '江苏省', children: [{ value: '南京市', label: '南京市' }, { value: '苏州市', label: '苏州市' }] },
|
||||
{ value: '广东省', label: '广东省', children: [{ value: '广州市', label: '广州市' }, { value: '深圳市', label: '深圳市' }] },
|
||||
{ value: '北京市', label: '北京市', children: [{ value: '北京市', label: '北京市' }] }
|
||||
];
|
||||
|
||||
var initialVehicles = [
|
||||
{ seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车', deliverySuccess: true, returnTaskCreated: false, returnTimeStart: null, returnTimeEnd: null, returnProvince: null, returnCity: null },
|
||||
{ seq: 2, brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌', deliverySuccess: false, returnTaskCreated: false, returnTimeStart: null, returnTimeEnd: null, returnProvince: null, returnCity: null },
|
||||
{ seq: 3, brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '', deliverySuccess: true, returnTaskCreated: true, returnTimeStart: '2026-04-10', returnTimeEnd: '2026-04-12', returnProvince: '浙江省', returnCity: '嘉兴市' }
|
||||
];
|
||||
|
||||
var vehiclesPair = useState(initialVehicles);
|
||||
var vehicles = vehiclesPair[0];
|
||||
var setVehicles = vehiclesPair[1];
|
||||
|
||||
var drawerOpenPair = useState(false);
|
||||
var drawerOpen = drawerOpenPair[0];
|
||||
var setDrawerOpen = drawerOpenPair[1];
|
||||
var drawerRowIndexPair = useState(-1);
|
||||
var drawerRowIndex = drawerRowIndexPair[0];
|
||||
var setDrawerRowIndex = drawerRowIndexPair[1];
|
||||
var drawerRangePair = useState(null);
|
||||
var drawerRange = drawerRangePair[0];
|
||||
var setDrawerRange = drawerRangePair[1];
|
||||
var drawerRegionPair = useState([]);
|
||||
var drawerRegion = drawerRegionPair[0];
|
||||
var setDrawerRegion = drawerRegionPair[1];
|
||||
|
||||
// Mock:当前查看的交车任务详情(从列表跳转带入或根据 id 拉取)
|
||||
var task = {
|
||||
projectName: '嘉兴某某物流氢能运输项目',
|
||||
contractCode: 'JXZL20260216YW101235A',
|
||||
@@ -17,21 +61,109 @@ const Component = function() {
|
||||
deliveryRegion: '浙江省 / 嘉兴市',
|
||||
deliveryLocation: '浙江省嘉兴市南湖区科技大道1号',
|
||||
planDeliveryDisplay: '2026-03-01至2026-03-05',
|
||||
billingStartDate: '2026-03-06',
|
||||
vehicles: [
|
||||
{ seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车' },
|
||||
{ seq: 2, brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌' },
|
||||
{ seq: 3, brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '' }
|
||||
]
|
||||
billingStartDate: '2026-03-06'
|
||||
};
|
||||
|
||||
var vehicleList = task.vehicles || [];
|
||||
function toYmd(d) {
|
||||
if (d == null) return '';
|
||||
if (typeof d === 'string') return d.slice(0, 10);
|
||||
if (typeof d.format === 'function') return d.format('YYYY-MM-DD');
|
||||
return '';
|
||||
}
|
||||
|
||||
function formatReturnTimeDisplay(row) {
|
||||
if (!row.returnTaskCreated || !row.returnTimeStart) return '-';
|
||||
var s = row.returnTimeStart;
|
||||
var e = row.returnTimeEnd || row.returnTimeStart;
|
||||
if (s === e) return s;
|
||||
return s + '至' + e;
|
||||
}
|
||||
|
||||
function formatReturnAreaDisplay(row) {
|
||||
if (!row.returnProvince || !row.returnCity) return '-';
|
||||
return row.returnProvince + '-' + row.returnCity;
|
||||
}
|
||||
|
||||
function returnStatusText(row) {
|
||||
return row.returnTaskCreated ? '已还车' : '未还车';
|
||||
}
|
||||
|
||||
var openReturnDrawer = useCallback(function (index) {
|
||||
setDrawerRowIndex(index);
|
||||
setDrawerRange(null);
|
||||
setDrawerRegion([]);
|
||||
setDrawerOpen(true);
|
||||
}, [setDrawerOpen, setDrawerRowIndex, setDrawerRange, setDrawerRegion]);
|
||||
|
||||
var closeReturnDrawer = useCallback(function () {
|
||||
setDrawerOpen(false);
|
||||
setDrawerRowIndex(-1);
|
||||
setDrawerRange(null);
|
||||
setDrawerRegion([]);
|
||||
}, [setDrawerOpen, setDrawerRowIndex, setDrawerRange, setDrawerRegion]);
|
||||
|
||||
var submitReturnDrawer = useCallback(function () {
|
||||
if (drawerRowIndex < 0) return;
|
||||
var dates = drawerRange;
|
||||
if (!dates || !dates[0] || !dates[1]) {
|
||||
message.warning('请选择还车时间');
|
||||
return;
|
||||
}
|
||||
if (!drawerRegion || drawerRegion.length < 2) {
|
||||
message.warning('请选择还车区域(省-市)');
|
||||
return;
|
||||
}
|
||||
var startStr = toYmd(dates[0]);
|
||||
var endStr = toYmd(dates[1]);
|
||||
var prov = drawerRegion[0];
|
||||
var city = drawerRegion[1];
|
||||
setVehicles(function (prev) {
|
||||
var next = prev.slice();
|
||||
var r = Object.assign({}, next[drawerRowIndex]);
|
||||
r.returnTaskCreated = true;
|
||||
r.returnTimeStart = startStr;
|
||||
r.returnTimeEnd = endStr;
|
||||
r.returnProvince = prov;
|
||||
r.returnCity = city;
|
||||
next[drawerRowIndex] = r;
|
||||
return next;
|
||||
});
|
||||
message.success('还车任务已创建');
|
||||
closeReturnDrawer();
|
||||
}, [drawerRowIndex, drawerRange, drawerRegion, setVehicles, closeReturnDrawer]);
|
||||
|
||||
var revokeReturn = useCallback(function (index) {
|
||||
if (vehicleArrived) {
|
||||
message.warning('交车单已确认车辆到达,无法撤销还车');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认撤销还车?',
|
||||
content: '撤销后将清除已填写的还车时间与还车区域,需重新发起还车。',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: function () {
|
||||
setVehicles(function (prev) {
|
||||
var next = prev.slice();
|
||||
var r = Object.assign({}, next[index]);
|
||||
r.returnTaskCreated = false;
|
||||
r.returnTimeStart = null;
|
||||
r.returnTimeEnd = null;
|
||||
r.returnProvince = null;
|
||||
r.returnCity = null;
|
||||
next[index] = r;
|
||||
return next;
|
||||
});
|
||||
message.success('已撤销还车');
|
||||
}
|
||||
});
|
||||
}, [vehicleArrived, setVehicles]);
|
||||
|
||||
var styles = {
|
||||
page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 },
|
||||
breadcrumb: { marginBottom: 16, color: '#666' },
|
||||
breadcrumbSep: { margin: '0 8px', color: '#999' },
|
||||
card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' },
|
||||
card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'visible' },
|
||||
cardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0' },
|
||||
cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' },
|
||||
cardBody: { padding: '20px 24px' },
|
||||
@@ -39,14 +171,35 @@ const Component = function() {
|
||||
formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 },
|
||||
label: { display: 'block', marginBottom: 6, color: '#333' },
|
||||
inputDisabled: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 },
|
||||
footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 },
|
||||
tableWrap: { marginTop: 16, overflowX: 'auto' },
|
||||
table: { width: '100%', borderCollapse: 'collapse', fontSize: 13 },
|
||||
th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 },
|
||||
td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }
|
||||
footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', alignItems: 'center', zIndex: 99 },
|
||||
tableWrap: { marginTop: 16, width: '100%', maxWidth: '100%', overflowX: 'auto', WebkitOverflowScrolling: 'touch' },
|
||||
/** 随列内容撑开总宽,窄屏由外层横向滚动 */
|
||||
table: { width: 'max-content', maxWidth: 'none', borderCollapse: 'collapse', fontSize: 13, tableLayout: 'auto' },
|
||||
th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600, whiteSpace: 'nowrap' },
|
||||
td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' },
|
||||
/** 表体单元格内输入框:按内容宽度,避免被 100% 拉满整表 */
|
||||
inputInTable: { boxSizing: 'border-box', minWidth: 72, maxWidth: 280, width: 'auto', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 },
|
||||
thStickyRight: {
|
||||
position: 'sticky',
|
||||
right: 0,
|
||||
zIndex: 4,
|
||||
backgroundColor: '#fafafa',
|
||||
boxShadow: '-6px 0 8px -4px rgba(0,0,0,0.12)',
|
||||
borderLeft: '1px solid #e8e8e8'
|
||||
},
|
||||
tdStickyRight: {
|
||||
position: 'sticky',
|
||||
right: 0,
|
||||
zIndex: 3,
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: '-6px 0 8px -4px rgba(0,0,0,0.1)',
|
||||
borderLeft: '1px solid #f0f0f0'
|
||||
},
|
||||
drawerFieldLabel: { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' },
|
||||
protoHint: { fontSize: 12, color: '#999', marginTop: 8 }
|
||||
};
|
||||
|
||||
var FormItemReadOnly = function(props) {
|
||||
var FormItemReadOnly = function (props) {
|
||||
return React.createElement('div', { style: styles.formCol },
|
||||
React.createElement('label', { style: styles.label }, props.label),
|
||||
React.createElement(Input, { value: props.value != null ? props.value : '', disabled: true, style: styles.inputDisabled })
|
||||
@@ -76,38 +229,122 @@ const Component = function() {
|
||||
React.createElement('th', { style: styles.th }, '车辆月租金'),
|
||||
React.createElement('th', { style: styles.th }, '服务费'),
|
||||
React.createElement('th', { style: styles.th }, '保证金'),
|
||||
React.createElement('th', { style: styles.th }, '备注')
|
||||
React.createElement('th', { style: styles.th }, '备注'),
|
||||
React.createElement('th', { style: styles.th }, '还车时间'),
|
||||
React.createElement('th', { style: styles.th }, '还车区域'),
|
||||
React.createElement('th', { style: styles.th }, '还车状态'),
|
||||
React.createElement('th', { style: Object.assign({}, styles.th, styles.thStickyRight, { width: 140, minWidth: 140 }) }, '操作')
|
||||
)
|
||||
);
|
||||
|
||||
var renderOperationCell = function (row, i) {
|
||||
if (!row.deliverySuccess) {
|
||||
return React.createElement('span', { style: { color: '#999' } }, '—');
|
||||
}
|
||||
if (!row.returnTaskCreated) {
|
||||
return React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { openReturnDrawer(i); } }, '还车');
|
||||
}
|
||||
return React.createElement(Button, {
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
style: { padding: 0 },
|
||||
disabled: vehicleArrived,
|
||||
onClick: function () { revokeReturn(i); }
|
||||
}, '撤销还车');
|
||||
};
|
||||
|
||||
var tableBody = React.createElement('tbody', null,
|
||||
vehicleList.length === 0
|
||||
? React.createElement('tr', null, React.createElement('td', { colSpan: 9, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '暂无车辆'))
|
||||
: vehicleList.map(function(row, i) {
|
||||
return React.createElement('tr', { key: i },
|
||||
React.createElement('td', { style: styles.td }, row.seq != null ? row.seq : '—'),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputDisabled }))
|
||||
);
|
||||
})
|
||||
vehicles.length === 0
|
||||
? React.createElement('tr', null, React.createElement('td', { colSpan: 13, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '暂无车辆'))
|
||||
: vehicles.map(function (row, i) {
|
||||
return React.createElement('tr', { key: row.vin || i },
|
||||
React.createElement('td', { style: styles.td }, row.seq != null ? row.seq : '—'),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: formatReturnTimeDisplay(row), disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: formatReturnAreaDisplay(row), disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: returnStatusText(row), disabled: true, style: styles.inputInTable })),
|
||||
React.createElement('td', { style: Object.assign({}, styles.td, styles.tdStickyRight, { minWidth: 140 }) }, renderOperationCell(row, i))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
var tableEl = React.createElement('div', { style: styles.tableWrap },
|
||||
React.createElement('table', { style: styles.table }, tableHeader, tableBody)
|
||||
);
|
||||
|
||||
var handleBack = function() {
|
||||
var handleBack = function () {
|
||||
if (window.history && window.history.back) window.history.back();
|
||||
else antd.message.info('返回');
|
||||
else message.info('返回');
|
||||
};
|
||||
|
||||
var reqSpecText = '查看交车任务\n一个「数字化资产ONEOS运管平台」中的「交车任务」「查看」模块。\n\n1.面包屑:业务管理-交车任务-查看交车任务\n\n2.表单(只读):项目名称、合同编码、客户名称、交车区域、交车地点、预计交车日期、开始计费日期等,根据交车任务数据反显。\n\n3.车辆列表(只读):\n3.1.序号:与租赁合同车辆序号对应;\n3.2.品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注。\n\n4.底部为返回按钮。';
|
||||
var drawerTitle = '还车';
|
||||
if (drawerRowIndex >= 0 && vehicles[drawerRowIndex]) {
|
||||
var dr = vehicles[drawerRowIndex];
|
||||
drawerTitle = '还车(序号 ' + dr.seq + (dr.plateNo ? ' · ' + dr.plateNo : '') + ')';
|
||||
}
|
||||
|
||||
var returnDrawer = React.createElement(Drawer, {
|
||||
title: drawerTitle,
|
||||
placement: 'right',
|
||||
width: 420,
|
||||
open: drawerOpen,
|
||||
onClose: closeReturnDrawer,
|
||||
destroyOnClose: true,
|
||||
footer: React.createElement('div', { style: { textAlign: 'right' } },
|
||||
React.createElement(Space, null,
|
||||
React.createElement(Button, { onClick: closeReturnDrawer }, '取消'),
|
||||
React.createElement(Button, { type: 'primary', onClick: submitReturnDrawer }, '提交')
|
||||
)
|
||||
)
|
||||
},
|
||||
React.createElement('div', { style: { paddingBottom: 8 } },
|
||||
React.createElement('div', { style: styles.drawerFieldLabel }, '还车时间'),
|
||||
React.createElement(RangePicker, {
|
||||
style: { width: '100%' },
|
||||
format: 'YYYY-MM-DD',
|
||||
placeholder: ['开始日期', '结束日期(单日请选同一天)'],
|
||||
value: drawerRange,
|
||||
onChange: function (dates) { setDrawerRange(dates && dates.length === 2 ? dates : null); }
|
||||
}),
|
||||
React.createElement('div', { style: { marginTop: 4, fontSize: 12, color: '#999' } }, '单日还车请将开始、结束选为同一天;跨天则为开始至结束时间段。')
|
||||
),
|
||||
React.createElement('div', { style: { marginTop: 20 } },
|
||||
React.createElement('div', { style: styles.drawerFieldLabel }, '还车区域'),
|
||||
React.createElement(Cascader, {
|
||||
style: { width: '100%' },
|
||||
options: returnRegionOptions,
|
||||
value: drawerRegion && drawerRegion.length ? drawerRegion : undefined,
|
||||
onChange: function (v) { setDrawerRegion(v || []); },
|
||||
placeholder: '请选择省 / 市',
|
||||
showSearch: true
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
var reqSpecText =
|
||||
'查看交车任务\n' +
|
||||
'一个「数字化资产ONEOS运管平台」中的「交车任务」「查看」模块。\n\n' +
|
||||
'1.面包屑:业务管理-交车任务-查看交车任务\n\n' +
|
||||
'2.表单(只读):项目名称、合同编码、客户名称、交车区域、交车地点、预计交车日期、开始计费日期等,根据交车任务数据反显。\n\n' +
|
||||
'3.车辆列表:\n' +
|
||||
'3.1.序号、品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注(只读);\n' +
|
||||
'3.2.还车时间:展示操作「还车」提交时填写的还车时间;单日为 YYYY-MM-DD,时间段为 YYYY-MM-DD至YYYY-MM-DD;未提交还车前显示为「-」;\n' +
|
||||
'3.3.还车区域:展示提交还车时选择的省-市(格式:省-市);未提交前显示为「-」;\n' +
|
||||
'3.4.还车状态:已还车(已创建还车任务)/ 未还车;\n' +
|
||||
'3.5.操作列:\n' +
|
||||
' · 仅「交车成功」的车辆显示操作;未交车成功的行操作列为「—」;\n' +
|
||||
' · 未创建还车任务时显示「还车」:点击后右侧抽屉打开,含「还车时间」(日期区间选择器,单输入框双日历,单日将起止选同一天)、「还车区域」(省-市二级级联),底部「提交」「取消」;提交校验通过后创建还车任务并关闭抽屉;\n' +
|
||||
' · 已创建还车任务后隐藏「还车」,显示「撤销还车」;点击「撤销还车」须二次确认(确认弹窗说明将清除还车时间与区域);确认后撤销还车任务;交车单已点击「车辆到达」后「撤销还车」按钮置灰不可点;\n' +
|
||||
'3.6.表格布局:表格总宽度随列内容撑开;容器内超出宽度时支持横向滚动;「操作」列固定在可视区域右侧(横向滚动时保持可见)。\n\n' +
|
||||
'4.原型演示:列表下方提供「模拟交车单已点击车辆到达」勾选,用于联调前验证「撤销还车」置灰逻辑;正式对接后由交车单状态接口驱动,可移除该勾选。\n\n' +
|
||||
'5.页面底部为「返回」按钮。';
|
||||
|
||||
return React.createElement('div', { style: styles.page },
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
|
||||
@@ -117,22 +354,26 @@ const Component = function() {
|
||||
React.createElement('span', null, '交车任务'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', { style: { color: '#1890ff' } }, '查看交车任务')),
|
||||
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')),
|
||||
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqModalOpen[1](true); } }, '查看需求说明')),
|
||||
React.createElement('div', { style: styles.card },
|
||||
React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')),
|
||||
React.createElement('div', { style: styles.cardBody },
|
||||
formRow1,
|
||||
formRow2,
|
||||
tableEl)),
|
||||
tableEl,
|
||||
React.createElement('div', { style: styles.protoHint },
|
||||
React.createElement(Checkbox, { checked: vehicleArrived, onChange: function (e) { setVehicleArrived(e.target.checked); } }),
|
||||
' 原型演示:模拟交车单已点击「车辆到达」(勾选后「撤销还车」置灰,对接接口后由交车单状态驱动)'))),
|
||||
React.createElement('div', { style: { height: 60 } }),
|
||||
React.createElement('div', { style: styles.footer },
|
||||
React.createElement(Button, { onClick: handleBack }, '返回')),
|
||||
returnDrawer,
|
||||
React.createElement(Modal, {
|
||||
title: '需求说明',
|
||||
open: reqModalOpen[0],
|
||||
onCancel: function() { reqModalOpen[1](false); },
|
||||
onCancel: function () { reqModalOpen[1](false); },
|
||||
width: 560,
|
||||
footer: React.createElement(Button, { onClick: function() { reqModalOpen[1](false); } }, '关闭'),
|
||||
footer: React.createElement(Button, { onClick: function () { reqModalOpen[1](false); } }, '关闭'),
|
||||
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
|
||||
}, React.createElement('div', { style: { padding: '8px 0', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, reqSpecText))
|
||||
);
|
||||
|
||||
812
web端/数据分析/业务部业绩明细.jsx
Normal file
812
web端/数据分析/业务部业绩明细.jsx
Normal file
@@ -0,0 +1,812 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 业务部业绩(明细 + 业绩/成本/利润汇总 Tab)
|
||||
// 明细:进入页面默认统计「上一自然月」;跨月后重新进入或刷新页面,默认月份随系统日期更新为上一个月。
|
||||
|
||||
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 message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
/** 表格正文:千分位 + 两位小数;空或 0 显示 -(利润等可为负,仍正常显示) */
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '-';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** 统计月份键 YYYY-MM(上一自然月;每月初「上月」随系统日期变化,刷新/重新进入页面即更新) */
|
||||
function getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
/** 月份选择器默认值(与 getLastMonthStatKey 同一自然月) */
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 filterActionsColStyle = { flex: '0 0 auto', marginLeft: 'auto' };
|
||||
var tableSingleLineStyle =
|
||||
'.biz-dept-perf-table .ant-table-thead th,.biz-dept-perf-table .ant-table-tbody td,.biz-dept-perf-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.biz-dept-perf-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var monthlyMetricKeys = ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other', 'total'];
|
||||
|
||||
function sumMonthlyRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
function monthlyColumnsFor(suffix) {
|
||||
if (suffix === '业绩') {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流业绩', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁业绩', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售业绩', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费业绩', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费业绩', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC业绩', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
if (suffix === '成本') {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流成本', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁成本', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售成本', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费成本', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费成本', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC成本', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流利润', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁利润', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售利润', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费利润', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费利润', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC利润', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
|
||||
function rowTotal(r) {
|
||||
var keys = ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other'];
|
||||
var s = 0;
|
||||
var any = false;
|
||||
keys.forEach(function (k) {
|
||||
var v = r[k];
|
||||
if (v === null || v === undefined || v === '') return;
|
||||
var n = Number(v);
|
||||
if (isNaN(n)) return;
|
||||
any = true;
|
||||
s += n;
|
||||
});
|
||||
return any ? s : null;
|
||||
}
|
||||
|
||||
function finalizeMonthlyRow(r) {
|
||||
var t = r.total != null && r.total !== '' ? r.total : rowTotal(r);
|
||||
return Object.assign({}, r, { total: t });
|
||||
}
|
||||
|
||||
/** 原型:2026 年按月业绩汇总 */
|
||||
var monthlyPerf2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: 1005557.94, lease: 4004.73, sales: 1495355.18, hydrogen: null, electricity: 88200.5, etc: 12000, other: 5000, total: null },
|
||||
{ month: 2, logistics: 250000, lease: 180000, sales: null, hydrogen: 3200, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: 180000, lease: 95000, sales: 220000, hydrogen: null, electricity: 45000, etc: 8000, other: 1200, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'perf-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
/** 原型:2026 年按月成本汇总 */
|
||||
var monthlyCost2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: 1167787.05, lease: 320000, sales: 980000, hydrogen: 15000, electricity: 72000, etc: 9800, other: 2400, total: null },
|
||||
{ month: 2, logistics: 210000, lease: 165000, sales: null, hydrogen: 2800, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: 155000, lease: 88000, sales: 195000, hydrogen: null, electricity: 38000, etc: 6500, other: 800, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'cost-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
/** 原型:2026 年按月利润汇总(可为负) */
|
||||
var monthlyProfit2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: -162229.11, lease: 45000, sales: null, hydrogen: -1200.5, electricity: 6200, etc: null, other: 800, total: null },
|
||||
{ month: 2, logistics: null, lease: 15000, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: null, lease: 7000, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'profit-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
var deptOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '华东业务部', label: '华东业务部' },
|
||||
{ value: '华南业务部', label: '华南业务部' },
|
||||
{ value: '华北业务部', label: '华北业务部' },
|
||||
{ value: '西南业务部', label: '西南业务部' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '尚建华', label: '尚建华' },
|
||||
{ value: '刘念念', label: '刘念念' },
|
||||
{ value: '谯云', label: '谯云' },
|
||||
{ value: '董剑煜', label: '董剑煜' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var allRows = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: '1',
|
||||
statMonth: sm,
|
||||
salesperson: '尚建华',
|
||||
dept: '华东业务部',
|
||||
logistics: 208868.38,
|
||||
lease: 36000,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: null,
|
||||
other: 55950.23,
|
||||
total: 300818.61
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
statMonth: sm,
|
||||
salesperson: '刘念念',
|
||||
dept: '华南业务部',
|
||||
logistics: 397181.78,
|
||||
lease: 223800,
|
||||
sales: 120000,
|
||||
hydrogen: 4500.12,
|
||||
electricity: 25000,
|
||||
etc: null,
|
||||
other: 224.14,
|
||||
total: 770706.04
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
statMonth: sm,
|
||||
salesperson: '谯云',
|
||||
dept: '华东业务部',
|
||||
logistics: 391153.33,
|
||||
lease: 15000,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: 8665.43,
|
||||
other: null,
|
||||
total: 414818.76
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
statMonth: sm,
|
||||
salesperson: '董剑煜',
|
||||
dept: '西南业务部',
|
||||
logistics: 8354.46,
|
||||
lease: null,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: null,
|
||||
other: 657.3,
|
||||
total: 9011.76
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
dept: undefined,
|
||||
salesperson: undefined
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
dept: undefined,
|
||||
salesperson: undefined
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e1) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (allRows || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.dept && r.dept !== applied.dept) return false;
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
return true;
|
||||
});
|
||||
}, [allRows, applied.month, applied.dept, applied.salesperson]);
|
||||
|
||||
var metricKeys = useMemo(function () {
|
||||
return ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other', 'total'];
|
||||
}, []);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
var sums = {};
|
||||
metricKeys.forEach(function (k) {
|
||||
sums[k] = filteredRows.reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}, [filteredRows, metricKeys]);
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
/** 与「查询」后已选月份一致:x年x月业绩明细表 */
|
||||
var detailTableTitle = useMemo(function () {
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
var m = applied.month.format('M');
|
||||
return y + '年' + m + '月业绩明细表';
|
||||
}
|
||||
return '业绩明细表';
|
||||
}, [applied.month]);
|
||||
|
||||
/** 与汇总 Tab「查询」后已选年份一致 */
|
||||
var perfSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年业绩汇总';
|
||||
}
|
||||
return '业绩汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var costSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年成本汇总';
|
||||
}
|
||||
return '成本汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var profitSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年利润汇总';
|
||||
}
|
||||
return '利润汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var perfMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyPerf2026); }, [monthlyDataSource, monthlyPerf2026]);
|
||||
var costMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyCost2026); }, [monthlyDataSource, monthlyCost2026]);
|
||||
var profitMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyProfit2026); }, [monthlyDataSource, monthlyProfit2026]);
|
||||
|
||||
var perfMonthlySums = useMemo(function () { return sumMonthlyRows(perfMonthlyRows, monthlyMetricKeys); }, [perfMonthlyRows]);
|
||||
var costMonthlySums = useMemo(function () { return sumMonthlyRows(costMonthlyRows, monthlyMetricKeys); }, [costMonthlyRows]);
|
||||
var profitMonthlySums = useMemo(function () { return sumMonthlyRows(profitMonthlyRows, monthlyMetricKeys); }, [profitMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), dept: undefined, salesperson: undefined };
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExportDetail = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'物流业绩',
|
||||
'租赁业绩',
|
||||
'销售业绩',
|
||||
'氢费业绩',
|
||||
'电费业绩',
|
||||
'ETC业绩',
|
||||
'其他',
|
||||
'合计'
|
||||
];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
fmtCell(r.logistics),
|
||||
fmtCell(r.lease),
|
||||
fmtCell(r.sales),
|
||||
fmtCell(r.hydrogen),
|
||||
fmtCell(r.electricity),
|
||||
fmtCell(r.etc),
|
||||
fmtCell(r.other),
|
||||
fmtCell(r.total)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(metricKeys.map(function (k) { return fmtSum(columnSums[k] || 0); })));
|
||||
downloadCsv('业绩明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, metricKeys, columnSums]);
|
||||
|
||||
var exportMonthly = useCallback(function (rows, sums, headerTitles, filePrefix) {
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var body = [headerTitles].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.logistics),
|
||||
fmtCell(r.lease),
|
||||
fmtCell(r.sales),
|
||||
fmtCell(r.hydrogen),
|
||||
fmtCell(r.electricity),
|
||||
fmtCell(r.etc),
|
||||
fmtCell(r.other),
|
||||
fmtCell(r.total)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(monthlyMetricKeys.map(function (k) { return fmtSum(sums[k] || 0); })));
|
||||
downloadCsv(filePrefix + '_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, []);
|
||||
|
||||
var handleExportPerfMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
perfMonthlyRows,
|
||||
perfMonthlySums,
|
||||
['月份', '物流业绩', '租赁业绩', '销售业绩', '氢费业绩', '电费业绩', 'ETC业绩', '其他', '合计'],
|
||||
'业绩汇总'
|
||||
);
|
||||
}, [exportMonthly, perfMonthlyRows, perfMonthlySums]);
|
||||
|
||||
var handleExportCostMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
costMonthlyRows,
|
||||
costMonthlySums,
|
||||
['月份', '物流成本', '租赁成本', '销售成本', '氢费成本', '电费成本', 'ETC成本', '其他', '合计'],
|
||||
'成本汇总'
|
||||
);
|
||||
}, [exportMonthly, costMonthlyRows, costMonthlySums]);
|
||||
|
||||
var handleExportProfitMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
profitMonthlyRows,
|
||||
profitMonthlySums,
|
||||
['月份', '物流利润', '租赁利润', '销售利润', '氢费利润', '电费利润', 'ETC利润', '其他', '合计'],
|
||||
'利润汇总'
|
||||
);
|
||||
}, [exportMonthly, profitMonthlyRows, profitMonthlySums]);
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 110, align: 'center' },
|
||||
{ title: '物流业绩', dataIndex: 'logistics', key: 'logistics', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁业绩', dataIndex: 'lease', key: 'lease', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售业绩', dataIndex: 'sales', key: 'sales', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费业绩', dataIndex: 'hydrogen', key: 'hydrogen', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费业绩', dataIndex: 'electricity', key: 'electricity', width: 140, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC业绩', dataIndex: 'etc', key: 'etc', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 130, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
metricKeys.map(function (k, idx) {
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'center' },
|
||||
fmtSum(columnSums[k] || 0)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [metricKeys, columnSums]);
|
||||
|
||||
function renderMonthlySummary(sums) {
|
||||
return function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyMetricKeys.map(function (k, idx) {
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'center' },
|
||||
fmtSum(sums[k] || 0)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
var columnsPerfMonthly = useMemo(function () { return monthlyColumnsFor('业绩'); }, []);
|
||||
var columnsCostMonthly = useMemo(function () { return monthlyColumnsFor('成本'); }, []);
|
||||
var columnsProfitMonthly = useMemo(function () { return monthlyColumnsFor('利润'); }, []);
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(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, { size: 8, wrap: true },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '业绩明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '选择器选择月份',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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, {
|
||||
placeholder: '选择器选择业务部门',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: deptOptions,
|
||||
value: draft.dept,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { dept: v }); }); },
|
||||
showSearch: true,
|
||||
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(Select, {
|
||||
placeholder: '选择器选择业务员',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
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, { size: 8, wrap: true },
|
||||
React.createElement(Button, { onClick: handleReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportDetail }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, detailTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 1200 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'perfSum',
|
||||
label: '业绩汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPerfMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, perfSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPerfMonthly,
|
||||
dataSource: perfMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(perfMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'costSum',
|
||||
label: '成本汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportCostMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, costSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsCostMonthly,
|
||||
dataSource: costMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(costMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'profitSum',
|
||||
label: '利润汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportProfitMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, profitSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsProfitMonthly,
|
||||
dataSource: profitMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(profitMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '业务部业绩' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'biz-dept-perf-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
843
web端/数据分析/物流业务月度统计.jsx
Normal file
843
web端/数据分析/物流业务月度统计.jsx
Normal file
@@ -0,0 +1,843 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 物流业务月度统计:Tab「物流业务人员明细」+「物流盈亏月度汇总」(1–12 月汇总,交互同业务部业绩汇总)
|
||||
// 人员明细:进入页面默认统计「上一自然月」
|
||||
|
||||
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 message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtIntCell(n) {
|
||||
if (n === null || n === undefined || n === '') return '—';
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
if (x === 0) return '—';
|
||||
return String(Math.round(x));
|
||||
}
|
||||
|
||||
/** 盈亏:允许负数、零显示 0.00 */
|
||||
function fmtProfit(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
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 getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 tableSingleLineStyle =
|
||||
'.logistics-monthly-stat-table .ant-table-thead th,.logistics-monthly-stat-table .ant-table-tbody td,.logistics-monthly-stat-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.logistics-monthly-stat-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var profitNegStyle = { background: '#fff1f0', padding: '2px 8px', borderRadius: 4, display: 'inline-block' };
|
||||
|
||||
var numericSumKeys = [
|
||||
'income',
|
||||
'hydrogenFee',
|
||||
'laborCost',
|
||||
'etcFee',
|
||||
'electricityFee',
|
||||
'salary',
|
||||
'vehicleCount',
|
||||
'tireCost',
|
||||
'deprecInsuranceFee',
|
||||
'socialSecurityFee',
|
||||
'trailerFee',
|
||||
'parkingFee',
|
||||
'vehicleTotalCost',
|
||||
'totalCost',
|
||||
'profitLoss'
|
||||
];
|
||||
|
||||
function sumRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var rawRowsTemplate = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: 'lm-1',
|
||||
statMonth: sm,
|
||||
salesperson: '谈云',
|
||||
businessName: '上海虹钦物流有限公司',
|
||||
vehicleModel: '帕立安4.5T',
|
||||
income: 1005557.94,
|
||||
hydrogenFee: 185420.5,
|
||||
laborCost: 128800,
|
||||
etcFee: 45230.12,
|
||||
electricityFee: 62300.8,
|
||||
salary: 96000,
|
||||
vehicleCount: 12,
|
||||
tireCost: 8900,
|
||||
deprecInsuranceFee: 156000,
|
||||
socialSecurityFee: 28400,
|
||||
trailerFee: 12000,
|
||||
parkingFee: 6800,
|
||||
vehicleTotalCost: 420000,
|
||||
totalCost: 918851.42,
|
||||
profitLoss: 86706.52,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-2',
|
||||
statMonth: sm,
|
||||
salesperson: '刘念念',
|
||||
businessName: '杭州绿道城配科技有限公司',
|
||||
vehicleModel: '古道车队',
|
||||
income: 680200,
|
||||
hydrogenFee: 142000,
|
||||
laborCost: 98000,
|
||||
etcFee: 32100,
|
||||
electricityFee: 41000,
|
||||
salary: 72000,
|
||||
vehicleCount: 8,
|
||||
tireCost: 5600,
|
||||
deprecInsuranceFee: 112000,
|
||||
socialSecurityFee: 21000,
|
||||
trailerFee: 8000,
|
||||
parkingFee: 4200,
|
||||
vehicleTotalCost: 298000,
|
||||
totalCost: 832900,
|
||||
profitLoss: -152700,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-3',
|
||||
statMonth: sm,
|
||||
salesperson: '谈云',
|
||||
businessName: '宁波港联氢运物流有限公司',
|
||||
vehicleModel: '飞越49T',
|
||||
income: 892300.5,
|
||||
hydrogenFee: 210000,
|
||||
laborCost: 145000,
|
||||
etcFee: 38900,
|
||||
electricityFee: 55000,
|
||||
salary: 88000,
|
||||
vehicleCount: 10,
|
||||
tireCost: 7200,
|
||||
deprecInsuranceFee: 198000,
|
||||
socialSecurityFee: 25600,
|
||||
trailerFee: 15000,
|
||||
parkingFee: 5500,
|
||||
vehicleTotalCost: 512000,
|
||||
totalCost: 1284200.5,
|
||||
profitLoss: -391900,
|
||||
remark: '淡季线路调整'
|
||||
},
|
||||
{
|
||||
key: 'lm-4',
|
||||
statMonth: sm,
|
||||
salesperson: '谯云',
|
||||
businessName: '嘉兴南湖氢能示范运营',
|
||||
vehicleModel: '帕立安4.5T',
|
||||
income: 325000,
|
||||
hydrogenFee: 52000,
|
||||
laborCost: 38000,
|
||||
etcFee: 12000,
|
||||
electricityFee: 18500,
|
||||
salary: 28000,
|
||||
vehicleCount: 4,
|
||||
tireCost: 2100,
|
||||
deprecInsuranceFee: 48000,
|
||||
socialSecurityFee: 9200,
|
||||
trailerFee: null,
|
||||
parkingFee: 1800,
|
||||
vehicleTotalCost: 125000,
|
||||
totalCost: 334600,
|
||||
profitLoss: -9600,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-5',
|
||||
statMonth: sm,
|
||||
salesperson: '董剑煜',
|
||||
businessName: '温州瓯江冷链专线',
|
||||
vehicleModel: '宇通49T',
|
||||
income: 456780,
|
||||
hydrogenFee: 88000,
|
||||
laborCost: 56000,
|
||||
etcFee: 22000,
|
||||
electricityFee: 31000,
|
||||
salary: 42000,
|
||||
vehicleCount: 5,
|
||||
tireCost: 4500,
|
||||
deprecInsuranceFee: 72000,
|
||||
socialSecurityFee: 11800,
|
||||
trailerFee: 6000,
|
||||
parkingFee: 2400,
|
||||
vehicleTotalCost: 198000,
|
||||
totalCost: 583700,
|
||||
profitLoss: -126920,
|
||||
remark: ''
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
var businessNameOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.businessName) set[r.businessName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
/** 物流盈亏月度汇总:按自然月 1–12 列示(原型仅 2026 年有数据) */
|
||||
var monthlyPlMetricKeys = numericSumKeys;
|
||||
|
||||
var monthlyPlSummary2026 = useMemo(function () {
|
||||
function mk(m, p) {
|
||||
var r = Object.assign({ key: 'plm-' + m, month: m }, p);
|
||||
if (r.profitLoss == null && r.income != null && r.totalCost != null) {
|
||||
r.profitLoss = Number(r.income) - Number(r.totalCost);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
function empty(m) {
|
||||
return mk(m, {});
|
||||
}
|
||||
return [
|
||||
mk(1, {
|
||||
income: 3350838.44,
|
||||
hydrogenFee: 537400,
|
||||
laborCost: 418800,
|
||||
etcFee: 148230.12,
|
||||
electricityFee: 228800.8,
|
||||
salary: 386000,
|
||||
vehicleCount: 39,
|
||||
tireCost: 28100,
|
||||
deprecInsuranceFee: 586000,
|
||||
socialSecurityFee: 96000,
|
||||
trailerFee: 53000,
|
||||
parkingFee: 19600,
|
||||
vehicleTotalCost: 1553000,
|
||||
totalCost: 4123731.44,
|
||||
profitLoss: -772893
|
||||
}),
|
||||
mk(2, {
|
||||
income: 2680000,
|
||||
hydrogenFee: 412000,
|
||||
laborCost: 298000,
|
||||
etcFee: 98500,
|
||||
electricityFee: 156000,
|
||||
salary: 265000,
|
||||
vehicleCount: 26,
|
||||
tireCost: 19200,
|
||||
deprecInsuranceFee: 445000,
|
||||
socialSecurityFee: 68400,
|
||||
trailerFee: 41000,
|
||||
parkingFee: 15800,
|
||||
vehicleTotalCost: 1180000,
|
||||
totalCost: 3550900,
|
||||
profitLoss: -870900
|
||||
}),
|
||||
mk(3, {
|
||||
income: 3012000,
|
||||
hydrogenFee: 468000,
|
||||
laborCost: 352000,
|
||||
etcFee: 108000,
|
||||
electricityFee: 172500,
|
||||
salary: 298000,
|
||||
vehicleCount: 31,
|
||||
tireCost: 22100,
|
||||
deprecInsuranceFee: 502000,
|
||||
socialSecurityFee: 75200,
|
||||
trailerFee: 38500,
|
||||
parkingFee: 16900,
|
||||
vehicleTotalCost: 1285000,
|
||||
totalCost: 3488200,
|
||||
profitLoss: -476200
|
||||
}),
|
||||
empty(4),
|
||||
empty(5),
|
||||
empty(6),
|
||||
empty(7),
|
||||
empty(8),
|
||||
empty(9),
|
||||
empty(10),
|
||||
empty(11),
|
||||
empty(12)
|
||||
];
|
||||
}, []);
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var mainTabState = useState('personDetail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
businessName: undefined
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
businessName: undefined
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (rawRowsTemplate || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
if (applied.businessName && r.businessName !== applied.businessName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rawRowsTemplate, applied.month, applied.salesperson, applied.businessName]);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
return sumRows(filteredRows, numericSumKeys);
|
||||
}, [filteredRows]);
|
||||
|
||||
var reportTitle = useMemo(function () {
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
var m = applied.month.format('M');
|
||||
return y + '年' + m + '月浙江羚牛氢能自运营物流业务盈亏月度汇总表';
|
||||
}
|
||||
return '浙江羚牛氢能自运营物流业务盈亏月度汇总表';
|
||||
}, [applied.month]);
|
||||
|
||||
var plSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年物流盈亏月度汇总';
|
||||
}
|
||||
return '物流盈亏月度汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyPlDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var plMonthlyRows = useMemo(function () {
|
||||
return monthlyPlDataSource(monthlyPlSummary2026);
|
||||
}, [monthlyPlDataSource, monthlyPlSummary2026]);
|
||||
|
||||
var plMonthlySums = useMemo(function () {
|
||||
return sumRows(plMonthlyRows, monthlyPlMetricKeys);
|
||||
}, [plMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), salesperson: undefined, businessName: undefined };
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExport = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'业务名称',
|
||||
'系统车型',
|
||||
'收入',
|
||||
'氢费',
|
||||
'人工费用',
|
||||
'ETC',
|
||||
'电费',
|
||||
'薪资',
|
||||
'投入车数',
|
||||
'轮胎',
|
||||
'(折旧、年审、保险)费用',
|
||||
'社保服务费',
|
||||
'挂车费用',
|
||||
'停车费',
|
||||
'车总费用',
|
||||
'总成本',
|
||||
'盈亏',
|
||||
'备注'
|
||||
];
|
||||
var rowLine = function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
r.businessName,
|
||||
r.vehicleModel,
|
||||
fmtCell(r.income),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.laborCost),
|
||||
fmtCell(r.etcFee),
|
||||
fmtCell(r.electricityFee),
|
||||
fmtCell(r.salary),
|
||||
fmtIntCell(r.vehicleCount),
|
||||
fmtCell(r.tireCost),
|
||||
fmtCell(r.deprecInsuranceFee),
|
||||
fmtCell(r.socialSecurityFee),
|
||||
fmtCell(r.trailerFee),
|
||||
fmtCell(r.parkingFee),
|
||||
fmtCell(r.vehicleTotalCost),
|
||||
fmtCell(r.totalCost),
|
||||
fmtProfit(r.profitLoss),
|
||||
r.remark || ''
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(rowLine));
|
||||
var sumLine = ['总计', '', ''].concat(
|
||||
numericSumKeys.map(function (k, idx) {
|
||||
if (k === 'vehicleCount') return fmtIntCell(columnSums[k]);
|
||||
if (k === 'profitLoss') return fmtSum(columnSums[k]);
|
||||
return fmtSum(columnSums[k]);
|
||||
})
|
||||
);
|
||||
body.push(sumLine);
|
||||
downloadCsv('物流业务月度统计_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, columnSums]);
|
||||
|
||||
var plMonthlyExportHeaders = [
|
||||
'月份',
|
||||
'收入',
|
||||
'氢费',
|
||||
'人工费用',
|
||||
'ETC',
|
||||
'电费',
|
||||
'薪资',
|
||||
'投入车数',
|
||||
'轮胎',
|
||||
'(折旧、年审、保险)费用',
|
||||
'社保服务费',
|
||||
'挂车费用',
|
||||
'停车费',
|
||||
'车总费用',
|
||||
'总成本',
|
||||
'盈亏月度合计'
|
||||
];
|
||||
|
||||
var handleExportPlMonthly = useCallback(function () {
|
||||
var rows = plMonthlyRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var rowLine = function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.income),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.laborCost),
|
||||
fmtCell(r.etcFee),
|
||||
fmtCell(r.electricityFee),
|
||||
fmtCell(r.salary),
|
||||
fmtIntCell(r.vehicleCount),
|
||||
fmtCell(r.tireCost),
|
||||
fmtCell(r.deprecInsuranceFee),
|
||||
fmtCell(r.socialSecurityFee),
|
||||
fmtCell(r.trailerFee),
|
||||
fmtCell(r.parkingFee),
|
||||
fmtCell(r.vehicleTotalCost),
|
||||
fmtCell(r.totalCost),
|
||||
fmtProfit(r.profitLoss)
|
||||
];
|
||||
};
|
||||
var body = [plMonthlyExportHeaders].concat(rows.map(rowLine));
|
||||
body.push(['总计'].concat(
|
||||
monthlyPlMetricKeys.map(function (k) {
|
||||
if (k === 'vehicleCount') return fmtIntCell(plMonthlySums[k]);
|
||||
return fmtSum(plMonthlySums[k] || 0);
|
||||
})
|
||||
));
|
||||
downloadCsv('物流盈亏月度汇总_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, [plMonthlyRows, plMonthlySums]);
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88, align: 'center', fixed: 'left' },
|
||||
{ title: '业务名称', dataIndex: 'businessName', key: 'businessName', width: 200, ellipsis: true, fixed: 'left' },
|
||||
{ title: '系统车型', dataIndex: 'vehicleModel', key: 'vehicleModel', width: 110, align: 'center' },
|
||||
{ title: '收入', dataIndex: 'income', key: 'income', width: 120, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '人工费用', dataIndex: 'laborCost', key: 'laborCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC', dataIndex: 'etcFee', key: 'etcFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费', dataIndex: 'electricityFee', key: 'electricityFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '薪资', dataIndex: 'salary', key: 'salary', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '投入车数', dataIndex: 'vehicleCount', key: 'vehicleCount', width: 92, align: 'right', render: function (v) { return fmtIntCell(v); } },
|
||||
{ title: '轮胎', dataIndex: 'tireCost', key: 'tireCost', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '(折旧、年审、保险)费用', dataIndex: 'deprecInsuranceFee', key: 'deprecInsuranceFee', width: 160, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '社保服务费', dataIndex: 'socialSecurityFee', key: 'socialSecurityFee', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '挂车费用', dataIndex: 'trailerFee', key: 'trailerFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '停车费', dataIndex: 'parkingFee', key: 'parkingFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '车总费用', dataIndex: 'vehicleTotalCost', key: 'vehicleTotalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 120, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{
|
||||
title: '盈亏',
|
||||
dataIndex: 'profitLoss',
|
||||
key: 'profitLoss',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
render: function (v) {
|
||||
var s = fmtProfit(v);
|
||||
var neg = v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) < 0;
|
||||
return neg ? React.createElement('span', { style: profitNegStyle }, s) : s;
|
||||
}
|
||||
},
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 140, ellipsis: true }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 3 }, '总计'),
|
||||
numericSumKeys.map(function (k, idx) {
|
||||
var text;
|
||||
if (k === 'vehicleCount') text = fmtIntCell(columnSums[k]);
|
||||
else text = fmtSum(columnSums[k]);
|
||||
var isNegProfit = k === 'profitLoss' && Number(columnSums[k]) < 0;
|
||||
var child = isNegProfit ? React.createElement('span', { style: profitNegStyle }, text) : text;
|
||||
return React.createElement(SummaryCell, { key: k, index: idx + 3, align: 'right' }, child);
|
||||
}),
|
||||
React.createElement(SummaryCell, { index: numericSumKeys.length + 3, align: 'left' }, '')
|
||||
)
|
||||
);
|
||||
}, [columnSums]);
|
||||
|
||||
var columnsPlMonthly = useMemo(function () {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 64, align: 'center' },
|
||||
{ title: '收入', dataIndex: 'income', key: 'income', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '人工费用', dataIndex: 'laborCost', key: 'laborCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC', dataIndex: 'etcFee', key: 'etcFee', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费', dataIndex: 'electricityFee', key: 'electricityFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '薪资', dataIndex: 'salary', key: 'salary', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '投入车数', dataIndex: 'vehicleCount', key: 'vehicleCount', width: 92, align: 'right', render: function (v) { return fmtIntCell(v); } },
|
||||
{ title: '轮胎', dataIndex: 'tireCost', key: 'tireCost', width: 84, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '(折旧、年审、保险)费用', dataIndex: 'deprecInsuranceFee', key: 'deprecInsuranceFee', width: 168, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '社保服务费', dataIndex: 'socialSecurityFee', key: 'socialSecurityFee', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '挂车费用', dataIndex: 'trailerFee', key: 'trailerFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '停车费', dataIndex: 'parkingFee', key: 'parkingFee', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '车总费用', dataIndex: 'vehicleTotalCost', key: 'vehicleTotalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{
|
||||
title: '盈亏月度合计',
|
||||
dataIndex: 'profitLoss',
|
||||
key: 'profitLoss',
|
||||
width: 128,
|
||||
align: 'right',
|
||||
render: function (v) {
|
||||
var s = fmtProfit(v);
|
||||
var neg = v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) < 0;
|
||||
return neg ? React.createElement('span', { style: profitNegStyle }, s) : s;
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummaryPlMonthly = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyPlMetricKeys.map(function (k, idx) {
|
||||
var text = k === 'vehicleCount' ? fmtIntCell(plMonthlySums[k]) : fmtSum(plMonthlySums[k] || 0);
|
||||
var isNeg = k === 'profitLoss' && Number(plMonthlySums[k]) < 0;
|
||||
var child = isNeg ? React.createElement('span', { style: profitNegStyle }, text) : text;
|
||||
return React.createElement(SummaryCell, { key: k, index: idx + 1, align: 'right' }, child);
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [plMonthlySums]);
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(v); }
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'personDetail',
|
||||
label: '物流业务人员明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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, {
|
||||
placeholder: '请选择业务员',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
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(Select, {
|
||||
placeholder: '请选择业务名称',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: businessNameOptions,
|
||||
value: draft.businessName,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { businessName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
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(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExport }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, reportTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'logistics-monthly-stat-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 2600 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'plMonthly',
|
||||
label: '物流盈亏月度汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPlMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, plSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'logistics-monthly-stat-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPlMonthly,
|
||||
dataSource: plMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummaryPlMonthly,
|
||||
scroll: { x: 2480 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '物流业务月度统计' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'logistics-monthly-stat-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
694
web端/数据分析/租赁客户氢费台账.jsx
Normal file
694
web端/数据分析/租赁客户氢费台账.jsx
Normal file
@@ -0,0 +1,694 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 租赁客户氢费台账:Tab「租赁客户氢费明细」+「租赁客户氢费总计」(按客户×月汇总金额)
|
||||
|
||||
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 message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
var RangePicker = DatePicker.RangePicker;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
function fmtDateDash(iso) {
|
||||
if (!iso) return '—';
|
||||
try {
|
||||
if (window.dayjs) {
|
||||
var d = window.dayjs(iso);
|
||||
if (!d.isValid()) return '—';
|
||||
return d.format('YYYY-MM-DD');
|
||||
}
|
||||
} catch (e1) {}
|
||||
try {
|
||||
var p = String(iso).split(/[-/]/);
|
||||
if (p.length >= 3) {
|
||||
var y = parseInt(p[0], 10);
|
||||
var m = parseInt(p[1], 10);
|
||||
var day = parseInt(p[2], 10);
|
||||
if (!isNaN(y) && !isNaN(m) && !isNaN(day)) {
|
||||
return y + '-' + (m < 10 ? '0' + m : m) + '-' + (day < 10 ? '0' + day : day);
|
||||
}
|
||||
}
|
||||
} catch (e2) {}
|
||||
return String(iso);
|
||||
}
|
||||
|
||||
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 getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStatMonthKey(monthsAgo) {
|
||||
var off = monthsAgo == null ? 1 : monthsAgo;
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(off, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - off);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthRange() {
|
||||
try {
|
||||
if (window.dayjs) {
|
||||
var s = window.dayjs().subtract(1, 'month').startOf('month');
|
||||
var e = window.dayjs().subtract(1, 'month').endOf('month');
|
||||
return [s, e];
|
||||
}
|
||||
} catch (e1) {}
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
function rowDayMs(iso) {
|
||||
if (!iso) return NaN;
|
||||
var t = Date.parse(String(iso).length <= 10 ? String(iso) + 'T12:00:00' : iso);
|
||||
return isNaN(t) ? NaN : t;
|
||||
}
|
||||
|
||||
function inRange(iso, range) {
|
||||
if (!range || !range[0] || !range[1]) return true;
|
||||
var t = rowDayMs(iso);
|
||||
if (isNaN(t)) return false;
|
||||
var s = range[0].startOf ? range[0].startOf('day').valueOf() : NaN;
|
||||
var e = range[1].endOf ? range[1].endOf('day').valueOf() : NaN;
|
||||
if (isNaN(s) || isNaN(e)) return true;
|
||||
return t >= s && t <= e;
|
||||
}
|
||||
|
||||
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%' };
|
||||
/** 与「车辆租赁合同」列表表一致:单行不换行;本页含合计行故增加 summary 单元格 */
|
||||
var tableSingleLineStyle =
|
||||
'.contract-list-table .ant-table-thead th,.contract-list-table .ant-table-tbody td,.contract-list-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var listToolbarStyle = { marginBottom: 16, display: 'flex', justifyContent: 'flex-end', alignItems: 'center', flexWrap: 'wrap', gap: 8 };
|
||||
var tabsBarStyle = '.lease-h2-ledger-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var boldNumStyle = { fontWeight: 600 };
|
||||
|
||||
/** 原型明细(statMonth / bizDate 随「上一月、上上月」变化,与默认筛选区间一致) */
|
||||
var allDetailRows = useMemo(function () {
|
||||
var m0 = getStatMonthKey(1);
|
||||
var m1 = getStatMonthKey(2);
|
||||
var p0 = m0.split('-');
|
||||
var p1 = m1.split('-');
|
||||
var y0 = parseInt(p0[0], 10);
|
||||
var mo0 = parseInt(p0[1], 10);
|
||||
var y1 = parseInt(p1[0], 10);
|
||||
var mo1 = parseInt(p1[1], 10);
|
||||
function iso(y, mo, day) {
|
||||
return y + '-' + (mo < 10 ? '0' + mo : mo) + '-' + (day < 10 ? '0' + day : day);
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'h2-1',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 1),
|
||||
salesperson: '刘念念',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '沪A68122F',
|
||||
quantityKg: 45.6,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.5,
|
||||
costAmount: 1299.6,
|
||||
unitPrice: 32,
|
||||
amount: 1459.2
|
||||
},
|
||||
{
|
||||
key: 'h2-2',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 3),
|
||||
salesperson: '尚建华',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '粤AGP3513',
|
||||
quantityKg: 38.2,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.5,
|
||||
costAmount: 1088.7,
|
||||
unitPrice: 32,
|
||||
amount: 1222.4
|
||||
},
|
||||
{
|
||||
key: 'h2-3',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 5),
|
||||
salesperson: '刘念念',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
plateNo: '浙A88888F',
|
||||
quantityKg: 52,
|
||||
location: '杭州钱塘综合能源站',
|
||||
purchasePrice: 29,
|
||||
costAmount: 1508,
|
||||
unitPrice: 33.5,
|
||||
amount: 1742
|
||||
},
|
||||
{
|
||||
key: 'h2-4',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 12),
|
||||
salesperson: '尚建华',
|
||||
customerName: '宁波港联氢运物流有限公司',
|
||||
plateNo: '浙B12345F',
|
||||
quantityKg: 60.5,
|
||||
location: '宁波北仑氢能示范站',
|
||||
purchasePrice: 27.8,
|
||||
costAmount: 1681.9,
|
||||
unitPrice: 31.2,
|
||||
amount: 1887.6
|
||||
},
|
||||
{
|
||||
key: 'h2-5',
|
||||
statMonth: m1,
|
||||
bizDate: iso(y1, mo1, 2),
|
||||
salesperson: '刘念念',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '沪A68122F',
|
||||
quantityKg: 41,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.6,
|
||||
costAmount: 1172.6,
|
||||
unitPrice: 32,
|
||||
amount: 1312
|
||||
},
|
||||
{
|
||||
key: 'h2-6',
|
||||
statMonth: m1,
|
||||
bizDate: iso(y1, mo1, 18),
|
||||
salesperson: '刘念念',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
plateNo: '浙A88888F',
|
||||
quantityKg: 48.5,
|
||||
location: '杭州钱塘综合能源站',
|
||||
purchasePrice: 29.1,
|
||||
costAmount: 1411.35,
|
||||
unitPrice: 33.5,
|
||||
amount: 1624.75
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [allDetailRows]);
|
||||
|
||||
var customerOptions = useMemo(function () {
|
||||
var set = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.customerName) set[r.customerName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [allDetailRows]);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var rangeDefault = useMemo(function () { return getLastMonthRange(); }, []);
|
||||
|
||||
var draftDetailState = useState(function () {
|
||||
return {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var draftDetail = draftDetailState[0];
|
||||
var setDraftDetail = draftDetailState[1];
|
||||
|
||||
var appliedDetailState = useState(function () {
|
||||
return {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var appliedDetail = appliedDetailState[0];
|
||||
var setAppliedDetail = appliedDetailState[1];
|
||||
|
||||
var draftTotalState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var draftTotal = draftTotalState[0];
|
||||
var setDraftTotal = draftTotalState[1];
|
||||
|
||||
var appliedTotalState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var appliedTotal = appliedTotalState[0];
|
||||
var setAppliedTotal = appliedTotalState[1];
|
||||
|
||||
var filteredDetailRows = useMemo(function () {
|
||||
return (allDetailRows || []).filter(function (r) {
|
||||
if (appliedDetail.dateRange && appliedDetail.dateRange[0] && appliedDetail.dateRange[1]) {
|
||||
if (!inRange(r.bizDate, appliedDetail.dateRange)) return false;
|
||||
}
|
||||
if (appliedDetail.salesperson && r.salesperson !== appliedDetail.salesperson) return false;
|
||||
if (appliedDetail.customerName && r.customerName !== appliedDetail.customerName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [allDetailRows, appliedDetail.dateRange, appliedDetail.salesperson, appliedDetail.customerName]);
|
||||
|
||||
var detailSums = useMemo(function () {
|
||||
var q = 0;
|
||||
var c = 0;
|
||||
var a = 0;
|
||||
filteredDetailRows.forEach(function (r) {
|
||||
q += isNaN(Number(r.quantityKg)) ? 0 : Number(r.quantityKg);
|
||||
c += isNaN(Number(r.costAmount)) ? 0 : Number(r.costAmount);
|
||||
a += isNaN(Number(r.amount)) ? 0 : Number(r.amount);
|
||||
});
|
||||
return { quantityKg: q, costAmount: c, amount: a };
|
||||
}, [filteredDetailRows]);
|
||||
|
||||
var totalByCustomerMonthRows = useMemo(function () {
|
||||
var mk = '';
|
||||
if (appliedTotal.month && appliedTotal.month.format) mk = appliedTotal.month.format('YYYY-MM');
|
||||
if (!mk) return [];
|
||||
var map = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.statMonth !== mk) return;
|
||||
if (appliedTotal.customerName && r.customerName !== appliedTotal.customerName) return;
|
||||
var k = r.customerName || '';
|
||||
if (!map[k]) {
|
||||
map[k] = { key: 'sum-' + mk + '-' + k, customerName: k, statMonth: mk, quantityKg: 0, amount: 0 };
|
||||
}
|
||||
map[k].quantityKg += isNaN(Number(r.quantityKg)) ? 0 : Number(r.quantityKg);
|
||||
map[k].amount += isNaN(Number(r.amount)) ? 0 : Number(r.amount);
|
||||
});
|
||||
return Object.keys(map).map(function (name) { return map[name]; });
|
||||
}, [allDetailRows, appliedTotal.month, appliedTotal.customerName]);
|
||||
|
||||
var totalTabSums = useMemo(function () {
|
||||
var q = 0;
|
||||
var a = 0;
|
||||
totalByCustomerMonthRows.forEach(function (r) {
|
||||
q += r.quantityKg || 0;
|
||||
a += r.amount || 0;
|
||||
});
|
||||
return { quantityKg: q, amount: a };
|
||||
}, [totalByCustomerMonthRows]);
|
||||
|
||||
var detailTitle = useMemo(function () {
|
||||
return '租赁客户氢费明细表';
|
||||
}, []);
|
||||
|
||||
var totalTitle = useMemo(function () {
|
||||
if (appliedTotal.month && appliedTotal.month.format) {
|
||||
return appliedTotal.month.format('YYYY年M月') + '租赁客户氢费总计';
|
||||
}
|
||||
return '租赁客户氢费总计';
|
||||
}, [appliedTotal.month]);
|
||||
|
||||
var handleQueryDetail = useCallback(function () {
|
||||
setAppliedDetail(Object.assign({}, draftDetail));
|
||||
}, [draftDetail]);
|
||||
|
||||
var handleResetDetail = useCallback(function () {
|
||||
var def = {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
setDraftDetail(def);
|
||||
setAppliedDetail(def);
|
||||
}, [rangeDefault]);
|
||||
|
||||
var handleQueryTotal = useCallback(function () {
|
||||
setAppliedTotal(Object.assign({}, draftTotal));
|
||||
}, [draftTotal]);
|
||||
|
||||
var handleResetTotal = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), customerName: undefined };
|
||||
setDraftTotal(def);
|
||||
setAppliedTotal(def);
|
||||
}, []);
|
||||
|
||||
var handleExportDetail = useCallback(function () {
|
||||
var rows = filteredDetailRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'客户名称',
|
||||
'日期',
|
||||
'车牌号',
|
||||
'加氢数量',
|
||||
'加氢地点',
|
||||
'进价',
|
||||
'成本金额',
|
||||
'单价',
|
||||
'金额'
|
||||
];
|
||||
var line = function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
r.customerName,
|
||||
fmtDateDash(r.bizDate),
|
||||
r.plateNo,
|
||||
fmtCell(r.quantityKg),
|
||||
r.location,
|
||||
fmtCell(r.purchasePrice),
|
||||
fmtCell(r.costAmount),
|
||||
fmtCell(r.unitPrice),
|
||||
fmtCell(r.amount)
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(line));
|
||||
body.push([
|
||||
'汇总',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
fmtSum(detailSums.quantityKg),
|
||||
'',
|
||||
'',
|
||||
fmtSum(detailSums.costAmount),
|
||||
'',
|
||||
fmtSum(detailSums.amount)
|
||||
]);
|
||||
downloadCsv('租赁客户氢费明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredDetailRows, detailSums]);
|
||||
|
||||
var handleExportTotal = useCallback(function () {
|
||||
var rows = totalByCustomerMonthRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请选择统计月份并查询');
|
||||
return;
|
||||
}
|
||||
var headers = ['客户名称', '统计月份', '加氢数量合计', '金额合计'];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [r.customerName, r.statMonth, fmtSum(r.quantityKg), fmtSum(r.amount)];
|
||||
})
|
||||
);
|
||||
body.push(['总计', '', fmtSum(totalTabSums.quantityKg), fmtSum(totalTabSums.amount)]);
|
||||
downloadCsv('租赁客户氢费总计_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [totalByCustomerMonthRows, totalTabSums]);
|
||||
|
||||
function renderBoldMoney(v) {
|
||||
var s = fmtCell(v);
|
||||
return React.createElement('span', { style: boldNumStyle }, s);
|
||||
}
|
||||
|
||||
var columnsDetail = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 100, align: 'left' },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true, align: 'left' },
|
||||
{ title: '日期', dataIndex: 'bizDate', key: 'bizDate', width: 118, align: 'left', render: function (_v, r) { return fmtDateDash(r.bizDate); } },
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120, align: 'left' },
|
||||
{ title: '加氢数量', dataIndex: 'quantityKg', key: 'quantityKg', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '加氢地点', dataIndex: 'location', key: 'location', width: 260, ellipsis: true, align: 'left' },
|
||||
{ title: '进价', dataIndex: 'purchasePrice', key: 'purchasePrice', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '成本金额', dataIndex: 'costAmount', key: 'costAmount', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '单价', dataIndex: 'unitPrice', key: 'unitPrice', width: 90, align: 'right', render: function (v) { return renderBoldMoney(v); } },
|
||||
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 110, align: 'right', render: function (v) { return renderBoldMoney(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var columnsTotal = useMemo(function () {
|
||||
return [
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 240, ellipsis: true, align: 'left' },
|
||||
{ title: '统计月份', dataIndex: 'statMonth', key: 'statMonth', width: 110, align: 'center' },
|
||||
{ title: '加氢数量合计', dataIndex: 'quantityKg', key: 'quantityKg', width: 130, align: 'right', render: function (v) { return fmtSum(v); } },
|
||||
{ title: '金额合计', dataIndex: 'amount', key: 'amount', width: 130, align: 'right', render: function (v) { return React.createElement('span', { style: boldNumStyle }, fmtSum(v)); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var detailTableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 4 }, '汇总'),
|
||||
React.createElement(SummaryCell, { index: 4, align: 'right' }, fmtSum(detailSums.quantityKg)),
|
||||
React.createElement(SummaryCell, { index: 5, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 6, align: 'right' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 7, align: 'right' }, fmtSum(detailSums.costAmount)),
|
||||
React.createElement(SummaryCell, { index: 8, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 9, align: 'right' },
|
||||
React.createElement('span', { style: boldNumStyle }, fmtSum(detailSums.amount))
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [detailSums]);
|
||||
|
||||
var totalTableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 2 }, '总计'),
|
||||
React.createElement(SummaryCell, { index: 2, align: 'right' }, fmtSum(totalTabSums.quantityKg)),
|
||||
React.createElement(SummaryCell, { index: 3, align: 'right' },
|
||||
React.createElement('span', { style: boldNumStyle }, fmtSum(totalTabSums.amount))
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [totalTabSums]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '租赁客户氢费明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
React.createElement(Row, { gutter: [16, 16], align: 'bottom' },
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 10, lg: 8 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '日期范围'),
|
||||
React.createElement(RangePicker, {
|
||||
style: filterControlStyle,
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
format: 'YYYY-MM-DD',
|
||||
value: draftDetail.dateRange,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { dateRange: v }); }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 7, lg: 6 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '业务员'),
|
||||
React.createElement(Select, {
|
||||
placeholder: '请选择业务员',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draftDetail.salesperson,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
),
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 7, lg: 6 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '客户名称'),
|
||||
React.createElement(Select, {
|
||||
placeholder: '请选择客户名称',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draftDetail.customerName,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleResetDetail }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQueryDetail }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: listToolbarStyle },
|
||||
React.createElement(Button, { onClick: handleExportDetail }, '导出')
|
||||
),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, detailTitle),
|
||||
React.createElement(React.Fragment, null,
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'contract-list-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsDetail,
|
||||
dataSource: filteredDetailRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: detailTableSummary,
|
||||
scroll: { x: 1320 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
label: '租赁客户氢费总计',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draftTotal.month,
|
||||
onChange: function (v) { setDraftTotal(function (p) { return Object.assign({}, p, { month: 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, {
|
||||
placeholder: '全部客户',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draftTotal.customerName,
|
||||
onChange: function (v) { setDraftTotal(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleResetTotal }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQueryTotal }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: listToolbarStyle },
|
||||
React.createElement(Button, { onClick: handleExportTotal }, '导出')
|
||||
),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, totalTitle),
|
||||
React.createElement(React.Fragment, null,
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'contract-list-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsTotal,
|
||||
dataSource: totalByCustomerMonthRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: totalTableSummary,
|
||||
scroll: { x: 720 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '租赁客户氢费台账' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'lease-h2-ledger-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
801
web端/数据分析/租赁车辆收入明细.jsx
Normal file
801
web端/数据分析/租赁车辆收入明细.jsx
Normal file
@@ -0,0 +1,801 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 租赁车辆收入明细(租赁业务明细 Tab + 租赁业务盈亏月度汇总 Tab,汇总交互参考「业务部业绩明细」)
|
||||
|
||||
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 message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
function fmtTextSlash(s) {
|
||||
if (s === null || s === undefined || String(s).trim() === '') return '/';
|
||||
return String(s);
|
||||
}
|
||||
|
||||
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 getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 tableSingleLineStyle =
|
||||
'.lease-income-detail-table .ant-table-thead th,.lease-income-detail-table .ant-table-tbody td,.lease-income-detail-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tableMonthlyLineStyle =
|
||||
'.lease-pl-monthly-table .ant-table-thead th,.lease-pl-monthly-table .ant-table-tbody td,.lease-pl-monthly-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.lease-income-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var outstandingPositiveStyle = { color: '#cf1322' };
|
||||
|
||||
var numericSumKeys = [
|
||||
'deposit',
|
||||
'receivable',
|
||||
'received',
|
||||
'outstanding',
|
||||
'naturalMonthIncome',
|
||||
'hydrogenPrepay',
|
||||
'baseCost',
|
||||
'brokerage',
|
||||
'hydrogenFee',
|
||||
'totalCost'
|
||||
];
|
||||
|
||||
/** 月度盈亏汇总表:各月金额列 + 盈亏(自然月收入 − 总成本,可负) */
|
||||
var monthlyPLKeys = numericSumKeys.concat(['profit']);
|
||||
|
||||
function sumMonthlyRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
function finalizeLeaseMonthlyRow(r) {
|
||||
var inc = r.naturalMonthIncome;
|
||||
var tc = r.totalCost;
|
||||
var profit = null;
|
||||
if (inc != null && inc !== '' && tc != null && tc !== '') {
|
||||
var a = Number(inc);
|
||||
var b = Number(tc);
|
||||
if (!isNaN(a) && !isNaN(b)) profit = a - b;
|
||||
}
|
||||
return Object.assign({}, r, { profit: profit });
|
||||
}
|
||||
|
||||
function sumRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var deptOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '业务二部', label: '业务二部' },
|
||||
{ value: '业务一部', label: '业务一部' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var rawRowsTemplate = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: 'lv-1',
|
||||
statMonth: sm,
|
||||
plateNo: '沪A52898F',
|
||||
vehicleType: '18T',
|
||||
salesperson: '刘念忠',
|
||||
nature: '纯租赁',
|
||||
systemModel: '飞驰18T',
|
||||
customerName: '上海虹钦物流有限公司',
|
||||
contractDate: '2025.12.1-2026.11.30',
|
||||
pickupDate: '2025-12-08',
|
||||
deposit: 20000,
|
||||
receivable: 18500,
|
||||
received: 18500,
|
||||
outstanding: 0,
|
||||
naturalMonthIncome: 18500,
|
||||
paymentDate: '2026-02-05',
|
||||
hydrogenPrepay: 5000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-02-06',
|
||||
baseCost: 12000,
|
||||
brokerage: 800,
|
||||
hydrogenFee: 3200,
|
||||
totalCost: 16000
|
||||
},
|
||||
{
|
||||
key: 'lv-2',
|
||||
statMonth: sm,
|
||||
plateNo: '粤AGF4535',
|
||||
vehicleType: '4.5T',
|
||||
salesperson: '冉建华',
|
||||
nature: '试用车',
|
||||
systemModel: '现代4.5T',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
contractDate: '2026.1.1-2026.12.31',
|
||||
pickupDate: '',
|
||||
deposit: 4500,
|
||||
receivable: 4200,
|
||||
received: 3000,
|
||||
outstanding: 1200,
|
||||
naturalMonthIncome: 4200,
|
||||
paymentDate: '',
|
||||
hydrogenPrepay: 0,
|
||||
paymentMethod: '季度指付',
|
||||
invoiceApplyDate: '',
|
||||
baseCost: 2800,
|
||||
brokerage: 0,
|
||||
hydrogenFee: 900,
|
||||
totalCost: 3700
|
||||
},
|
||||
{
|
||||
key: 'lv-3',
|
||||
statMonth: sm,
|
||||
plateNo: '浙A88888F',
|
||||
vehicleType: '49T',
|
||||
salesperson: '刘念忠',
|
||||
nature: '纯租赁',
|
||||
systemModel: '苏龙18T',
|
||||
customerName: '宁波港联氢运物流有限公司',
|
||||
contractDate: '2025.11.15-2026.11.14',
|
||||
pickupDate: '2025-11-20',
|
||||
deposit: 50000,
|
||||
receivable: 26800,
|
||||
received: 20000,
|
||||
outstanding: 6800,
|
||||
naturalMonthIncome: 26800,
|
||||
paymentDate: '2026-01-28',
|
||||
hydrogenPrepay: 12000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-01-30',
|
||||
baseCost: 18500,
|
||||
brokerage: 1500,
|
||||
hydrogenFee: 6200,
|
||||
totalCost: 26200
|
||||
},
|
||||
{
|
||||
key: 'lv-4',
|
||||
statMonth: sm,
|
||||
plateNo: '苏E66666F',
|
||||
vehicleType: '4.5T',
|
||||
salesperson: '冉建华',
|
||||
nature: '纯租赁',
|
||||
systemModel: '现代4.5T',
|
||||
customerName: '嘉兴南湖氢能示范运营',
|
||||
contractDate: '2026.2.1-2027.1.31',
|
||||
pickupDate: '2026-02-10',
|
||||
deposit: 8000,
|
||||
receivable: 3800,
|
||||
received: 3800,
|
||||
outstanding: 0,
|
||||
naturalMonthIncome: 3800,
|
||||
paymentDate: '2026-02-12',
|
||||
hydrogenPrepay: 2000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-02-15',
|
||||
baseCost: 2100,
|
||||
brokerage: 200,
|
||||
hydrogenFee: 650,
|
||||
totalCost: 2950
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
var customerOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.customerName) set[r.customerName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
/** 原型:按年 1–12 月租赁业务金额汇总(联调后由接口按年返回) */
|
||||
var monthlyLeasePL2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, deposit: 120000, receivable: 98500, received: 92000, outstanding: 6500, naturalMonthIncome: 92000, hydrogenPrepay: 18000, baseCost: 62000, brokerage: 4200, hydrogenFee: 15000, totalCost: 81200 },
|
||||
{ month: 2, deposit: 45000, receivable: 53200, received: 48000, outstanding: 5200, naturalMonthIncome: 50500, hydrogenPrepay: 8000, baseCost: 31000, brokerage: 800, hydrogenFee: 9800, totalCost: 41600 },
|
||||
{ month: 3, deposit: null, receivable: 66800, received: 66800, outstanding: 0, naturalMonthIncome: 66800, hydrogenPrepay: null, baseCost: 40200, brokerage: 1500, hydrogenFee: 12100, totalCost: 53800 },
|
||||
{ month: 4, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 5, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 6, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 7, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 8, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 9, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 10, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 11, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 12, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeLeaseMonthlyRow(Object.assign({ key: 'lease-pl-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e1) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (rawRowsTemplate || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
if (applied.customerName && r.customerName !== applied.customerName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rawRowsTemplate, applied.month, applied.salesperson, applied.customerName]);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
return sumRows(filteredRows, numericSumKeys);
|
||||
}, [filteredRows]);
|
||||
|
||||
var reportTitle = useMemo(function () {
|
||||
var dept = applied.dept || '业务二部';
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
return y + '年浙江羚牛氢能租赁车辆收入明细表(' + dept + ')';
|
||||
}
|
||||
return '浙江羚牛氢能租赁车辆收入明细表(' + dept + ')';
|
||||
}, [applied.month, applied.dept]);
|
||||
|
||||
var plSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return '租赁业务' + summaryYearApplied.format('YYYY') + '年盈亏月度汇总';
|
||||
}
|
||||
return '租赁业务盈亏月度汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var plMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyLeasePL2026); }, [monthlyDataSource, monthlyLeasePL2026]);
|
||||
var plMonthlySums = useMemo(function () { return sumMonthlyRows(plMonthlyRows, monthlyPLKeys); }, [plMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExport = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'车牌号码',
|
||||
'车型',
|
||||
'业务员',
|
||||
'性质',
|
||||
'系统车型',
|
||||
'客户名称',
|
||||
'合同日期',
|
||||
'提车日期',
|
||||
'押金',
|
||||
'应收',
|
||||
'实收',
|
||||
'未收',
|
||||
'自然月收入',
|
||||
'付款日期',
|
||||
'氢费预充值',
|
||||
'付款方式',
|
||||
'申请开票日期',
|
||||
'成本',
|
||||
'居间费',
|
||||
'氢费',
|
||||
'总成本'
|
||||
];
|
||||
var line = function (r) {
|
||||
return [
|
||||
r.plateNo,
|
||||
r.vehicleType,
|
||||
r.salesperson,
|
||||
r.nature,
|
||||
r.systemModel,
|
||||
r.customerName,
|
||||
fmtTextSlash(r.contractDate),
|
||||
fmtTextSlash(r.pickupDate),
|
||||
fmtCell(r.deposit),
|
||||
fmtCell(r.receivable),
|
||||
fmtCell(r.received),
|
||||
fmtCell(r.outstanding),
|
||||
fmtCell(r.naturalMonthIncome),
|
||||
fmtTextSlash(r.paymentDate),
|
||||
fmtCell(r.hydrogenPrepay),
|
||||
fmtTextSlash(r.paymentMethod),
|
||||
fmtTextSlash(r.invoiceApplyDate),
|
||||
fmtCell(r.baseCost),
|
||||
fmtCell(r.brokerage),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.totalCost)
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(line));
|
||||
body.push([
|
||||
'汇总', '', '', '', '', '', '', '',
|
||||
fmtSum(columnSums.deposit),
|
||||
fmtSum(columnSums.receivable),
|
||||
fmtSum(columnSums.received),
|
||||
fmtSum(columnSums.outstanding),
|
||||
fmtSum(columnSums.naturalMonthIncome),
|
||||
'',
|
||||
fmtSum(columnSums.hydrogenPrepay),
|
||||
'', '',
|
||||
fmtSum(columnSums.baseCost),
|
||||
fmtSum(columnSums.brokerage),
|
||||
fmtSum(columnSums.hydrogenFee),
|
||||
fmtSum(columnSums.totalCost)
|
||||
]);
|
||||
downloadCsv('租赁车辆收入明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, columnSums]);
|
||||
|
||||
var handleExportPLMonthly = useCallback(function () {
|
||||
var rows = plMonthlyRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'月份',
|
||||
'押金',
|
||||
'应收',
|
||||
'实收',
|
||||
'未收',
|
||||
'自然月收入',
|
||||
'氢费预充值',
|
||||
'成本',
|
||||
'居间费',
|
||||
'氢费',
|
||||
'总成本',
|
||||
'盈亏'
|
||||
];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.deposit),
|
||||
fmtCell(r.receivable),
|
||||
fmtCell(r.received),
|
||||
fmtCell(r.outstanding),
|
||||
fmtCell(r.naturalMonthIncome),
|
||||
fmtCell(r.hydrogenPrepay),
|
||||
fmtCell(r.baseCost),
|
||||
fmtCell(r.brokerage),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.totalCost),
|
||||
fmtCell(r.profit)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(monthlyPLKeys.map(function (k) { return fmtSum(plMonthlySums[k] || 0); })));
|
||||
downloadCsv('租赁业务盈亏月度汇总_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, [plMonthlyRows, plMonthlySums]);
|
||||
|
||||
function renderOutstanding(v) {
|
||||
var s = fmtCell(v);
|
||||
if (v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) > 0) {
|
||||
return React.createElement('span', { style: outstandingPositiveStyle }, s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '车牌号码', dataIndex: 'plateNo', key: 'plateNo', width: 110, fixed: 'left', align: 'center' },
|
||||
{ title: '车型', dataIndex: 'vehicleType', key: 'vehicleType', width: 72, align: 'center' },
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88, align: 'center' },
|
||||
{ title: '性质', dataIndex: 'nature', key: 'nature', width: 88, align: 'center' },
|
||||
{ title: '系统车型', dataIndex: 'systemModel', key: 'systemModel', width: 100, align: 'center' },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true },
|
||||
{ title: '合同日期', dataIndex: 'contractDate', key: 'contractDate', width: 168, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '提车日期', dataIndex: 'pickupDate', key: 'pickupDate', width: 110, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '押金', dataIndex: 'deposit', key: 'deposit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '应收', dataIndex: 'receivable', key: 'receivable', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '实收', dataIndex: 'received', key: 'received', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '未收', dataIndex: 'outstanding', key: 'outstanding', width: 100, align: 'right', render: function (v) { return renderOutstanding(v); } },
|
||||
{ title: '自然月收入', dataIndex: 'naturalMonthIncome', key: 'naturalMonthIncome', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '付款日期', dataIndex: 'paymentDate', key: 'paymentDate', width: 110, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '氢费预充值', dataIndex: 'hydrogenPrepay', key: 'hydrogenPrepay', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '付款方式', dataIndex: 'paymentMethod', key: 'paymentMethod', width: 100, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '申请开票日期', dataIndex: 'invoiceApplyDate', key: 'invoiceApplyDate', width: 120, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '成本', dataIndex: 'baseCost', key: 'baseCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '居间费', dataIndex: 'brokerage', key: 'brokerage', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var columnsPLMonthly = useMemo(function () {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '押金', dataIndex: 'deposit', key: 'deposit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '应收', dataIndex: 'receivable', key: 'receivable', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '实收', dataIndex: 'received', key: 'received', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '未收', dataIndex: 'outstanding', key: 'outstanding', width: 100, align: 'right', render: function (v) { return renderOutstanding(v); } },
|
||||
{ title: '自然月收入', dataIndex: 'naturalMonthIncome', key: 'naturalMonthIncome', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费预充值', dataIndex: 'hydrogenPrepay', key: 'hydrogenPrepay', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '成本', dataIndex: 'baseCost', key: 'baseCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '居间费', dataIndex: 'brokerage', key: 'brokerage', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '盈亏', dataIndex: 'profit', key: 'profit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
function renderPLMonthlySummary(sums) {
|
||||
return function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyPLKeys.map(function (k, idx) {
|
||||
var cell = fmtSum(sums[k] || 0);
|
||||
if (k === 'outstanding' && Number(sums.outstanding) > 0) {
|
||||
cell = React.createElement('span', { style: outstandingPositiveStyle }, fmtSum(sums.outstanding || 0));
|
||||
}
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'right' },
|
||||
cell
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(v); }
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 8 }, '汇总'),
|
||||
React.createElement(SummaryCell, { index: 8, align: 'right' }, fmtSum(columnSums.deposit)),
|
||||
React.createElement(SummaryCell, { index: 9, align: 'right' }, fmtSum(columnSums.receivable)),
|
||||
React.createElement(SummaryCell, { index: 10, align: 'right' }, fmtSum(columnSums.received)),
|
||||
React.createElement(SummaryCell, { index: 11, align: 'right' },
|
||||
Number(columnSums.outstanding) > 0
|
||||
? React.createElement('span', { style: outstandingPositiveStyle }, fmtSum(columnSums.outstanding))
|
||||
: fmtSum(columnSums.outstanding)
|
||||
),
|
||||
React.createElement(SummaryCell, { index: 12, align: 'right' }, fmtSum(columnSums.naturalMonthIncome)),
|
||||
React.createElement(SummaryCell, { index: 13, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 14, align: 'right' }, fmtSum(columnSums.hydrogenPrepay)),
|
||||
React.createElement(SummaryCell, { index: 15, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 16, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 17, align: 'right' }, fmtSum(columnSums.baseCost)),
|
||||
React.createElement(SummaryCell, { index: 18, align: 'right' }, fmtSum(columnSums.brokerage)),
|
||||
React.createElement(SummaryCell, { index: 19, align: 'right' }, fmtSum(columnSums.hydrogenFee)),
|
||||
React.createElement(SummaryCell, { index: 20, align: 'right' }, fmtSum(columnSums.totalCost))
|
||||
)
|
||||
);
|
||||
}, [columnSums]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '租赁业务明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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, {
|
||||
placeholder: '请选择业务部门',
|
||||
style: filterControlStyle,
|
||||
options: deptOptions,
|
||||
value: draft.dept,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { dept: 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, {
|
||||
placeholder: '请选择业务员',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
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(Select, {
|
||||
placeholder: '请选择客户名称',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draft.customerName,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
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(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExport }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, reportTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'lease-income-detail-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 2680 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'monthlyPL',
|
||||
label: '租赁业务盈亏月度汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPLMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, plSumTableTitle),
|
||||
React.createElement('style', null, tableMonthlyLineStyle),
|
||||
React.createElement('div', { className: 'lease-pl-monthly-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPLMonthly,
|
||||
dataSource: plMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderPLMonthlySummary(plMonthlySums),
|
||||
scroll: { x: 1320 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '租赁车辆收入明细' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'lease-income-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -53,6 +53,7 @@ const Component = function() {
|
||||
deliveryProvince: '浙江省',
|
||||
deliveryCity: '嘉兴市',
|
||||
deliveryLocation: '嘉兴市南湖区科技大道1号',
|
||||
contractApprovalType: '标准合同审批',
|
||||
remarks: '',
|
||||
authorizedList: [{ name: '张三', phone: '13800138001', idCard: '330102199001011234' }],
|
||||
rentalOrders: [
|
||||
@@ -128,6 +129,9 @@ const Component = function() {
|
||||
var bs11 = React.useState(prevContractSample.deliveryLocation);
|
||||
var deliveryLocation = bs11[0];
|
||||
var setDeliveryLocation = bs11[1];
|
||||
var bs11a = React.useState(prevContractSample.contractApprovalType);
|
||||
var contractApprovalType = bs11a[0];
|
||||
var setContractApprovalType = bs11a[1];
|
||||
var bs12 = React.useState('转正式合同自:旧合同编码JXZL20260216YW101235A');
|
||||
var remarks = bs12[0];
|
||||
var setRemarks = bs12[1];
|
||||
@@ -321,6 +325,7 @@ const Component = function() {
|
||||
if (!signingCompany) errs.signingCompany = '请选择签约公司';
|
||||
if (!deliveryProvince || !deliveryCity) errs.deliveryRegion = '请选择交车区域';
|
||||
if (!deliveryLocation || !deliveryLocation.trim()) errs.deliveryLocation = '请输入交车地点';
|
||||
if (!contractApprovalType) errs.contractApprovalType = '请选择合同审批类型';
|
||||
var authInvalid = authorizedList.some(function(a) { return !a.name || !a.name.trim() || !a.phone || !a.phone.trim() || !a.idCard || !a.idCard.trim(); });
|
||||
if (authInvalid) errs.authorizedList = '请完整填写被授权人姓名、联系电话、身份证';
|
||||
var rentalInvalid = rentalOrders.some(function(r) { return !r.brand || !r.model || !(r.monthRent && String(r.monthRent).trim()) || !(r.deposit && String(r.deposit).trim()); });
|
||||
@@ -347,7 +352,7 @@ const Component = function() {
|
||||
if (!billingMethod) errs.billingMethod = '请选择账单计算方式';
|
||||
setFormErrors(errs);
|
||||
if (Object.keys(errs).length > 0) {
|
||||
var firstId = errs.customer || errs.businessDept || errs.businessOwner ? 'card-customer' : (errs.projectName || errs.effectiveDate || errs.paymentMethod || errs.endDate || errs.paymentPeriod || errs.signingCompany || errs.deliveryRegion || errs.deliveryLocation || errs.contractOriginal) ? 'card-contract' : errs.authorizedList ? 'card-authorized' : (errs.rentalOrders || errs.serviceItems || errs.hydrogenBearer || errs.hydrogenPaymentMethod || errs.hydrogenPrepay || errs.returnHydrogenPrice) ? 'card-rental' : errs.feeTemplate ? 'card-fee' : 'card-billing';
|
||||
var firstId = errs.customer || errs.businessDept || errs.businessOwner ? 'card-customer' : (errs.projectName || errs.effectiveDate || errs.paymentMethod || errs.endDate || errs.paymentPeriod || errs.signingCompany || errs.deliveryRegion || errs.deliveryLocation || errs.contractApprovalType || errs.contractOriginal) ? 'card-contract' : errs.authorizedList ? 'card-authorized' : (errs.rentalOrders || errs.serviceItems || errs.hydrogenBearer || errs.hydrogenPaymentMethod || errs.hydrogenPrepay || errs.returnHydrogenPrice) ? 'card-rental' : errs.feeTemplate ? 'card-fee' : 'card-billing';
|
||||
setCc1(false); setCc2(false); setCc3(false); setCc4(false); setCc5(false); setCc6(false);
|
||||
setTimeout(function() { var el = document.getElementById(firstId); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 100);
|
||||
return false;
|
||||
@@ -579,7 +584,8 @@ const Component = function() {
|
||||
React.createElement(FormItem, { label: '交车区域', required: true, error: formErrors.deliveryRegion }, React.createElement('div', { id: 'delivery-region-wrap', style: { position: 'relative' } }, React.createElement(Input, { style: Object.assign({}, formErrors.deliveryRegion ? { borderColor: '#ff4d4f' } : {}, { cursor: 'pointer', caretColor: 'transparent', width: '100%' }), placeholder: '请选择省-市', value: deliveryRegionDisplay, readOnly: true, onClick: function() { setDeliveryRegionOpen(!deliveryRegionOpen); } }), deliveryRegionOpen ? React.createElement('div', { style: styles.regionCascader, onMouseDown: function() { deliveryRegionClickInsideRef.current = true; } }, React.createElement('div', { style: styles.regionCascaderCol }, regionList.map(function(r, i) { var isActive = r.province === deliveryProvince; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); setEdited(true); setDeliveryProvince(r.province); setDeliveryCity(''); } }, r.province); })), React.createElement('div', { style: styles.regionCascaderColLast }, deliveryProvince ? (regionList.find(function(x) { return x.province === deliveryProvince; }) || { cities: [] }).cities.map(function(c, i) { var isActive = c === deliveryCity; return React.createElement('div', { key: i, style: Object.assign({}, styles.regionCascaderItem, isActive ? { backgroundColor: '#e6f7ff', color: '#1890ff' } : {}), onMouseEnter: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = '#f5f5f5'; }, onMouseLeave: function(e) { if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'; }, onMouseDown: function(e) { e.preventDefault(); selectDeliveryRegion(deliveryProvince, c); } }, c); }) : React.createElement('div', { style: { padding: 16, color: '#999', fontSize: 13 } }, '请先选择省'))) : null))
|
||||
);
|
||||
var contractFormRow4 = React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '交车地点', required: true, error: formErrors.deliveryLocation }, React.createElement(Input, { placeholder: '请输入交车地点', value: deliveryLocation, onChange: function(e) { setEdited(true); setDeliveryLocation(e.target.value); }, status: formErrors.deliveryLocation ? 'error' : undefined, style: { width: '100%' } }))
|
||||
React.createElement(FormItem, { label: '交车地点', required: true, error: formErrors.deliveryLocation }, React.createElement(Input, { placeholder: '请输入交车地点', value: deliveryLocation, onChange: function(e) { setEdited(true); setDeliveryLocation(e.target.value); }, status: formErrors.deliveryLocation ? 'error' : undefined, style: { width: '100%' } })),
|
||||
React.createElement(FormItem, { label: '合同审批类型', required: true, error: formErrors.contractApprovalType }, React.createElement(Select, { placeholder: '请选择合同审批类型', style: { width: '100%' }, value: contractApprovalType || undefined, onChange: function(v) { setEdited(true); setContractApprovalType(v || ''); }, status: formErrors.contractApprovalType ? 'error' : undefined }, React.createElement(Option, { value: '标准合同审批' }, '标准合同审批'), React.createElement(Option, { value: '非标准合同审批' }, '非标准合同审批')))
|
||||
);
|
||||
var contractFormRow5 = React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '合同原件', required: true, error: formErrors.contractOriginal },
|
||||
@@ -759,8 +765,9 @@ const Component = function() {
|
||||
3.8.签约公司:从原合同自动反写,必选项,选择器,从组织机构表中获取所有根组织机构(如嘉兴羚牛、上海羚牛、广东羚牛等),默认显示新增用户当前机构,可手动修改,后期需要考虑从签约公司维度统计合同相关数据;
|
||||
3.9.交车区域:从原合同自动反写,必选项,地区选择器,支持省-市2级,选择区域后,该任务生成交车任务时会自动推送至该区域负责运维人员;
|
||||
3.10.交车地点:从原合同自动反写,必填项,输入框,支持自定义输入交车地点;
|
||||
3.11.合同原件:从原合同自动反写,必填项,按钮,按钮文字为:上传附件,支持多个附件上传(doc/docx/pdf格式);
|
||||
3.12.备注:从原合同自动反写,如果该合同为转正式合同,则自动在已填备注信息上方额外添加:转正式合同自:旧合同合同编码xxx,文本域,支持自定义输入备注信息;
|
||||
3.11.合同审批类型:从原合同自动反写,必填项,选择器,分为「标准合同审批」「非标准合同审批」;
|
||||
3.12.合同原件:从原合同自动反写,必填项,按钮,按钮文字为:上传附件,支持多个附件上传(doc/docx/pdf格式);
|
||||
3.13.备注:从原合同自动反写,如果该合同为转正式合同,则自动在已填备注信息上方额外添加:转正式合同自:旧合同合同编码xxx,文本域,支持自定义输入备注信息;
|
||||
|
||||
4.被授权人信息卡片:
|
||||
#用于定义租赁合同相关被授权人相关信息,被授权人在交车单完成时,需要选择被授权人,并通过被授权人手机短信,在E签宝进行签字确认;
|
||||
|
||||
@@ -37,6 +37,7 @@ const Component = function() {
|
||||
signingCompany: '嘉兴羚牛',
|
||||
deliveryRegion: '浙江省 / 嘉兴市',
|
||||
deliveryLocation: '嘉兴市南湖区科技大道1号',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractOriginalName: '租赁合同-嘉兴某某物流.pdf',
|
||||
remarks: '首年优惠',
|
||||
authorizedList: [
|
||||
@@ -221,6 +222,7 @@ const Component = function() {
|
||||
React.createElement(FormItem, { label: '签约公司' }, React.createElement(Input, { value: detail.signingCompany, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '交车区域' }, React.createElement(Input, { value: detail.deliveryRegion, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '交车地点' }, React.createElement(Input, { value: detail.deliveryLocation, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '合同审批类型' }, React.createElement(Input, { value: detail.contractApprovalType || '—', disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '合同原件' }, React.createElement(Input, { value: detail.contractOriginalName, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '备注', fullWidth: true }, React.createElement(Input.TextArea, { value: detail.remarks, disabled: true, style: { width: '100%', minHeight: 80, backgroundColor: '#f5f5f5' } }))
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ const Component = function() {
|
||||
var Popover = antd.Popover;
|
||||
var Dropdown = antd.Dropdown;
|
||||
var Modal = antd.Modal;
|
||||
var Upload = antd.Upload;
|
||||
var message = antd.message;
|
||||
var App = antd.App;
|
||||
|
||||
@@ -33,6 +34,7 @@ const Component = function() {
|
||||
var _businessDept = useState([]);
|
||||
var _businessOwner = useState([]);
|
||||
var _contractType = useState(['全部']);
|
||||
var _contractApprovalType = useState(['全部']);
|
||||
var _creator = useState([]);
|
||||
var _endDateRange = useState(null);
|
||||
|
||||
@@ -46,6 +48,7 @@ const Component = function() {
|
||||
businessDept: [],
|
||||
businessOwner: [],
|
||||
contractType: ['全部'],
|
||||
contractApprovalType: ['全部'],
|
||||
creator: [],
|
||||
endDateRange: null
|
||||
});
|
||||
@@ -65,6 +68,14 @@ const Component = function() {
|
||||
var _terminateModalVisible = useState(false);
|
||||
var _terminateModalRecord = useState(null);
|
||||
var _requirementModalVisible = useState(false);
|
||||
var _stampModalVisible = useState(false);
|
||||
var _stampModalRecord = useState(null);
|
||||
var _stampFileList = useState([]);
|
||||
// 上传盖章合同完成后,按合同 id 记录已上传(原型:与列表 legalStampedContractUploaded 合并判断)
|
||||
var _stampedUploadedOverride = useState({});
|
||||
|
||||
// 联调时:当前登录用户所属部门为法务部时为 true(原型默认 true 便于演示「上传盖章合同」)
|
||||
var isLegalDeptUser = true;
|
||||
|
||||
// 模拟选项(与新增租赁合同保持一致)
|
||||
var contractCodeOptions = [
|
||||
@@ -118,6 +129,11 @@ const Component = function() {
|
||||
{ value: '正式合同', label: '正式合同' },
|
||||
{ value: '试用合同', label: '试用合同' }
|
||||
];
|
||||
var contractApprovalTypeOptions = [
|
||||
{ value: '全部', label: '全部' },
|
||||
{ value: '标准合同审批', label: '标准合同审批' },
|
||||
{ value: '非标准合同审批', label: '非标准合同审批' }
|
||||
];
|
||||
var deptOptions = [
|
||||
{ value: '业务1部', label: '业务1部' },
|
||||
{ value: '业务2部', label: '业务2部' },
|
||||
@@ -151,6 +167,7 @@ const Component = function() {
|
||||
businessDept: '业务1部',
|
||||
businessOwner: '张经理',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2026-02-16',
|
||||
contactName: '张三',
|
||||
contactPhone: '13800138001',
|
||||
@@ -158,7 +175,8 @@ const Component = function() {
|
||||
createTime: '2025-01-05 10:00',
|
||||
updater: '-',
|
||||
updateTime: '-',
|
||||
remark: '草稿待完善'
|
||||
remark: '草稿待完善',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 2. 未提交 + 草稿(试用合同)
|
||||
{
|
||||
@@ -176,6 +194,7 @@ const Component = function() {
|
||||
businessDept: '业务2部',
|
||||
businessOwner: '李专员',
|
||||
contractType: '试用合同',
|
||||
contractApprovalType: '非标准合同审批',
|
||||
contractEndDate: '2025-08-01',
|
||||
contactName: '李四',
|
||||
contactPhone: '13800138002',
|
||||
@@ -183,7 +202,8 @@ const Component = function() {
|
||||
createTime: '2025-02-10 09:00',
|
||||
updater: '-',
|
||||
updateTime: '-',
|
||||
remark: '试用期 3 个月'
|
||||
remark: '试用期 3 个月',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 3. 待审批 + 已提交审批(初次提交,尚未有任何节点审批)
|
||||
{
|
||||
@@ -201,6 +221,7 @@ const Component = function() {
|
||||
businessDept: '业务3部',
|
||||
businessOwner: '王专员',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2026-06-30',
|
||||
contactName: '王五',
|
||||
contactPhone: '13800138003',
|
||||
@@ -208,7 +229,8 @@ const Component = function() {
|
||||
createTime: '2025-02-12 11:00',
|
||||
updater: '-',
|
||||
updateTime: '-',
|
||||
remark: '-'
|
||||
remark: '-',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 4. 审批中 + 已提交审批(已有节点审批,未走完)
|
||||
{
|
||||
@@ -227,6 +249,7 @@ const Component = function() {
|
||||
businessDept: '业务1部',
|
||||
businessOwner: '张经理',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '非标准合同审批',
|
||||
contractEndDate: '2026-03-01',
|
||||
contactName: '赵六',
|
||||
contactPhone: '13900139001',
|
||||
@@ -234,7 +257,8 @@ const Component = function() {
|
||||
createTime: '2025-02-14 09:00',
|
||||
updater: '李专员',
|
||||
updateTime: '2025-02-15 16:00',
|
||||
remark: '-'
|
||||
remark: '-',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 5. 审批中 + 变更(已通过审批后做了变更并重新提交)
|
||||
{
|
||||
@@ -252,6 +276,7 @@ const Component = function() {
|
||||
businessDept: '业务2部',
|
||||
businessOwner: '李专员',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2026-05-31',
|
||||
contactName: '孙七',
|
||||
contactPhone: '13900139002',
|
||||
@@ -259,7 +284,8 @@ const Component = function() {
|
||||
createTime: '2025-02-18 10:00',
|
||||
updater: '李专员',
|
||||
updateTime: '2025-02-22 14:00',
|
||||
remark: '变更车辆数量'
|
||||
remark: '变更车辆数量',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 6. 审批通过 + 合同进行中(正式合同)
|
||||
{
|
||||
@@ -279,6 +305,7 @@ const Component = function() {
|
||||
businessDept: '业务3部',
|
||||
businessOwner: '王专员',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2026-12-31',
|
||||
contactName: '周八',
|
||||
contactPhone: '13900139003',
|
||||
@@ -286,7 +313,8 @@ const Component = function() {
|
||||
createTime: '2025-01-15 09:00',
|
||||
updater: '王专员',
|
||||
updateTime: '2025-01-25 11:00',
|
||||
remark: '-'
|
||||
remark: '-',
|
||||
legalStampedContractUploaded: false
|
||||
},
|
||||
// 7. 审批通过 + 合同进行中(试用合同,可转正式)
|
||||
{
|
||||
@@ -304,6 +332,7 @@ const Component = function() {
|
||||
businessDept: '业务1部',
|
||||
businessOwner: '张经理',
|
||||
contractType: '试用合同',
|
||||
contractApprovalType: '非标准合同审批',
|
||||
contractEndDate: '2025-05-31',
|
||||
contactName: '吴九',
|
||||
contactPhone: '13900139004',
|
||||
@@ -311,7 +340,8 @@ const Component = function() {
|
||||
createTime: '2025-01-28 10:00',
|
||||
updater: '-',
|
||||
updateTime: '-',
|
||||
remark: '试用 3 个月'
|
||||
remark: '试用 3 个月',
|
||||
legalStampedContractUploaded: false
|
||||
},
|
||||
// 8. 审批驳回 + 已提交审批(可编辑和重新提交)
|
||||
{
|
||||
@@ -330,6 +360,7 @@ const Component = function() {
|
||||
businessDept: '业务2部',
|
||||
businessOwner: '李专员',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2026-08-15',
|
||||
contactName: '郑十',
|
||||
contactPhone: '13900139005',
|
||||
@@ -337,7 +368,8 @@ const Component = function() {
|
||||
createTime: '2025-02-20 14:00',
|
||||
updater: '李专员',
|
||||
updateTime: '2025-02-23 09:00',
|
||||
remark: '驳回原因:费用条款需调整'
|
||||
remark: '驳回原因:费用条款需调整',
|
||||
legalStampedContractUploaded: undefined
|
||||
},
|
||||
// 9. 审批通过 + 到期合同(审批已通过但合同结束日期已过)
|
||||
{
|
||||
@@ -356,6 +388,7 @@ const Component = function() {
|
||||
businessDept: '业务3部',
|
||||
businessOwner: '王专员',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '标准合同审批',
|
||||
contractEndDate: '2024-12-31',
|
||||
contactName: '王五',
|
||||
contactPhone: '13800138003',
|
||||
@@ -363,7 +396,8 @@ const Component = function() {
|
||||
createTime: '2024-02-20 10:00',
|
||||
updater: '王专员',
|
||||
updateTime: '2024-12-20 16:00',
|
||||
remark: '已到期可续签'
|
||||
remark: '已到期可续签',
|
||||
legalStampedContractUploaded: false
|
||||
},
|
||||
// 10. 审批通过 + 已结束(操作列终止合同并完成审核)
|
||||
{
|
||||
@@ -381,6 +415,7 @@ const Component = function() {
|
||||
businessDept: '业务1部',
|
||||
businessOwner: '张经理',
|
||||
contractType: '正式合同',
|
||||
contractApprovalType: '非标准合同审批',
|
||||
contractEndDate: '2025-01-15',
|
||||
contactName: '张三',
|
||||
contactPhone: '13800138001',
|
||||
@@ -388,7 +423,8 @@ const Component = function() {
|
||||
createTime: '2024-05-20 09:00',
|
||||
updater: '张经理',
|
||||
updateTime: '2025-01-10 14:00',
|
||||
remark: '-'
|
||||
remark: '-',
|
||||
legalStampedContractUploaded: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -428,6 +464,10 @@ const Component = function() {
|
||||
if (type && type.length > 0 && type.indexOf('全部') === -1) {
|
||||
list = list.filter(function(r) { return type.indexOf(r.contractType) !== -1; });
|
||||
}
|
||||
var capType = f.contractApprovalType;
|
||||
if (capType && capType.length > 0 && capType.indexOf('全部') === -1) {
|
||||
list = list.filter(function(r) { return capType.indexOf(r.contractApprovalType) !== -1; });
|
||||
}
|
||||
if (f.creator && f.creator.length > 0) {
|
||||
list = list.filter(function(r) { return f.creator.indexOf(r.creator) !== -1; });
|
||||
}
|
||||
@@ -463,6 +503,7 @@ const Component = function() {
|
||||
businessDept: _businessDept[0] ? _businessDept[0].slice() : [],
|
||||
businessOwner: _businessOwner[0] ? _businessOwner[0].slice() : [],
|
||||
contractType: _contractType[0] ? _contractType[0].slice() : ['全部'],
|
||||
contractApprovalType: _contractApprovalType[0] ? _contractApprovalType[0].slice() : ['全部'],
|
||||
creator: _creator[0] ? _creator[0].slice() : [],
|
||||
endDateRange: _endDateRange[0]
|
||||
});
|
||||
@@ -478,6 +519,7 @@ const Component = function() {
|
||||
_businessDept[1]([]);
|
||||
_businessOwner[1]([]);
|
||||
_contractType[1](['全部']);
|
||||
_contractApprovalType[1](['全部']);
|
||||
_creator[1]([]);
|
||||
_endDateRange[1](null);
|
||||
_appliedFilter[1]({
|
||||
@@ -490,6 +532,7 @@ const Component = function() {
|
||||
businessDept: [],
|
||||
businessOwner: [],
|
||||
contractType: ['全部'],
|
||||
contractApprovalType: ['全部'],
|
||||
creator: [],
|
||||
endDateRange: null
|
||||
});
|
||||
@@ -544,6 +587,22 @@ const Component = function() {
|
||||
}
|
||||
_contractType[1](v);
|
||||
}, []);
|
||||
var handleContractApprovalTypeChange = useCallback(function(v) {
|
||||
if (!v || v.length === 0) { _contractApprovalType[1](['全部']); return; }
|
||||
if (v.indexOf('全部') !== -1 && v.length > 1) {
|
||||
var prevA = _contractApprovalType[0] || [];
|
||||
var hadAllOnlyA = prevA.length === 1 && prevA.indexOf('全部') !== -1;
|
||||
if (hadAllOnlyA) {
|
||||
var nextA = [];
|
||||
for (var ia = 0; ia < v.length; ia++) { if (v[ia] !== '全部') nextA.push(v[ia]); }
|
||||
_contractApprovalType[1](nextA);
|
||||
} else {
|
||||
_contractApprovalType[1](['全部']);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_contractApprovalType[1](v);
|
||||
}, []);
|
||||
|
||||
var addAuthorizedRow = useCallback(function() {
|
||||
_authorizedList[1](function(prev) { return prev.concat([{ name: '', phone: '', idCard: '' }]); });
|
||||
@@ -649,6 +708,21 @@ const Component = function() {
|
||||
if (type === '试用合同') {
|
||||
items.push({ key: 'toFormal', label: '转正式合同', onClick: function() { message.info('转正式合同(原型)'); } });
|
||||
}
|
||||
// 审批通过且法务审核环节尚未上传盖章合同附件时,法务部员工可在「更多」中上传(上传完成后入口关闭)
|
||||
if (approval === '审批通过' && isLegalDeptUser) {
|
||||
var needStampUpload = (record.legalStampedContractUploaded === false) && !(_stampedUploadedOverride[0][record.id] === true);
|
||||
if (needStampUpload) {
|
||||
items.push({
|
||||
key: 'uploadStamped',
|
||||
label: '上传盖章合同',
|
||||
onClick: function() {
|
||||
_stampModalRecord[1](record);
|
||||
_stampFileList[1]([]);
|
||||
_stampModalVisible[1](true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -751,6 +825,7 @@ const Component = function() {
|
||||
{ title: '业务部门', dataIndex: 'businessDept', key: 'businessDept', width: 100 },
|
||||
{ title: '业务负责人', dataIndex: 'businessOwner', key: 'businessOwner', width: 100 },
|
||||
{ title: '合同类型', dataIndex: 'contractType', key: 'contractType', width: 100 },
|
||||
{ title: '合同审批类型', dataIndex: 'contractApprovalType', key: 'contractApprovalType', width: 130 },
|
||||
{ title: '合同结束日期', dataIndex: 'contractEndDate', key: 'contractEndDate', width: 120 },
|
||||
{ title: '客户联系人', dataIndex: 'contactName', key: 'contactName', width: 100 },
|
||||
{ title: '联系电话', dataIndex: 'contactPhone', key: 'contactPhone', width: 120 },
|
||||
@@ -865,6 +940,16 @@ const Component = function() {
|
||||
onChange: handleContractTypeChange,
|
||||
options: contractTypeOptions
|
||||
})),
|
||||
React.createElement('div', { key: 'contractApprovalType', style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '合同审批类型'),
|
||||
React.createElement(Select, {
|
||||
mode: 'multiple',
|
||||
placeholder: '请选择',
|
||||
style: filterControlStyle,
|
||||
value: _contractApprovalType[0],
|
||||
onChange: handleContractApprovalTypeChange,
|
||||
options: contractApprovalTypeOptions
|
||||
})),
|
||||
React.createElement('div', { key: 'creator', style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '创建人'),
|
||||
React.createElement(Select, {
|
||||
@@ -885,7 +970,7 @@ const Component = function() {
|
||||
}))
|
||||
];
|
||||
|
||||
var filterCount = _filterExpanded[0] ? 11 : 3;
|
||||
var filterCount = _filterExpanded[0] ? 12 : 3;
|
||||
var filterNodes = [];
|
||||
for (var i = 0; i < filterCount && i < filterItems.length; i++) {
|
||||
filterNodes.push(filterItems[i]);
|
||||
@@ -1034,6 +1119,61 @@ const Component = function() {
|
||||
scroll: { x: 920 }
|
||||
})
|
||||
)),
|
||||
React.createElement(Modal, {
|
||||
title: '上传盖章合同',
|
||||
open: _stampModalVisible[0],
|
||||
onCancel: function() {
|
||||
_stampModalVisible[1](false);
|
||||
_stampModalRecord[1](null);
|
||||
_stampFileList[1]([]);
|
||||
},
|
||||
width: 560,
|
||||
footer: [
|
||||
React.createElement(Button, { key: 'cancel', onClick: function() {
|
||||
_stampModalVisible[1](false);
|
||||
_stampModalRecord[1](null);
|
||||
_stampFileList[1]([]);
|
||||
} }, '取消'),
|
||||
React.createElement(Button, { key: 'ok', type: 'primary', onClick: function() {
|
||||
var rec = _stampModalRecord[0];
|
||||
var list = _stampFileList[0] || [];
|
||||
if (!list.length) {
|
||||
message.warning('请先选择要上传的文件');
|
||||
return;
|
||||
}
|
||||
var pending = list.some(function(f) { return f.status === 'uploading'; });
|
||||
if (pending) {
|
||||
message.warning('请等待文件上传完成');
|
||||
return;
|
||||
}
|
||||
_stampedUploadedOverride[1](function(prev) {
|
||||
var n = Object.assign({}, prev);
|
||||
if (rec) n[rec.id] = true;
|
||||
return n;
|
||||
});
|
||||
message.success('盖章合同已上传(原型)');
|
||||
_stampModalVisible[1](false);
|
||||
_stampModalRecord[1](null);
|
||||
_stampFileList[1]([]);
|
||||
} }, '确认上传')
|
||||
]
|
||||
}, React.createElement('div', { style: { padding: '8px 0' } },
|
||||
React.createElement('div', { style: { marginBottom: 12, fontSize: 14, color: 'rgba(0,0,0,0.65)' } },
|
||||
'合同编码:',
|
||||
React.createElement('span', { style: { color: 'rgba(0,0,0,0.85)', fontWeight: 500 } }, _stampModalRecord[0] ? _stampModalRecord[0].contractCode : '-')
|
||||
),
|
||||
React.createElement(Upload.Dragger, {
|
||||
multiple: true,
|
||||
fileList: _stampFileList[0],
|
||||
onChange: function(info) { _stampFileList[1](info.fileList); },
|
||||
customRequest: function(opts) {
|
||||
setTimeout(function() {
|
||||
if (opts.onSuccess) opts.onSuccess('ok');
|
||||
}, 200);
|
||||
},
|
||||
accept: '.pdf,.doc,.docx,image/*'
|
||||
}, React.createElement('p', { style: { margin: 0, padding: '20px 0' } }, '点击或拖拽文件到此区域上传,支持多文件'))
|
||||
)),
|
||||
React.createElement(Modal, {
|
||||
title: '需求说明',
|
||||
open: _requirementModalVisible[0],
|
||||
@@ -1047,7 +1187,7 @@ const Component = function() {
|
||||
React.createElement('div', { style: reqSectionStyle }, '1.面包屑:'),
|
||||
React.createElement('div', { style: reqSubStyle }, '1.1.业务管理-车辆租赁合同'),
|
||||
React.createElement('div', { style: reqSectionStyle }, '2.筛选:'),
|
||||
React.createElement('div', { style: reqSubStyle }, '2.1.支持通过合同编码、项目名称、客户名称、签约公司、审批状态、合同状态、业务部门、业务负责人、合同类型、创建人、合同结束日期等条件进行筛选,右侧为重置、查询、展开/收起(筛选条件以3列显示,默认显示一行,点击展开/收起对筛选栏卡片进行展开/收起所有筛选条件),点击查询后,筛选条件与列表内容联动。点击重置会回到默认筛选条件并在列表展示结果:'),
|
||||
React.createElement('div', { style: reqSubStyle }, '2.1.支持通过合同编码、项目名称、客户名称、签约公司、审批状态、合同状态、业务部门、业务负责人、合同类型、合同审批类型、创建人、合同结束日期等条件进行筛选,右侧为重置、查询、展开/收起(筛选条件以3列显示,默认显示一行,点击展开/收起对筛选栏卡片进行展开/收起所有筛选条件),点击查询后,筛选条件与列表内容联动。点击重置会回到默认筛选条件并在列表展示结果:'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.1.合同编码:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.2.项目名称:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.3.客户名称:选择器,支持从输入框内输入内容进行模糊搜索,下拉显示匹配选项;'),
|
||||
@@ -1057,10 +1197,11 @@ const Component = function() {
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.7.业务部门:选择器,支持全选或多选,拉取部门下所有业务相关部门;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.8.业务负责人:选择器,支持全选或多选,拉取所有业务相关部门下所有用户姓名;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.9.合同类型:选择器,支持全选或多选,选项为:全部、正式合同、试用合同;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.10.创建人:选择器,支持全选或多选,拉取所有业务相关部门下所有用户姓名;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.11.合同结束日期:日期选择器,支持单输入框内双日历选择开始-结束时间;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.10.合同审批类型:选择器,支持全选或多选,选项为:全部、标准合同审批、非标准合同审批;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.11.创建人:选择器,支持全选或多选,拉取所有业务相关部门下所有用户姓名;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '2.1.12.合同结束日期:日期选择器,支持单输入框内双日历选择开始-结束时间;'),
|
||||
React.createElement('div', { style: reqSectionStyle }, '3.列表:'),
|
||||
React.createElement('div', { style: reqSubStyle }, '3.1.列表展示所有租赁合同信息,字段依次为:合同编码、项目名称、租赁车辆数、已交车辆数、审批状态、合同状态、客户名称、签约公司、业务部门、业务负责人、合同类型、合同结束日期、客户联系人、联系电话、创建人、创建时间、更新人、最后更新时间、备注、操作;列表右上角为新增、导出;'),
|
||||
React.createElement('div', { style: reqSubStyle }, '3.1.列表展示所有租赁合同信息,字段依次为:合同编码、项目名称、租赁车辆数、已交车辆数、审批状态、合同状态、客户名称、签约公司、业务部门、业务负责人、合同类型、合同审批类型、合同结束日期、客户联系人、联系电话、创建人、创建时间、更新人、最后更新时间、备注、操作;列表右上角为新增、导出;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.1.合同编码:显示租赁合同对应合同编码;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.2.项目名称:显示租赁合同对应项目名称;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.3.租赁车辆数:显示租赁车辆数,点击显示气泡卡片,卡片中列表显示:车辆类型、品牌、型号、车牌号、实际交车日期;'),
|
||||
@@ -1089,29 +1230,31 @@ const Component = function() {
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.8.业务部门:显示租赁合同创建时所选业务部门,业务部门来自部门表(业务组);'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.9.业务负责人:显示租赁合同创建时所选业务负责人,与业务部门联动,查询该业务组下人员;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.10.合同类型:显示租赁合同类型,类型包括:正式合同、试用合同,于创建租赁合同时选取;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.11.合同结束日期:显示租赁合同结束日期,精确至日,格式为YYYY-MM-DD;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.12.客户联系人:显示租赁合同客户联系人姓名,客户联系人姓名来自「客户管理」-「联系人」;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.13.联系电话:显示租赁合同客户联系电话,客户联系电话来自「客户管理」-「联系人手机」;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.14.创建人:显示租赁合同创建人姓名,取自操作用户姓名;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.15.创建时间:显示租赁合同创建时间,精确至分钟,格式为YYYY-MM-DD HH:MM;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.16.更新人:显示租赁合同最后一次更新人姓名,取自操作用户姓名,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.17.最后更新时间:显示租赁合同最后一次更新时间,精确至分钟,格式为YYYY-MM-DD HH:MM,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.18.备注:显示租赁合同创建时输入的备注信息,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.19.操作:操作分为:查看、编辑、新增车辆、续签合同、删除合同、撤回合同、添加被授权人、附加费用、变更为三方合同、转正式合同、终止合同;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.1.查看:跳转车辆租赁合同-查看;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.2.编辑:当「合同状态」为「草稿」时显示,点击跳转编辑租赁合同页面,编辑租赁合同页面可输入项参考「新增租赁合同」页面,并支持对保存时已填内容进行修改;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.3.新增车辆:当「合同状态」为「合同进行中」时显示,仅能在租赁订单信息卡片下新订单中进行车辆新增;新增车辆提交后触发重新审核流程,审核通过后生效(不影响原有合同正常业务);'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.4.续签合同:当「合同状态」为「合同进行中」、「合同到期」时显示,点击跳转车辆租赁合同-续签合同页,提交时重新触发租赁合同审核流程;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.5.删除合同:当「合同状态」为「草稿」时显示,点击删除合同时进行二次确认,提示语:是否确认删除该合同草稿;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.6.撤回合同:当「合同状态」为「已提交审核」时显示,点击撤回合同时进行二次确认,提示语:是否确认撤回该合同;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.7.添加被授权人:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中可编辑被授权人、被授权人联系电话、被授权人身份证,同时支持添加一行、删除已有行等操作;添加被授权人触发重新审核流程,审核通过后生效;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.8.附加费用:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中显示该租赁合同内:车辆类型、品牌、型号、车牌号,同时可对服务项目、费用、生效时间进行编辑,触发租赁合同审核流程,审核通过后生效;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.8.1.服务项目:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.8.2.费用:输入框,后缀为元,支持2位小数输入;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.8.3.生效时间:日期选择器,精确至日;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.9.变更为三方合同:当「合同状态」为「合同进行中」时显示,点击跳转车辆租赁合同-变更为三方合同页面,提交时重新触发租赁合同审核流程;;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.10.转正式合同:当「合同类型」为「试用合同」时显示,点击后跳转车辆租赁合同-转正式合同页面,提交时重新触发租赁合同审核流程;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.19.11.终止合同:当「合同状态」为「合同进行中」时显示,点击后进行二次确认,提示语:是否确认终止合同,确认按钮为提交审核,会重新发起合同审核流程,审核通过后,合同状态变更为:已结束;')
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.11.合同审批类型:显示租赁合同创建时所选合同审批类型,包括:标准合同审批、非标准合同审批;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.12.合同结束日期:显示租赁合同结束日期,精确至日,格式为YYYY-MM-DD;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.13.客户联系人:显示租赁合同客户联系人姓名,客户联系人姓名来自「客户管理」-「联系人」;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.14.联系电话:显示租赁合同客户联系电话,客户联系电话来自「客户管理」-「联系人手机」;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.15.创建人:显示租赁合同创建人姓名,取自操作用户姓名;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.16.创建时间:显示租赁合同创建时间,精确至分钟,格式为YYYY-MM-DD HH:MM;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.17.更新人:显示租赁合同最后一次更新人姓名,取自操作用户姓名,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.18.最后更新时间:显示租赁合同最后一次更新时间,精确至分钟,格式为YYYY-MM-DD HH:MM,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.19.备注:显示租赁合同创建时输入的备注信息,如无则显示:-;'),
|
||||
React.createElement('div', { style: reqItemStyle }, '3.1.20.操作:操作分为:查看、编辑、新增车辆、续签合同、删除合同、撤回合同、添加被授权人、附加费用、变更为三方合同、转正式合同、终止合同、上传盖章合同;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.1.查看:跳转车辆租赁合同-查看;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.2.编辑:当「合同状态」为「草稿」时显示,点击跳转编辑租赁合同页面,编辑租赁合同页面可输入项参考「新增租赁合同」页面,并支持对保存时已填内容进行修改;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.3.新增车辆:当「合同状态」为「合同进行中」时显示,仅能在租赁订单信息卡片下新订单中进行车辆新增;新增车辆提交后触发重新审核流程,审核通过后生效(不影响原有合同正常业务);'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.4.续签合同:当「合同状态」为「合同进行中」、「合同到期」时显示,点击跳转车辆租赁合同-续签合同页,提交时重新触发租赁合同审核流程;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.5.删除合同:当「合同状态」为「草稿」时显示,点击删除合同时进行二次确认,提示语:是否确认删除该合同草稿;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.6.撤回合同:当「合同状态」为「已提交审核」时显示,点击撤回合同时进行二次确认,提示语:是否确认撤回该合同;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.7.添加被授权人:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中可编辑被授权人、被授权人联系电话、被授权人身份证,同时支持添加一行、删除已有行等操作;添加被授权人触发重新审核流程,审核通过后生效;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.8.附加费用:当「合同状态」为「合同进行中」时显示,点击弹出卡片,卡片中显示该租赁合同内:车辆类型、品牌、型号、车牌号,同时可对服务项目、费用、生效时间进行编辑,触发租赁合同审核流程,审核通过后生效;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.8.1.服务项目:代处理费用、罚款、违章处理违约金、未参加安全培训、车辆出险、年检年审违约、停车费、设备损坏金(包含易损件)、清洗费、上门收车人工费、上门收车送车行驶费、上门收车基础服务费、保险上浮、保养费用、补办驾驶证、补办牌照、补办营运证、补办加氢证、借用备用钥匙、补配钥匙、租金、氢气费-客、退还车氢量差、能源费补缴、能源费退款、送车上门人工费、送车上门送车行驶费、送车上门基础服务费、保证金、氢气预付费、维修费用、ETC-客、ETC卡缺损费、ETC设备缺损费、电费-客、未结算保养费、未结算维修费、车损费、工具损坏或丢失费、证件费、广告损坏费、送车服务费、接车服务费、补办行驶证、超赔险、轮胎磨损费、无忧包、轮胎保、养护保、尾板;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.8.2.费用:输入框,后缀为元,支持2位小数输入;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.8.3.生效时间:日期选择器,精确至日;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.9.变更为三方合同:当「合同状态」为「合同进行中」时显示,点击跳转车辆租赁合同-变更为三方合同页面,提交时重新触发租赁合同审核流程;;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.10.转正式合同:当「合同类型」为「试用合同」时显示,点击后跳转车辆租赁合同-转正式合同页面,提交时重新触发租赁合同审核流程;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.11.终止合同:当「合同状态」为「合同进行中」时显示,点击后进行二次确认,提示语:是否确认终止合同,确认按钮为提交审核,会重新发起合同审核流程,审核通过后,合同状态变更为:已结束;'),
|
||||
React.createElement('div', { style: reqSubItemStyle }, '3.1.20.12.上传盖章合同:当「审批状态」为「审批通过」且法务审核环节尚未上传盖章合同附件时,在「更多」中显示,仅法务部员工可见并可操作;点击后弹出上传窗口,可浏览本地文件上传,支持多文件;上传完成后关闭该入口(列表不再展示「上传盖章合同」);')
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user