feat(web): 同步 web 端目录更新至 Gitea
包含加氢站站点信息、运维交车/故障、台账与数据分析等页面新增与改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -113,6 +113,12 @@ const Component = function() {
|
||||
var os1j = React.useState('');
|
||||
var serviceItemSearch = os1j[0];
|
||||
var setServiceItemSearch = os1j[1];
|
||||
var copyPopState = React.useState(null);
|
||||
var copyPopover = copyPopState[0];
|
||||
var setCopyPopover = copyPopState[1];
|
||||
var copyQtyState = React.useState('1');
|
||||
var copyQuantity = copyQtyState[0];
|
||||
var setCopyQuantity = copyQtyState[1];
|
||||
var os1k = React.useState(null);
|
||||
var plateNoDropdownRect = os1k[0];
|
||||
var setPlateNoDropdownRect = os1k[1];
|
||||
@@ -296,16 +302,57 @@ const Component = function() {
|
||||
next.splice(index, 1);
|
||||
setRentalOrders(next.length ? next : [Object.assign({}, emptyRentalRow)]);
|
||||
};
|
||||
var copyRentalRow = function(index) {
|
||||
var row = rentalOrders[index];
|
||||
if (!row) return;
|
||||
var serviceItemsCopy = (row.serviceItems || []).map(function(si) { return { project: si.project || '', fee: si.fee || '', effectiveDate: si.effectiveDate || '' }; });
|
||||
var buildDuplicateRentalRow = function(sourceRow) {
|
||||
var serviceItemsCopy = (sourceRow.serviceItems || []).map(function(si) { return { project: si.project || '', fee: si.fee || '', effectiveDate: si.effectiveDate || '' }; });
|
||||
if (serviceItemsCopy.length === 0) serviceItemsCopy = [{ project: '', fee: '', effectiveDate: '' }];
|
||||
var newRow = { id: nextRentalRowIdRef.current++, brand: row.brand || '', model: row.model || '', plateNo: '', vin: '', monthRent: row.monthRent || '', serviceItems: serviceItemsCopy, deposit: row.deposit || '', remark: row.remark || '' };
|
||||
return { id: nextRentalRowIdRef.current++, brand: sourceRow.brand || '', model: sourceRow.model || '', plateNo: '', vin: '', monthRent: sourceRow.monthRent || '', serviceItems: serviceItemsCopy, deposit: sourceRow.deposit || '', remark: sourceRow.remark || '' };
|
||||
};
|
||||
var applyRentalRowCopies = function(rowIndex, count) {
|
||||
var row = rentalOrders[rowIndex];
|
||||
if (!row) return;
|
||||
var n = typeof count === 'number' ? count : parseInt(String(count), 10);
|
||||
if (isNaN(n) || n < 0) {
|
||||
if (message.warning) message.warning('请输入非负整数');
|
||||
return;
|
||||
}
|
||||
n = Math.floor(n);
|
||||
if (n === 0) {
|
||||
setCopyPopover(null);
|
||||
if (message.info) message.info('复制数量为 0,未新增行');
|
||||
return;
|
||||
}
|
||||
var next = rentalOrders.slice(0);
|
||||
next.splice(index + 1, 0, newRow);
|
||||
var insertAt = rowIndex + 1;
|
||||
for (var i = 0; i < n; i++) {
|
||||
next.splice(insertAt + i, 0, buildDuplicateRentalRow(row));
|
||||
}
|
||||
setRentalOrders(next);
|
||||
if (typeof message !== 'undefined' && message.success) message.success('已复制该行(车牌号已清空)');
|
||||
setCopyPopover(null);
|
||||
if (message.success) message.success('已复制 ' + n + ' 条记录(车牌号已清空)');
|
||||
};
|
||||
var confirmRentalCopyPopover = function() {
|
||||
if (!copyPopover) return;
|
||||
if (copyQuantity === '') {
|
||||
if (message.warning) message.warning('请输入复制数量');
|
||||
return;
|
||||
}
|
||||
if (!/^\d+$/.test(copyQuantity)) {
|
||||
if (message.warning) message.warning('请输入非负整数');
|
||||
return;
|
||||
}
|
||||
applyRentalRowCopies(copyPopover.index, parseInt(copyQuantity, 10));
|
||||
};
|
||||
var openRentalCopyPopover = function(rowIndex, anchorEl) {
|
||||
if (!anchorEl || !anchorEl.getBoundingClientRect) return;
|
||||
var r = anchorEl.getBoundingClientRect();
|
||||
var cardH = 168;
|
||||
var top = r.bottom + 8;
|
||||
if (top + cardH > window.innerHeight - 8) top = Math.max(8, r.top - cardH - 8);
|
||||
var left = r.left;
|
||||
var cardW = 240;
|
||||
if (left + cardW > window.innerWidth - 8) left = Math.max(8, window.innerWidth - cardW - 8);
|
||||
setCopyPopover({ index: rowIndex, top: top, left: left });
|
||||
setCopyQuantity('1');
|
||||
};
|
||||
var updateRentalOrder = function(index, field, value) {
|
||||
var next = rentalOrders.slice(0);
|
||||
@@ -384,6 +431,22 @@ const Component = function() {
|
||||
return function() { document.removeEventListener('mousedown', handler); };
|
||||
}, [deliveryRegionOpen]);
|
||||
|
||||
React.useEffect(function() {
|
||||
if (!copyPopover) return;
|
||||
var handler = function(e) {
|
||||
var pop = document.getElementById('rental-copy-popover');
|
||||
if (pop && pop.contains(e.target)) return;
|
||||
setCopyPopover(null);
|
||||
};
|
||||
var t = window.setTimeout(function() {
|
||||
document.addEventListener('mousedown', handler);
|
||||
}, 0);
|
||||
return function() {
|
||||
window.clearTimeout(t);
|
||||
document.removeEventListener('mousedown', handler);
|
||||
};
|
||||
}, [copyPopover]);
|
||||
|
||||
React.useEffect(function() {
|
||||
if (plateNoFocusRow === null) { setPlateNoDropdownRect(null); return; }
|
||||
var timer = setTimeout(function() {
|
||||
@@ -451,7 +514,10 @@ const Component = function() {
|
||||
btnGroupItemActive: { backgroundColor: '#1677ff', color: '#fff', borderColor: '#1677ff', borderRightColor: '#1677ff' },
|
||||
feeSectionTitle: { fontSize: 16, fontWeight: 600, color: '#333', marginTop: 20, marginBottom: 10 },
|
||||
feeSectionTitleFirst: { marginTop: 0 },
|
||||
modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' }
|
||||
modalFormInput: { width: '100%', padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: 4, fontSize: 14, height: 36, boxSizing: 'border-box' },
|
||||
copyPopoverCard: { position: 'fixed', zIndex: 2000, backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.12), 0 0 1px rgba(0,0,0,0.08)', border: '1px solid #f0f0f0', padding: '14px 16px', minWidth: 232, maxWidth: 'calc(100vw - 24px)' },
|
||||
copyPopoverTitle: { fontSize: 14, fontWeight: 600, color: 'rgba(0,0,0,0.88)', marginBottom: 12 },
|
||||
copyPopoverFooter: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }
|
||||
};
|
||||
|
||||
var CardBlock = function(props) {
|
||||
@@ -582,7 +648,7 @@ const Component = function() {
|
||||
React.createElement('td', { style: styles.rentalTdCenter }, calcRowServiceFee(row) + ' 元'),
|
||||
React.createElement('td', { style: styles.rentalTd }, React.createElement('div', { style: { display: 'flex', alignItems: 'center' } }, React.createElement(Input, { placeholder: '0.00', value: row.deposit || '', onChange: function(e) { updateRentalOrder(idx, 'deposit', e.target.value); }, style: styles.rentalInput }), React.createElement('span', { style: { marginLeft: 4, whiteSpace: 'nowrap' } }, '元'))),
|
||||
React.createElement('td', { style: styles.rentalTd }, React.createElement(Input, { placeholder: '备注', value: row.remark || '', onChange: function(e) { updateRentalOrder(idx, 'remark', e.target.value); }, style: Object.assign({}, styles.rentalInput, { width: '100%' }) })),
|
||||
React.createElement('td', { style: styles.rentalTdCenter }, React.createElement('div', { style: { display: 'inline-flex', gap: 4, alignItems: 'center' } }, React.createElement(Button, { type: 'link', size: 'small', onClick: function() { copyRentalRow(idx); } }, '复制'), React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')))
|
||||
React.createElement('td', { style: styles.rentalTdCenter }, React.createElement('div', { style: { display: 'inline-flex', gap: 4, alignItems: 'center' } }, React.createElement(Button, { type: 'link', size: 'small', 'aria-haspopup': 'dialog', 'aria-expanded': copyPopover && copyPopover.index === idx, onClick: function(e) { e.preventDefault(); e.stopPropagation(); openRentalCopyPopover(idx, e.currentTarget); } }, '复制'), React.createElement(Button, { type: 'link', size: 'small', danger: true, onClick: function() { removeRentalRow(idx); } }, '删除')))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -609,6 +675,41 @@ const Component = function() {
|
||||
var rentalTableTbody = React.createElement('tbody', null, rentalTableBody);
|
||||
var rentalTableEl = React.createElement('table', { style: styles.rentalTable }, rentalTableThead, rentalTableTbody);
|
||||
var rentalTableWrap = React.createElement('div', { style: { overflowX: 'auto', marginBottom: 16 } }, rentalTableEl);
|
||||
var rentalCopyPopoverEl = copyPopover
|
||||
? React.createElement('div', {
|
||||
id: 'rental-copy-popover',
|
||||
role: 'dialog',
|
||||
'aria-modal': 'false',
|
||||
'aria-label': '复制租赁订单行',
|
||||
style: Object.assign({}, styles.copyPopoverCard, { top: copyPopover.top, left: copyPopover.left }),
|
||||
onMouseDown: function(e) { e.stopPropagation(); }
|
||||
},
|
||||
React.createElement('div', { style: styles.copyPopoverTitle }, '复制本行'),
|
||||
React.createElement('div', null,
|
||||
React.createElement('label', { style: { display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 } }, '复制数量'),
|
||||
React.createElement(Input, {
|
||||
value: copyQuantity,
|
||||
placeholder: '非负整数,如 3',
|
||||
inputMode: 'numeric',
|
||||
pattern: '[0-9]*',
|
||||
autoComplete: 'off',
|
||||
'aria-label': '复制数量,非负整数',
|
||||
onChange: function(e) {
|
||||
var raw = (e.target && e.target.value) || '';
|
||||
var digits = raw.replace(/\D/g, '');
|
||||
setCopyQuantity(digits);
|
||||
},
|
||||
onPressEnter: confirmRentalCopyPopover,
|
||||
style: { width: '100%' }
|
||||
})
|
||||
),
|
||||
React.createElement('div', { style: styles.copyPopoverFooter },
|
||||
React.createElement(Button, { size: 'small', onClick: function() { setCopyPopover(null); } }, '取消'),
|
||||
React.createElement(Button, { type: 'primary', size: 'small', onClick: confirmRentalCopyPopover }, '确认')
|
||||
)
|
||||
)
|
||||
: null;
|
||||
|
||||
var rentalContent = React.createElement('div', null, rentalSummary, formErrors.rentalOrders ? React.createElement('div', { style: styles.errMsg }, formErrors.rentalOrders) : null, rentalTableWrap, React.createElement(Button, { type: 'dashed', style: { marginTop: 12, width: '100%' }, onClick: addRentalRow }, '添加一行'), hydrogenFormRow);
|
||||
|
||||
var feeTableHeader3 = React.createElement('tr', null, React.createElement('th', { style: styles.rentalTh }, '项目'), React.createElement('th', { style: styles.rentalTh }, '收费标准'), React.createElement('th', { style: styles.rentalTh }, '服务费'));
|
||||
@@ -692,6 +793,7 @@ const Component = function() {
|
||||
React.createElement(CardBlock, { id: 'card-fee', cardStyle: { marginTop: 16 }, title: '其他费用信息', collapsed: cc5, setCollapsed: setCc5 }, feeContent),
|
||||
React.createElement(CardBlock, { id: 'card-billing', cardStyle: { marginTop: 16 }, title: '账单计算方式', collapsed: cc6, setCollapsed: setCc6 }, billingContent),
|
||||
React.createElement('div', { style: { height: 60 } }),
|
||||
rentalCopyPopoverEl,
|
||||
serviceModalContent,
|
||||
reqSpecModalContent,
|
||||
React.createElement('div', { style: styles.footer }, React.createElement(Button, { type: 'primary', size: 'large', onClick: function() { if (validateSubmitAndReview()) { message.success('租赁合同已提交审核。'); } } }, '提交并审核'), React.createElement(Button, { size: 'large', onClick: function() { message.info('保存,加入租赁合同列表(仅操作人可查看编辑)'); } }, '保存'), React.createElement(Button, { size: 'large', onClick: function() { message.info('取消'); } }, '取消'))
|
||||
|
||||
@@ -15,6 +15,8 @@ const Component = function() {
|
||||
var Card = antd.Card;
|
||||
var DatePicker = antd.DatePicker;
|
||||
var Popover = antd.Popover;
|
||||
var Tag = antd.Tag;
|
||||
var Avatar = antd.Avatar;
|
||||
var Dropdown = antd.Dropdown;
|
||||
var Modal = antd.Modal;
|
||||
var Upload = antd.Upload;
|
||||
@@ -258,7 +260,12 @@ const Component = function() {
|
||||
updater: '李专员',
|
||||
updateTime: '2025-02-15 16:00',
|
||||
remark: '-',
|
||||
legalStampedContractUploaded: undefined
|
||||
legalStampedContractUploaded: undefined,
|
||||
approvalFlowNodes: [
|
||||
{ nodeTitle: '业务服务主管审批', result: 'passed', operatorName: '姚守涛', operatorTime: '2026-04-29 17:57:15' },
|
||||
{ nodeTitle: '发起审批', result: 'passed', operatorName: '超级用户', operatorTime: '2026-04-28 17:44:45' },
|
||||
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['超级用户', '金可鹏'] }
|
||||
]
|
||||
},
|
||||
// 5. 审批中 + 变更(已通过审批后做了变更并重新提交)
|
||||
{
|
||||
@@ -285,7 +292,12 @@ const Component = function() {
|
||||
updater: '李专员',
|
||||
updateTime: '2025-02-22 14:00',
|
||||
remark: '变更车辆数量',
|
||||
legalStampedContractUploaded: undefined
|
||||
legalStampedContractUploaded: undefined,
|
||||
approvalFlowNodes: [
|
||||
{ nodeTitle: '法务审核', result: 'passed', operatorName: '李法务', operatorTime: '2025-02-21 18:00:00' },
|
||||
{ nodeTitle: '发起审批', result: 'passed', operatorName: '李专员', operatorTime: '2025-02-22 10:00:00' },
|
||||
{ nodeTitle: '业务负责人审批', result: 'pending', pendingApprovers: ['张经理', '赵总监'] }
|
||||
]
|
||||
},
|
||||
// 6. 审批通过 + 合同进行中(正式合同)
|
||||
{
|
||||
@@ -664,6 +676,131 @@ const Component = function() {
|
||||
{ title: '交车人', dataIndex: 'deliveryPerson', key: 'deliveryPerson', width: 100 }
|
||||
];
|
||||
|
||||
function approvalAvatarText(name) {
|
||||
if (!name) return '?';
|
||||
var s = String(name);
|
||||
return s.length <= 2 ? s : s.slice(-2);
|
||||
}
|
||||
|
||||
function renderApprovalFlowContent(nodes) {
|
||||
var list = nodes && nodes.length ? nodes : [];
|
||||
if (!list.length) {
|
||||
return React.createElement('div', { style: { color: 'rgba(0,0,0,0.45)', fontSize: 13, padding: '4px 0' } }, '暂无审批节点明细');
|
||||
}
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ style: { maxWidth: 360, paddingTop: 4 } },
|
||||
list.map(function(node, index) {
|
||||
var isLast = index === list.length - 1;
|
||||
var isPending = node.result === 'pending';
|
||||
var dot = React.createElement(
|
||||
Avatar,
|
||||
{
|
||||
size: 28,
|
||||
style: {
|
||||
backgroundColor: isPending ? '#1890ff' : '#f0f0f0',
|
||||
color: isPending ? '#fff' : 'rgba(0,0,0,0.45)',
|
||||
fontSize: 11,
|
||||
lineHeight: '28px',
|
||||
flexShrink: 0
|
||||
},
|
||||
children: isPending
|
||||
? React.createElement(
|
||||
'svg',
|
||||
{ viewBox: '0 0 24 24', width: 14, height: 14, fill: 'currentColor', style: { verticalAlign: 'middle' } },
|
||||
React.createElement('path', {
|
||||
d: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4zm5-9h2v2h-2V5zm0 4h2v2h-2V9z'
|
||||
})
|
||||
)
|
||||
: approvalAvatarText(node.operatorName)
|
||||
}
|
||||
);
|
||||
var body = React.createElement(
|
||||
'div',
|
||||
{ style: { flex: 1, paddingLeft: 12, paddingBottom: isLast ? 0 : 14, minWidth: 0 } },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ style: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginBottom: 6 } },
|
||||
React.createElement('span', { style: { fontSize: 14, color: 'rgba(0,0,0,0.88)', fontWeight: 500 } }, node.nodeTitle || '-'),
|
||||
React.createElement(Tag, { color: isPending ? 'processing' : 'success', style: { margin: 0 } }, isPending ? '待审核' : '通过')
|
||||
),
|
||||
isPending && node.pendingApprovers && node.pendingApprovers.length
|
||||
? React.createElement(
|
||||
Space,
|
||||
{ size: [8, 8], wrap: true },
|
||||
node.pendingApprovers.map(function(apName, i) {
|
||||
return React.createElement(
|
||||
Tag,
|
||||
{
|
||||
key: i,
|
||||
style: {
|
||||
margin: 0,
|
||||
color: '#1890ff',
|
||||
background: '#e6f7ff',
|
||||
borderColor: '#91d5ff'
|
||||
}
|
||||
},
|
||||
React.createElement(
|
||||
'span',
|
||||
null,
|
||||
React.createElement(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 24 24',
|
||||
width: 12,
|
||||
height: 12,
|
||||
fill: '#1890ff',
|
||||
style: { marginRight: 4, verticalAlign: '-2px' }
|
||||
},
|
||||
React.createElement('path', {
|
||||
d: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'
|
||||
})
|
||||
),
|
||||
apName
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ style: { fontSize: 13, color: 'rgba(0,0,0,0.45)' } },
|
||||
(node.operatorName || '') + (node.operatorTime ? ' ' + node.operatorTime : '')
|
||||
)
|
||||
);
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ key: index, style: { display: 'flex', alignItems: 'stretch' } },
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
style: {
|
||||
width: 36,
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}
|
||||
},
|
||||
React.createElement('div', { style: { lineHeight: 0 } }, dot),
|
||||
isLast
|
||||
? null
|
||||
: React.createElement('div', {
|
||||
style: {
|
||||
flex: 1,
|
||||
width: 2,
|
||||
minHeight: 12,
|
||||
marginTop: 4,
|
||||
background: '#f0f0f0',
|
||||
borderRadius: 1
|
||||
}
|
||||
})
|
||||
),
|
||||
body
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getMoreMenuItems(record) {
|
||||
var status = record.contractStatus;
|
||||
var type = record.contractType;
|
||||
@@ -818,7 +955,37 @@ const Component = function() {
|
||||
);
|
||||
}
|
||||
},
|
||||
{ title: '审批状态', dataIndex: 'approvalStatus', key: 'approvalStatus', width: 100 },
|
||||
{
|
||||
title: '审批状态',
|
||||
dataIndex: 'approvalStatus',
|
||||
key: 'approvalStatus',
|
||||
width: 100,
|
||||
render: function(text, record) {
|
||||
if (text !== '审批中') return text;
|
||||
var nodes = record.approvalFlowNodes;
|
||||
return React.createElement(
|
||||
Popover,
|
||||
{
|
||||
content: renderApprovalFlowContent(nodes),
|
||||
trigger: 'hover',
|
||||
placement: 'rightTop',
|
||||
overlayInnerStyle: { maxWidth: 400 },
|
||||
mouseEnterDelay: 0.15
|
||||
},
|
||||
React.createElement(
|
||||
'span',
|
||||
{
|
||||
style: {
|
||||
cursor: 'help',
|
||||
borderBottom: '1px dashed rgba(0,0,0,0.35)',
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
}
|
||||
},
|
||||
text
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{ title: '合同状态', dataIndex: 'contractStatus', key: 'contractStatus', width: 110 },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140 },
|
||||
{ title: '签约公司', dataIndex: 'signingCompany', key: 'signingCompany', width: 100 },
|
||||
|
||||
Reference in New Issue
Block a user