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 = () => (
|
||||
<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" />
|
||||
@@ -3310,7 +3347,7 @@ const AnnualReviewPrdDoc = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const Component = function () {
|
||||
const AnnualReviewPanel = function AnnualReviewPanel({ embedded = false, theme = 'default', onBack, onOpenPrd }) {
|
||||
const [mainTab, setMainTab] = useState('pending');
|
||||
const [searchPlate, setSearchPlate] = useState('');
|
||||
const [plateKbOpen, setPlateKbOpen] = useState(false);
|
||||
@@ -3336,6 +3373,8 @@ const Component = function () {
|
||||
const [licenseForm, setLicenseForm] = useState({ ...EMPTY_LICENSE_FORM });
|
||||
const [operateDrafts, setOperateDrafts] = useState(() => loadOperateDrafts());
|
||||
const licenseOcrTimerRef = useRef(null);
|
||||
const themeClass = theme === 'xll' ? ' xll-module-theme' : '';
|
||||
const embedStyles = `${PAGE_STYLE}${EMBED_STYLE}${theme === 'xll' ? XLL_THEME_PATCH : ''}`;
|
||||
|
||||
const resetOperateForms = () => {
|
||||
if (licenseOcrTimerRef.current) {
|
||||
@@ -3431,6 +3470,22 @@ const Component = function () {
|
||||
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 setM2 = (key, val) => setM2Form((p) => ({ ...p, [key]: val }));
|
||||
const setZb = (key, val) => setZbForm((p) => ({ ...p, [key]: val }));
|
||||
@@ -4078,92 +4133,85 @@ const Component = function () {
|
||||
);
|
||||
};
|
||||
|
||||
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={() => setPrdOpen(true)}
|
||||
/>
|
||||
const phoneMain = (
|
||||
<>
|
||||
{historyViewTask ? (
|
||||
renderHistoryDetailPage()
|
||||
) : operateTask ? (
|
||||
renderOperatePage()
|
||||
) : (
|
||||
<>
|
||||
<div className="ar-tabs" role="tablist">
|
||||
<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 ? (
|
||||
renderHistoryDetailPage()
|
||||
) : operateTask ? (
|
||||
renderOperatePage()
|
||||
) : (
|
||||
<>
|
||||
<div className="ar-tabs" role="tablist">
|
||||
<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 className="ar-search-row">
|
||||
<div
|
||||
className="ar-search-box"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={openPlateKeyboard}
|
||||
onKeyDown={(e) => e.key === 'Enter' && openPlateKeyboard()}
|
||||
>
|
||||
{!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-search-row">
|
||||
<div
|
||||
className="ar-search-box"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={openPlateKeyboard}
|
||||
onKeyDown={(e) => e.key === 'Enter' && openPlateKeyboard()}
|
||||
>
|
||||
{!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">
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className="ar-empty">暂无符合条件的年审任务<br />请调整搜索或筛选条件</div>
|
||||
) : (
|
||||
filteredTasks.map(renderTaskCard)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="ar-list">
|
||||
{filteredTasks.length === 0 ? (
|
||||
<div className="ar-empty">暂无符合条件的年审任务<br />请调整搜索或筛选条件</div>
|
||||
) : (
|
||||
filteredTasks.map(renderTaskCard)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<PlateKeyboardPanel
|
||||
open={plateKbOpen && !operateTask && !historyViewTask}
|
||||
value={plateKbDraft}
|
||||
onChange={setPlateKbDraft}
|
||||
onConfirm={confirmPlateKeyboard}
|
||||
onClose={closePlateKeyboard}
|
||||
/>
|
||||
</div>
|
||||
<PlateKeyboardPanel
|
||||
open={plateKbOpen && !operateTask && !historyViewTask}
|
||||
value={plateKbDraft}
|
||||
onChange={setPlateKbDraft}
|
||||
onConfirm={confirmPlateKeyboard}
|
||||
onClose={closePlateKeyboard}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const moduleOverlays = (
|
||||
<>
|
||||
<Drawer
|
||||
title="筛选"
|
||||
placement="right"
|
||||
@@ -4184,7 +4232,9 @@ const Component = function () {
|
||||
style={{
|
||||
height: 46,
|
||||
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'
|
||||
}}
|
||||
>
|
||||
@@ -4236,7 +4286,7 @@ const Component = function () {
|
||||
</Drawer>
|
||||
|
||||
<Modal
|
||||
open={prdOpen}
|
||||
open={embedded ? false : prdOpen}
|
||||
title="年审管理 · 产品需求说明"
|
||||
onCancel={() => setPrdOpen(false)}
|
||||
footer={[
|
||||
@@ -4244,7 +4294,10 @@ const Component = function () {
|
||||
key="ok"
|
||||
type="primary"
|
||||
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>
|
||||
@@ -4255,8 +4308,45 @@ const Component = function () {
|
||||
>
|
||||
<AnnualReviewPrdDoc />
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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