feat(web): 租赁账单、安全培训扫码、提车应收款等模块更新与新增

Made-with: Cursor
This commit is contained in:
王冕
2026-03-11 09:10:28 +08:00
parent 4bc7ad9741
commit 30e3d9f156
17 changed files with 3712 additions and 1384 deletions

View File

@@ -0,0 +1,268 @@
// 【重要】必须使用 const Component 作为组件变量名
// 安全培训扫码 - H5 分步表单(扫码链接进入)
const Component = function () {
var useState = React.useState;
var useCallback = React.useCallback;
var useRef = React.useRef;
var useEffect = React.useEffect;
var antd = window.antd;
var Steps = antd.Steps;
var Button = antd.Button;
var Input = antd.Input;
var message = antd.message;
var Progress = antd.Progress;
// 当前步骤 1 | 2 | 3 | 4
var stepState = useState(1);
var step = stepState[0];
var setStep = stepState[1];
// 第一步:手机号、验证码
var phoneState = useState('');
var verifyCodeState = useState('');
var codeCountdownState = useState(0);
var phone = phoneState[0];
var setPhone = phoneState[1];
var verifyCode = verifyCodeState[0];
var setVerifyCode = verifyCodeState[1];
var codeCountdown = codeCountdownState[0];
var setCodeCountdown = codeCountdownState[1];
// 模拟:已完成培训的手机号,再次进入直接显示提车码
var completedPhonesState = useState({ '13800138000': 'TC-2026-8888', '13900139000': 'TC-2026-9999' });
var completedPhones = completedPhonesState[0];
var setCompletedPhones = completedPhonesState[1];
// 第二步:证照上传(用文件名/已上传标记模拟)
var idFrontState = useState(null);
var idBackState = useState(null);
var licenseState = useState(null);
var qualificationState = useState(null);
var needQualification = false; // 18吨以上车辆必填此处为可选
// 第三步视频进度0-100是否播放中
var videoProgressState = useState(0);
var videoPlayingState = useState(false);
var videoProgress = videoProgressState[0];
var setVideoProgress = videoProgressState[1];
var videoPlaying = videoPlayingState[0];
var setVideoPlaying = videoPlayingState[1];
var videoTimerRef = useRef(null);
// 第四步:提车码(生成后保存,同一手机再次进入可直接显示)
var pickupCodeState = useState('');
var pickupCode = pickupCodeState[0];
var setPickupCode = pickupCodeState[1];
// 获取验证码倒计时
useEffect(function () {
if (codeCountdown <= 0) return;
var t = setTimeout(function () { setCodeCountdown(function (c) { return c - 1; }); }, 1000);
return function () { clearTimeout(t); };
}, [codeCountdown]);
// 视频模拟:播放时每 500ms 增加进度,到 100% 停止
useEffect(function () {
if (!videoPlaying || videoProgress >= 100) {
if (videoProgress >= 100) setVideoPlaying(false);
return;
}
var t = setInterval(function () {
setVideoProgress(function (p) {
var next = Math.min(100, p + 2);
return next;
});
}, 500);
return function () { clearInterval(t); };
}, [videoPlaying, videoProgress]);
// 第一步:验证并进入
var handleStep1Next = useCallback(function () {
var p = (phone || '').trim();
var c = (verifyCode || '').trim();
if (!p) { message.warning('请输入手机号'); return; }
if (!c) { message.warning('请输入验证码'); return; }
// 已完成培训的手机号直接进入第四步
if (completedPhones[p]) {
setPickupCode(completedPhones[p]);
setStep(4);
return;
}
// 模拟验证成功(任意 4-6 位验证码)
if (c.length < 4) { message.warning('请输入正确的验证码'); return; }
setStep(2);
}, [phone, verifyCode, completedPhones]);
var handleSendCode = useCallback(function () {
var p = (phone || '').trim();
if (!p || p.length < 11) { message.warning('请输入正确手机号'); return; }
if (codeCountdown > 0) return;
setCodeCountdown(60);
message.success('验证码已发送');
}, [phone, codeCountdown]);
// 第二步:上传(模拟点击即视为已上传)
var uploadAreaStyle = {
border: '1px dashed #d9d9d9',
borderRadius: 8,
padding: '24px 16px',
textAlign: 'center',
background: '#fafafa',
color: 'rgba(0,0,0,0.65)',
fontSize: 14,
cursor: 'pointer'
};
var uploadDoneStyle = { borderColor: '#52c41a', background: '#f6ffed', color: '#52c41a' };
function renderUpload(label, value, setValue) {
var done = value != null && value !== '';
return React.createElement('div', {
key: label,
style: Object.assign({}, uploadAreaStyle, done ? uploadDoneStyle : {}),
onClick: function () {
// 模拟选择文件/拍照:点击即视为已上传
setValue(done ? null : label + '-已上传.jpg');
}
}, done ? (value + ' ✓') : ('点击上传 ' + label + '(支持现场拍照/本地文件)'));
}
var allRequiredUploaded = (idFrontState[0] != null && idFrontState[0] !== '') &&
(idBackState[0] != null && idBackState[0] !== '') &&
(licenseState[0] != null && licenseState[0] !== '') &&
(!needQualification || (qualificationState[0] != null && qualificationState[0] !== ''));
var handleStep2WatchVideo = useCallback(function () {
if (!allRequiredUploaded) { message.warning('请完成全部必填证照上传'); return; }
setStep(3);
setVideoProgress(0);
setVideoPlaying(false);
}, [allRequiredUploaded]);
// 第三步:播放/暂停,进度到 100% 后可生成提车码
var handleVideoPlayPause = useCallback(function () {
if (videoProgress >= 100) return;
setVideoPlaying(function (v) { return !v; });
}, [videoProgress]);
var handleStep3Generate = useCallback(function () {
if (videoProgress < 100) { message.warning('请完整观看安全培训视频'); return; }
var code = 'TC-' + new Date().getFullYear() + '-' + Math.floor(1000 + Math.random() * 9000);
setPickupCode(code);
setCompletedPhones(function (prev) {
var next = {}; for (var k in prev) next[k] = prev[k]; next[phone.trim()] = code; return next;
});
setStep(4);
message.success('提车码已生成');
}, [videoProgress, phone]);
// H5 移动端布局
var pageStyle = {
maxWidth: 414,
margin: '0 auto',
minHeight: '100vh',
background: '#f5f5f5',
padding: '16px',
boxSizing: 'border-box'
};
var stepIndicatorStyle = { marginBottom: 24 };
var cardStyle = { marginBottom: 16, borderRadius: 12 };
var labelStyle = { display: 'block', marginBottom: 8, fontSize: 15, color: 'rgba(0,0,0,0.85)' };
var inputStyle = { width: '100%', height: 48, fontSize: 16, boxSizing: 'border-box' };
var btnBlockStyle = { width: '100%', height: 48, fontSize: 16, marginTop: 8 };
var stepTitleStyle = { fontSize: 17, fontWeight: 600, marginBottom: 16, color: 'rgba(0,0,0,0.85)' };
var stepsItems = [
{ title: '验证' },
{ title: '证照' },
{ title: '视频' },
{ title: '提车码' }
];
// 第一步视图
var step1Content = React.createElement('div', null,
React.createElement('div', { style: stepTitleStyle }, '手机号验证'),
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('label', { style: labelStyle }, '手机号'),
React.createElement(Input, {
placeholder: '请输入手机号',
value: phone,
onChange: function (e) { setPhone(e.target.value); },
style: inputStyle,
maxLength: 11,
type: 'tel'
})
),
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('label', { style: labelStyle }, '验证码'),
React.createElement('div', { style: { display: 'flex', gap: 8 } },
React.createElement(Input, {
placeholder: '请输入验证码',
value: verifyCode,
onChange: function (e) { setVerifyCode(e.target.value); },
style: Object.assign({}, inputStyle, { flex: 1 }),
maxLength: 6
}),
React.createElement(Button, {
disabled: codeCountdown > 0,
onClick: handleSendCode,
style: { height: 48, minWidth: 100 }
}, codeCountdown > 0 ? codeCountdown + 's' : '获取验证码')
)
),
React.createElement(Button, { type: 'primary', size: 'large', style: btnBlockStyle, onClick: handleStep1Next }, '下一步')
);
// 第二步视图
var step2Content = React.createElement('div', null,
React.createElement('div', { style: stepTitleStyle }, '证照上传'),
React.createElement('div', { style: { marginBottom: 12 } }, '请上传以下证照,支持现场拍照或手机本地文件。'),
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } },
renderUpload('身份证正面', idFrontState[0], idFrontState[1]),
renderUpload('身份证反面', idBackState[0], idBackState[1]),
renderUpload('驾驶证', licenseState[0], licenseState[1]),
renderUpload('从业资格证18吨以上车辆请上传可选', qualificationState[0], qualificationState[1])
),
React.createElement(Button, { type: 'primary', size: 'large', style: Object.assign({}, btnBlockStyle, { marginTop: 24 }), onClick: handleStep2WatchVideo, disabled: !allRequiredUploaded }, '观看安全培训视频')
);
// 第三步视图:视频区域(不可快进快退,仅暂停/播放)
var step3Content = React.createElement('div', null,
React.createElement('div', { style: stepTitleStyle }, '安全培训视频'),
React.createElement('div', { style: { marginBottom: 8, fontSize: 14, color: 'rgba(0,0,0,0.65)' } }, '请完整观看视频,不支持快进快退。'),
React.createElement('div', {
style: { background: '#000', borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', paddingBottom: '56.25%', height: 0 },
onClick: handleVideoPlayPause
},
React.createElement('div', { style: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 14 } },
videoProgress >= 100 ? '播放完成' : (videoPlaying ? '播放中... 点击暂停' : '点击播放')
)
),
React.createElement('div', { style: { marginBottom: 8 } },
React.createElement(Progress, { percent: videoProgress, showInfo: true })
),
React.createElement('div', { style: { display: 'flex', gap: 8 } },
React.createElement(Button, { onClick: handleVideoPlayPause, disabled: videoProgress >= 100 }, videoPlaying ? '暂停' : '播放'),
React.createElement(Button, { type: 'primary', disabled: videoProgress < 100, onClick: handleStep3Generate }, '生成提车码')
)
);
// 第四步视图
var step4Content = React.createElement('div', { style: { textAlign: 'center', padding: '24px 0' } },
React.createElement('div', { style: stepTitleStyle }, '提车码'),
React.createElement('div', { style: { fontSize: 15, color: 'rgba(0,0,0,0.65)', marginBottom: 24 } }, '小程序扫描提车码后自动拉取司机证件信息。提车码在运维完成扫提车码并交车成功后失效。'),
React.createElement('div', {
style: { fontSize: 28, fontWeight: 700, letterSpacing: 4, padding: '20px', background: '#f0f0f0', borderRadius: 12, marginBottom: 16, userSelect: 'all' }
}, pickupCode || '—'),
React.createElement('div', { style: { width: 160, height: 160, margin: '0 auto 16px', background: '#f0f0f0', border: '2px dashed #d9d9d9', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#999', fontSize: 12 } }, '提车码二维码')
);
var stepContents = [step1Content, step2Content, step3Content, step4Content];
return React.createElement('div', { style: pageStyle },
React.createElement('div', { style: { padding: '12px 0', marginBottom: 8, fontSize: 18, fontWeight: 600 } }, '安全培训扫码'),
React.createElement(Steps, { current: step - 1, style: stepIndicatorStyle, size: 'small', items: stepsItems }),
React.createElement('div', { style: { background: '#fff', borderRadius: 12, padding: 20, boxShadow: '0 1px 2px rgba(0,0,0,0.05)' } }, stepContents[step - 1])
);
};