feat(miniapp): 新增小羚羚小程序主体与审批中心等页面,并重构年审管理为支持嵌入XLL主题
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1864
ONE-OS小程序/审批中心.jsx
Normal file
1864
ONE-OS小程序/审批中心.jsx
Normal file
File diff suppressed because it is too large
Load Diff
5356
ONE-OS小程序/小羚羚.jsx
Normal file
5356
ONE-OS小程序/小羚羚.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2152,6 +2152,43 @@ const PAGE_STYLE = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const XLL_GREEN = '#7AB929';
|
||||||
|
const XLL_GREEN_DEEP = '#6AA322';
|
||||||
|
const XLL_GREEN_SOFT = 'rgba(122, 185, 41, 0.14)';
|
||||||
|
|
||||||
|
const EMBED_STYLE = `
|
||||||
|
.ar-embed-root {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background: ${COLOR_PAGE};
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ar-embed-root .ar-tabs,
|
||||||
|
.ar-embed-root .ar-search-row { flex-shrink: 0; }
|
||||||
|
.ar-embed-root .ar-list,
|
||||||
|
.ar-embed-root .ar-operate-scroll,
|
||||||
|
.ar-embed-root .ar-history-scroll { flex: 1; min-height: 0; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const XLL_THEME_PATCH = `
|
||||||
|
.xll-module-theme .ar-tab.active { color: ${XLL_GREEN}; }
|
||||||
|
.xll-module-theme .ar-tab.active::after { background: ${XLL_GREEN}; }
|
||||||
|
.xll-module-theme .ar-filter-btn:active { border-color: ${XLL_GREEN}; color: ${XLL_GREEN}; }
|
||||||
|
.xll-module-theme .ar-card-action { color: ${XLL_GREEN}; }
|
||||||
|
.xll-module-theme .ar-add-btn { color: ${XLL_GREEN_DEEP}; }
|
||||||
|
.xll-module-theme .ar-mp-link { color: ${XLL_GREEN_DEEP}; }
|
||||||
|
.xll-module-theme .ar-form-control .ant-select-focused .ant-select-selector {
|
||||||
|
background: ${XLL_GREEN_SOFT} !important;
|
||||||
|
}
|
||||||
|
.xll-module-theme .ar-operate-foot .ant-btn-primary {
|
||||||
|
background: linear-gradient(135deg, ${XLL_GREEN} 0%, ${XLL_GREEN_DEEP} 100%) !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const IconFilter = () => (
|
const IconFilter = () => (
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={COLOR_PRIMARY_DEEP} strokeWidth="2" strokeLinecap="round">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={COLOR_PRIMARY_DEEP} strokeWidth="2" strokeLinecap="round">
|
||||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
|
||||||
@@ -3310,7 +3347,7 @@ const AnnualReviewPrdDoc = () => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Component = function () {
|
const AnnualReviewPanel = function AnnualReviewPanel({ embedded = false, theme = 'default', onBack, onOpenPrd }) {
|
||||||
const [mainTab, setMainTab] = useState('pending');
|
const [mainTab, setMainTab] = useState('pending');
|
||||||
const [searchPlate, setSearchPlate] = useState('');
|
const [searchPlate, setSearchPlate] = useState('');
|
||||||
const [plateKbOpen, setPlateKbOpen] = useState(false);
|
const [plateKbOpen, setPlateKbOpen] = useState(false);
|
||||||
@@ -3336,6 +3373,8 @@ const Component = function () {
|
|||||||
const [licenseForm, setLicenseForm] = useState({ ...EMPTY_LICENSE_FORM });
|
const [licenseForm, setLicenseForm] = useState({ ...EMPTY_LICENSE_FORM });
|
||||||
const [operateDrafts, setOperateDrafts] = useState(() => loadOperateDrafts());
|
const [operateDrafts, setOperateDrafts] = useState(() => loadOperateDrafts());
|
||||||
const licenseOcrTimerRef = useRef(null);
|
const licenseOcrTimerRef = useRef(null);
|
||||||
|
const themeClass = theme === 'xll' ? ' xll-module-theme' : '';
|
||||||
|
const embedStyles = `${PAGE_STYLE}${EMBED_STYLE}${theme === 'xll' ? XLL_THEME_PATCH : ''}`;
|
||||||
|
|
||||||
const resetOperateForms = () => {
|
const resetOperateForms = () => {
|
||||||
if (licenseOcrTimerRef.current) {
|
if (licenseOcrTimerRef.current) {
|
||||||
@@ -3431,6 +3470,22 @@ const Component = function () {
|
|||||||
setHistoryViewTask(null);
|
setHistoryViewTask(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!embedded || typeof window === 'undefined') return undefined;
|
||||||
|
window.__xllInspectionBack = () => {
|
||||||
|
if (historyViewTask) {
|
||||||
|
closeHistoryViewPage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (operateTask) {
|
||||||
|
closeOperatePage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return () => { delete window.__xllInspectionBack; };
|
||||||
|
}, [embedded, historyViewTask, operateTask]);
|
||||||
|
|
||||||
const setInspection = (key, val) => setInspectionForm((p) => ({ ...p, [key]: val }));
|
const setInspection = (key, val) => setInspectionForm((p) => ({ ...p, [key]: val }));
|
||||||
const setM2 = (key, val) => setM2Form((p) => ({ ...p, [key]: val }));
|
const setM2 = (key, val) => setM2Form((p) => ({ ...p, [key]: val }));
|
||||||
const setZb = (key, val) => setZbForm((p) => ({ ...p, [key]: val }));
|
const setZb = (key, val) => setZbForm((p) => ({ ...p, [key]: val }));
|
||||||
@@ -4078,92 +4133,85 @@ const Component = function () {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const phoneMain = (
|
||||||
<div className="ar-mini-root">
|
<>
|
||||||
<style>{PAGE_STYLE}</style>
|
{historyViewTask ? (
|
||||||
<div className="ar-phone">
|
renderHistoryDetailPage()
|
||||||
<MiniProgramChrome
|
) : operateTask ? (
|
||||||
title={historyViewTask ? '年审记录' : operateTask ? '年审操作' : '年审'}
|
renderOperatePage()
|
||||||
showBack={!!operateTask || !!historyViewTask}
|
) : (
|
||||||
onBack={historyViewTask ? closeHistoryViewPage : closeOperatePage}
|
<>
|
||||||
showPrdLink={!operateTask && !historyViewTask}
|
<div className="ar-tabs" role="tablist">
|
||||||
onPrdClick={() => setPrdOpen(true)}
|
<button
|
||||||
/>
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={mainTab === 'pending'}
|
||||||
|
className={`ar-tab${mainTab === 'pending' ? ' active' : ''}`}
|
||||||
|
onClick={() => setMainTab('pending')}
|
||||||
|
>
|
||||||
|
待处理
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={mainTab === 'history'}
|
||||||
|
className={`ar-tab${mainTab === 'history' ? ' active' : ''}`}
|
||||||
|
onClick={() => setMainTab('history')}
|
||||||
|
>
|
||||||
|
历史记录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{historyViewTask ? (
|
<div className="ar-search-row">
|
||||||
renderHistoryDetailPage()
|
<div
|
||||||
) : operateTask ? (
|
className="ar-search-box"
|
||||||
renderOperatePage()
|
role="button"
|
||||||
) : (
|
tabIndex={0}
|
||||||
<>
|
onClick={openPlateKeyboard}
|
||||||
<div className="ar-tabs" role="tablist">
|
onKeyDown={(e) => e.key === 'Enter' && openPlateKeyboard()}
|
||||||
<button
|
>
|
||||||
type="button"
|
{!searchPlate ? (
|
||||||
role="tab"
|
<span className="ar-search-placeholder" aria-hidden="true">
|
||||||
aria-selected={mainTab === 'pending'}
|
请输入车牌号
|
||||||
className={`ar-tab${mainTab === 'pending' ? ' active' : ''}`}
|
</span>
|
||||||
onClick={() => setMainTab('pending')}
|
) : null}
|
||||||
>
|
<input
|
||||||
待处理
|
readOnly
|
||||||
</button>
|
value={searchPlate}
|
||||||
<button
|
aria-label="车牌号搜索"
|
||||||
type="button"
|
onFocus={(e) => {
|
||||||
role="tab"
|
e.target.blur();
|
||||||
aria-selected={mainTab === 'history'}
|
openPlateKeyboard();
|
||||||
className={`ar-tab${mainTab === 'history' ? ' active' : ''}`}
|
}}
|
||||||
onClick={() => setMainTab('history')}
|
/>
|
||||||
>
|
|
||||||
历史记录
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" className="ar-filter-btn" aria-label="打开筛选" onClick={openFilter}>
|
||||||
|
<IconFilter />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="ar-search-row">
|
<div className="ar-list">
|
||||||
<div
|
{filteredTasks.length === 0 ? (
|
||||||
className="ar-search-box"
|
<div className="ar-empty">暂无符合条件的年审任务<br />请调整搜索或筛选条件</div>
|
||||||
role="button"
|
) : (
|
||||||
tabIndex={0}
|
filteredTasks.map(renderTaskCard)
|
||||||
onClick={openPlateKeyboard}
|
)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && openPlateKeyboard()}
|
</div>
|
||||||
>
|
</>
|
||||||
{!searchPlate ? (
|
)}
|
||||||
<span className="ar-search-placeholder" aria-hidden="true">
|
|
||||||
请输入车牌号
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
<input
|
|
||||||
readOnly
|
|
||||||
value={searchPlate}
|
|
||||||
aria-label="车牌号搜索"
|
|
||||||
onFocus={(e) => {
|
|
||||||
e.target.blur();
|
|
||||||
openPlateKeyboard();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button type="button" className="ar-filter-btn" aria-label="打开筛选" onClick={openFilter}>
|
|
||||||
<IconFilter />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="ar-list">
|
<PlateKeyboardPanel
|
||||||
{filteredTasks.length === 0 ? (
|
open={plateKbOpen && !operateTask && !historyViewTask}
|
||||||
<div className="ar-empty">暂无符合条件的年审任务<br />请调整搜索或筛选条件</div>
|
value={plateKbDraft}
|
||||||
) : (
|
onChange={setPlateKbDraft}
|
||||||
filteredTasks.map(renderTaskCard)
|
onConfirm={confirmPlateKeyboard}
|
||||||
)}
|
onClose={closePlateKeyboard}
|
||||||
</div>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
);
|
||||||
|
|
||||||
<PlateKeyboardPanel
|
|
||||||
open={plateKbOpen && !operateTask && !historyViewTask}
|
|
||||||
value={plateKbDraft}
|
|
||||||
onChange={setPlateKbDraft}
|
|
||||||
onConfirm={confirmPlateKeyboard}
|
|
||||||
onClose={closePlateKeyboard}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
const moduleOverlays = (
|
||||||
|
<>
|
||||||
<Drawer
|
<Drawer
|
||||||
title="筛选"
|
title="筛选"
|
||||||
placement="right"
|
placement="right"
|
||||||
@@ -4184,7 +4232,9 @@ const Component = function () {
|
|||||||
style={{
|
style={{
|
||||||
height: 46,
|
height: 46,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
background: `linear-gradient(135deg, ${COLOR_PRIMARY} 0%, ${COLOR_PRIMARY_DEEP} 100%)`,
|
background: theme === 'xll'
|
||||||
|
? `linear-gradient(135deg, ${XLL_GREEN} 0%, ${XLL_GREEN_DEEP} 100%)`
|
||||||
|
: `linear-gradient(135deg, ${COLOR_PRIMARY} 0%, ${COLOR_PRIMARY_DEEP} 100%)`,
|
||||||
border: 'none'
|
border: 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -4236,7 +4286,7 @@ const Component = function () {
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={prdOpen}
|
open={embedded ? false : prdOpen}
|
||||||
title="年审管理 · 产品需求说明"
|
title="年审管理 · 产品需求说明"
|
||||||
onCancel={() => setPrdOpen(false)}
|
onCancel={() => setPrdOpen(false)}
|
||||||
footer={[
|
footer={[
|
||||||
@@ -4244,7 +4294,10 @@ const Component = function () {
|
|||||||
key="ok"
|
key="ok"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => setPrdOpen(false)}
|
onClick={() => setPrdOpen(false)}
|
||||||
style={{ background: COLOR_PRIMARY_DEEP, borderColor: COLOR_PRIMARY_DEEP }}
|
style={{
|
||||||
|
background: theme === 'xll' ? XLL_GREEN_DEEP : COLOR_PRIMARY_DEEP,
|
||||||
|
borderColor: theme === 'xll' ? XLL_GREEN_DEEP : COLOR_PRIMARY_DEEP,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
知道了
|
知道了
|
||||||
</Button>
|
</Button>
|
||||||
@@ -4255,8 +4308,45 @@ const Component = function () {
|
|||||||
>
|
>
|
||||||
<AnnualReviewPrdDoc />
|
<AnnualReviewPrdDoc />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (embedded) {
|
||||||
|
return (
|
||||||
|
<div className={`ar-embed-root${themeClass}`}>
|
||||||
|
<style>{embedStyles}</style>
|
||||||
|
{phoneMain}
|
||||||
|
{moduleOverlays}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ar-mini-root">
|
||||||
|
<style>{PAGE_STYLE}</style>
|
||||||
|
<div className="ar-phone">
|
||||||
|
<MiniProgramChrome
|
||||||
|
title={historyViewTask ? '年审记录' : operateTask ? '年审操作' : '年审'}
|
||||||
|
showBack={!!operateTask || !!historyViewTask}
|
||||||
|
onBack={historyViewTask ? closeHistoryViewPage : closeOperatePage}
|
||||||
|
showPrdLink={!operateTask && !historyViewTask}
|
||||||
|
onPrdClick={() => (onOpenPrd ? onOpenPrd() : setPrdOpen(true))}
|
||||||
|
/>
|
||||||
|
<div className="ar-embed-root">{phoneMain}</div>
|
||||||
|
</div>
|
||||||
|
{moduleOverlays}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Component = function AnnualReviewMiniApp() {
|
||||||
|
return <AnnualReviewPanel embedded={false} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.Component = Component;
|
||||||
|
window.ONEOS_MP_EMBED = window.ONEOS_MP_EMBED || {};
|
||||||
|
window.ONEOS_MP_EMBED.AnnualReviewPanel = AnnualReviewPanel;
|
||||||
|
}
|
||||||
|
|
||||||
export default Component;
|
export default Component;
|
||||||
|
|||||||
1131
ONE-OS小程序/提车应收款-审批.jsx
Normal file
1131
ONE-OS小程序/提车应收款-审批.jsx
Normal file
File diff suppressed because it is too large
Load Diff
96
ONE-OS小程序/还车应结款-审批.jsx
Normal file
96
ONE-OS小程序/还车应结款-审批.jsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// 【重要】必须使用 const Component 作为组件变量名
|
||||||
|
// ONE-OS 小程序 - 还车应结款审批办理(参照 web 端还车应结款-查看,适配移动端)
|
||||||
|
|
||||||
|
// 独立预览页:完整审批能力已内嵌于「审批中心.jsx」,本页供 Axhub 单独打开预览。
|
||||||
|
|
||||||
|
const { useState, useMemo, useRef, useEffect } = React;
|
||||||
|
const moment = window.moment || window.dayjs;
|
||||||
|
|
||||||
|
const COLOR_PRIMARY = '#16D1A1';
|
||||||
|
const COLOR_PRIMARY_DEEP = '#00BFA5';
|
||||||
|
const COLOR_PRIMARY_SOFT = 'rgba(22, 209, 161, 0.12)';
|
||||||
|
const COLOR_TEXT = '#1D2129';
|
||||||
|
const COLOR_TEXT_SEC = '#4E5969';
|
||||||
|
const COLOR_MUTED = '#86909C';
|
||||||
|
const COLOR_LINE = '#E5E6EB';
|
||||||
|
const COLOR_BG = '#FFFFFF';
|
||||||
|
const COLOR_PAGE = '#F2F3F5';
|
||||||
|
const COLOR_SUCCESS = '#00B42A';
|
||||||
|
const COLOR_DANGER = '#F53F3F';
|
||||||
|
const COLOR_WARN = '#FF7D00';
|
||||||
|
const FONT_FAMILY = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", STHeiti, sans-serif';
|
||||||
|
|
||||||
|
const antd = (() => {
|
||||||
|
const raw = window.antd;
|
||||||
|
if (!raw) return {};
|
||||||
|
return raw.default && typeof raw.default === 'object' ? { ...raw, ...raw.default } : raw;
|
||||||
|
})();
|
||||||
|
const message = antd.message || { info: () => {}, success: () => {}, warning: () => {} };
|
||||||
|
const Drawer = antd.Drawer;
|
||||||
|
|
||||||
|
const formatMoney = (val) => {
|
||||||
|
const n = parseFloat(val);
|
||||||
|
if (Number.isNaN(n)) return val || '—';
|
||||||
|
return `¥${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||||
|
};
|
||||||
|
const formatYuan = (val) => {
|
||||||
|
const n = parseFloat(val);
|
||||||
|
if (Number.isNaN(n)) return '—';
|
||||||
|
return `${n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 元`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_TASK = {
|
||||||
|
bizNo: 'HC-2026-0418',
|
||||||
|
plateNo: '粤B58888F',
|
||||||
|
customerName: '嘉兴某某物流有限公司',
|
||||||
|
projectName: '嘉兴腾4.5T租赁',
|
||||||
|
pendingSettle: '927.50',
|
||||||
|
depositAmount: '5000.00',
|
||||||
|
refundTotal: '4072.50',
|
||||||
|
payTotal: '0.00',
|
||||||
|
actualRent: '0.00',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = function ReturnSettlementApproveStandalone() {
|
||||||
|
const task = window.__returnSettlementTask || DEFAULT_TASK;
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (window.__returnSettlementBack) window.__returnSettlementBack();
|
||||||
|
else message.info('返回审批中心(原型)');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="hc-mini-root">
|
||||||
|
<style>{`
|
||||||
|
.hc-mini-root { min-height:100dvh; background:linear-gradient(165deg,#e8ebef 0%,${COLOR_PAGE} 40%); display:flex; justify-content:center; padding:16px 12px 32px; font-family:${FONT_FAMILY}; }
|
||||||
|
.hc-phone { width:100%; max-width:390px; min-height:844px; background:${COLOR_PAGE}; border-radius:28px; overflow:hidden; box-shadow:0 24px 48px rgba(15,23,42,.14); display:flex; flex-direction:column; position:relative; }
|
||||||
|
.hc-chrome { background:${COLOR_BG}; border-bottom:1px solid rgba(0,0,0,.05); padding:44px 8px 0; }
|
||||||
|
.hc-nav { height:48px; display:flex; align-items:center; position:relative; }
|
||||||
|
.hc-nav-title { flex:1; text-align:center; font-size:17px; font-weight:700; }
|
||||||
|
.hc-back { width:40px; height:40px; border:none; background:transparent; cursor:pointer; }
|
||||||
|
.hc-tip { padding:24px; text-align:center; color:${COLOR_MUTED}; font-size:14px; line-height:1.6; }
|
||||||
|
`}</style>
|
||||||
|
<div className="hc-phone">
|
||||||
|
<div className="hc-chrome">
|
||||||
|
<div className="hc-nav">
|
||||||
|
<button type="button" className="hc-back" onClick={handleBack} aria-label="返回">←</button>
|
||||||
|
<div className="hc-nav-title">还车应结款审批</div>
|
||||||
|
<span style={{ width: 40 }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hc-tip">
|
||||||
|
完整还车应结款审批页(含结算明细、费用分组、审批操作)已内嵌于
|
||||||
|
<strong> 审批中心.jsx </strong>
|
||||||
|
,请从「我的待办 → 还车应结款 HC-2026-0418 → 去审批」进入体验。
|
||||||
|
<br /><br />
|
||||||
|
待结算总额:<strong style={{ color: '#8B5CF6' }}>{formatMoney(task.pendingSettle)}</strong>
|
||||||
|
<br />
|
||||||
|
{task.plateNo} · {task.customerName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') window.Component = Component;
|
||||||
|
export default Component;
|
||||||
Reference in New Issue
Block a user