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

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

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

View File

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

View File

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