merge: 合并远程审批中心,保留本地加氢订单/记录与数量统计并同步导航。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"updatedAt": "2026-07-14T15:10:00.000Z",
|
"updatedAt": "2026-07-16T01:03:00.000Z",
|
||||||
"prototypes": [
|
"prototypes": [
|
||||||
{
|
{
|
||||||
"id": "folder-1783875936186-27raza",
|
"id": "folder-1783875936186-27raza",
|
||||||
@@ -80,6 +80,37 @@
|
|||||||
"title": "工作台",
|
"title": "工作台",
|
||||||
"itemKey": "prototypes/oneos-web-workbench"
|
"itemKey": "prototypes/oneos-web-workbench"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "folder-oneos-approval",
|
||||||
|
"kind": "folder",
|
||||||
|
"title": "审批中心",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-initiated",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我发起的",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-initiated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-todo",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的待办",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-todo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-done",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的已办",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-cc",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的抄送",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-cc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "folder-1782874599304-roj8fd",
|
"id": "folder-1782874599304-roj8fd",
|
||||||
"kind": "folder",
|
"kind": "folder",
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ const STANDALONE_MODULES = [
|
|||||||
{ slug: 'oneos-web-h2-station-stats', title: '加氢站数量统计', skipCodegen: true },
|
{ slug: 'oneos-web-h2-station-stats', title: '加氢站数量统计', skipCodegen: true },
|
||||||
{ slug: 'vehicle-return-settlement', title: '还车应结款', skipCodegen: true },
|
{ slug: 'vehicle-return-settlement', title: '还车应结款', skipCodegen: true },
|
||||||
{ slug: 'vehicle-pickup-receivable', title: '提车应收款', skipCodegen: true },
|
{ slug: 'vehicle-pickup-receivable', title: '提车应收款', skipCodegen: true },
|
||||||
|
{ slug: 'oneos-web-approval', title: '审批中心', skipCodegen: true },
|
||||||
|
{ slug: 'oneos-web-approval-initiated', title: '我发起的', skipCodegen: true },
|
||||||
|
{ slug: 'oneos-web-approval-todo', title: '我的待办', skipCodegen: true },
|
||||||
|
{ slug: 'oneos-web-approval-done', title: '我的已办', skipCodegen: true },
|
||||||
|
{ slug: 'oneos-web-approval-cc', title: '我的抄送', skipCodegen: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
|
|||||||
57
src/common/oneos-web-approval/ApprovalApp.tsx
Normal file
57
src/common/oneos-web-approval/ApprovalApp.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import '../oneosWebLegacy/legacyGlobals';
|
||||||
|
import '../../prototypes/vehicle-management/style.css';import './styles/index.css';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { ConfigProvider } from 'antd';
|
||||||
|
import type { ApprovalTabKey } from './types';
|
||||||
|
import { ApprovalCenterPage } from './pages/ApprovalCenterPage';
|
||||||
|
|
||||||
|
const approvalTheme = {
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#165dff',
|
||||||
|
borderRadius: 8,
|
||||||
|
fontFamily:
|
||||||
|
'Inter, -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif',
|
||||||
|
colorBgLayout: '#f5f7fa',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Tabs: {
|
||||||
|
inkBarColor: '#165dff',
|
||||||
|
itemSelectedColor: '#165dff',
|
||||||
|
itemHoverColor: '#165dff',
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
controlHeight: 32,
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
controlHeight: 32,
|
||||||
|
},
|
||||||
|
Input: {
|
||||||
|
controlHeight: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ApprovalAppProps {
|
||||||
|
tabKey?: ApprovalTabKey;
|
||||||
|
pageTitle?: string;
|
||||||
|
showTabs?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApprovalApp({
|
||||||
|
tabKey,
|
||||||
|
pageTitle = '审批中心',
|
||||||
|
showTabs = false,
|
||||||
|
}: ApprovalAppProps) {
|
||||||
|
return (
|
||||||
|
<ConfigProvider theme={approvalTheme}>
|
||||||
|
<ApprovalCenterPage
|
||||||
|
tabKey={tabKey}
|
||||||
|
pageTitle={pageTitle}
|
||||||
|
showTabs={showTabs}
|
||||||
|
/>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApprovalApp;
|
||||||
96
src/common/oneos-web-approval/components/ApprovalCard.tsx
Normal file
96
src/common/oneos-web-approval/components/ApprovalCard.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import type { ApprovalCardItem } from '../types';
|
||||||
|
import {
|
||||||
|
formatStatusLabel,
|
||||||
|
getApprovalTypeColor,
|
||||||
|
getApprovalTypeLabel,
|
||||||
|
} from '../config/approvalTypeConfig';
|
||||||
|
|
||||||
|
export interface ApprovalCardProps {
|
||||||
|
item: ApprovalCardItem;
|
||||||
|
selected?: boolean;
|
||||||
|
onClick?: (item: ApprovalCardItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusClassName(status: ApprovalCardItem['status']): string {
|
||||||
|
if (status === 'approved') return 'ap-status ap-status--approved';
|
||||||
|
if (status === 'rejected') return 'ap-status ap-status--rejected';
|
||||||
|
return 'ap-status ap-status--pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApprovalCard({ item, selected = false, onClick }: ApprovalCardProps) {
|
||||||
|
const typeColor = getApprovalTypeColor(item.type);
|
||||||
|
const statusLabel = formatStatusLabel(item);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article
|
||||||
|
className={`ap-card${selected ? ' ap-card--selected' : ''}`}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => onClick?.(item)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
onClick?.(item);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="ap-card__header">
|
||||||
|
<span
|
||||||
|
className="ap-card__type"
|
||||||
|
style={{ backgroundColor: `${typeColor}14`, color: typeColor, borderColor: `${typeColor}33` }}
|
||||||
|
>
|
||||||
|
{item.typeLabel ?? getApprovalTypeLabel(item.type)}
|
||||||
|
</span>
|
||||||
|
<span className={statusClassName(item.status)}>{statusLabel}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-card__body">
|
||||||
|
<h3 className="ap-card__title">{item.title}</h3>
|
||||||
|
{item.subtitle ? <p className="ap-card__subtitle">{item.subtitle}</p> : null}
|
||||||
|
|
||||||
|
{item.keyFacts && item.keyFacts.length > 0 ? (
|
||||||
|
<dl className="ap-card__facts">
|
||||||
|
{item.keyFacts.map((fact) => (
|
||||||
|
<div key={`${item.id}-${fact.label}`} className="ap-card__fact">
|
||||||
|
<dt>{fact.label}</dt>
|
||||||
|
<dd className={fact.emphasis ? 'ap-card__fact-value--emphasis' : undefined}>
|
||||||
|
{fact.value}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{item.risks && item.risks.length > 0 ? (
|
||||||
|
<div className="ap-card__risks">
|
||||||
|
{item.risks.map((risk) => (
|
||||||
|
<span
|
||||||
|
key={`${item.id}-${risk.label}`}
|
||||||
|
className={`ap-card__risk ap-card__risk--${risk.level ?? 'default'}`}
|
||||||
|
>
|
||||||
|
{risk.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-card__footer">
|
||||||
|
<div className="ap-card__footer-meta">
|
||||||
|
{item.showInitiatorInFooter ? (
|
||||||
|
<span className="ap-card__footer-text">发起人 {item.initiatedBy}</span>
|
||||||
|
) : null}
|
||||||
|
<span className="ap-card__footer-text">
|
||||||
|
{item.footerText ?? `发起时间 ${item.initiatedAt}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="ap-card__action">
|
||||||
|
查看详情
|
||||||
|
<RightOutlined />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Empty, Spin } from 'antd';
|
||||||
|
import type { ApprovalCardItem } from '../types';
|
||||||
|
import { ApprovalCard } from './ApprovalCard';
|
||||||
|
|
||||||
|
export interface ApprovalCardListProps {
|
||||||
|
items: ApprovalCardItem[];
|
||||||
|
loading?: boolean;
|
||||||
|
selectedId?: string | null;
|
||||||
|
onItemClick?: (item: ApprovalCardItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApprovalCardList({
|
||||||
|
items,
|
||||||
|
loading = false,
|
||||||
|
selectedId,
|
||||||
|
onItemClick,
|
||||||
|
}: ApprovalCardListProps) {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="ap-list ap-list--loading">
|
||||||
|
<Spin tip="加载中..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="ap-list ap-list--empty">
|
||||||
|
<Empty description="暂无审批事项" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ap-list">
|
||||||
|
{items.map((item) => (
|
||||||
|
<ApprovalCard
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
selected={item.id === selectedId}
|
||||||
|
onClick={onItemClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Empty,
|
||||||
|
Space,
|
||||||
|
Tabs,
|
||||||
|
Timeline,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
CheckCircleOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import type { ApprovalCardItem } from '../types';
|
||||||
|
import {
|
||||||
|
formatStatusLabel,
|
||||||
|
getApprovalTypeLabel,
|
||||||
|
} from '../config/approvalTypeConfig';
|
||||||
|
import { InsuranceComparisonDetail } from './InsuranceComparisonDetail';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
export interface ApprovalDetailPlaceholderProps {
|
||||||
|
item: ApprovalCardItem | null;
|
||||||
|
onRefresh?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusBadgeClass(status: ApprovalCardItem['status']): string {
|
||||||
|
if (status === 'approved') return 'ap-detail__badge ap-detail__badge--approved';
|
||||||
|
if (status === 'rejected') return 'ap-detail__badge ap-detail__badge--rejected';
|
||||||
|
return 'ap-detail__badge ap-detail__badge--pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOCK_TIMELINE = [
|
||||||
|
{ role: '发起人', name: '提交申请', time: '', color: 'blue' as const },
|
||||||
|
{ role: '部门主管', name: '审批通过', time: '2026-07-08 10:30', color: 'green' as const },
|
||||||
|
{ role: '财务审核', name: '审批中', time: '', color: 'gray' as const },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function ApprovalDetailPlaceholder({ item, onRefresh }: ApprovalDetailPlaceholderProps) {
|
||||||
|
const [activeTab, setActiveTab] = useState('detail');
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return (
|
||||||
|
<div className="ap-detail ap-detail--empty">
|
||||||
|
<Empty description="请从左侧选择一条审批事项" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.typeLabel === '保险比价采购') {
|
||||||
|
return <InsuranceComparisonDetail item={item} onRefresh={onRefresh} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusLabel = formatStatusLabel(item);
|
||||||
|
const typeLabel = getApprovalTypeLabel(item.type);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ap-detail">
|
||||||
|
<div className="ap-detail__toolbar">
|
||||||
|
<Space size={8}>
|
||||||
|
<Text type="secondary">编号:</Text>
|
||||||
|
<Text copyable={{ icon: <CopyOutlined />, text: item.id }}>{item.id}</Text>
|
||||||
|
</Space>
|
||||||
|
<Button type="text" icon={<ReloadOutlined />} onClick={onRefresh}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-detail__header">
|
||||||
|
<div className="ap-detail__header-main">
|
||||||
|
<Title level={4} className="ap-detail__title">
|
||||||
|
{item.title}
|
||||||
|
</Title>
|
||||||
|
<span className={statusBadgeClass(item.status)}>{statusLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div className="ap-detail__meta">
|
||||||
|
<Space size={16} wrap>
|
||||||
|
<Space size={6}>
|
||||||
|
<Avatar size={24} icon={<UserOutlined />} />
|
||||||
|
<Text>{item.initiatedBy}</Text>
|
||||||
|
</Space>
|
||||||
|
<Space size={6}>
|
||||||
|
<Text type="secondary">流程分类</Text>
|
||||||
|
<Text>{typeLabel}</Text>
|
||||||
|
</Space>
|
||||||
|
<Space size={6}>
|
||||||
|
<ClockCircleOutlined style={{ color: '#8c8c8c' }} />
|
||||||
|
<Text type="secondary">提交时间</Text>
|
||||||
|
<Text>{item.initiatedAt}</Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={setActiveTab}
|
||||||
|
className="ap-detail__tabs"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'detail',
|
||||||
|
label: '审批详情',
|
||||||
|
children: (
|
||||||
|
<div className="ap-detail__content">
|
||||||
|
{item.subtitle ? (
|
||||||
|
<p className="ap-detail__subtitle">{item.subtitle}</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{item.keyFacts && item.keyFacts.length > 0 ? (
|
||||||
|
<dl className="ap-detail__facts">
|
||||||
|
{item.keyFacts.map((fact) => (
|
||||||
|
<div key={fact.label} className="ap-detail__fact">
|
||||||
|
<dt>{fact.label}</dt>
|
||||||
|
<dd className={fact.emphasis ? 'ap-detail__fact-value--emphasis' : undefined}>
|
||||||
|
{fact.value}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{item.risks && item.risks.length > 0 ? (
|
||||||
|
<div className="ap-detail__risks">
|
||||||
|
{item.risks.map((risk) => (
|
||||||
|
<span
|
||||||
|
key={risk.label}
|
||||||
|
className={`ap-card__risk ap-card__risk--${risk.level ?? 'default'}`}
|
||||||
|
>
|
||||||
|
{risk.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="ap-detail__timeline-section">
|
||||||
|
<Text strong className="ap-detail__section-title">
|
||||||
|
审批进度
|
||||||
|
</Text>
|
||||||
|
<Timeline
|
||||||
|
className="ap-detail__timeline"
|
||||||
|
items={MOCK_TIMELINE.map((step, index) => ({
|
||||||
|
color: step.color,
|
||||||
|
dot:
|
||||||
|
step.color === 'green' ? (
|
||||||
|
<CheckCircleOutlined style={{ fontSize: 14 }} />
|
||||||
|
) : undefined,
|
||||||
|
children: (
|
||||||
|
<div className="ap-detail__timeline-item">
|
||||||
|
<div className="ap-detail__timeline-head">
|
||||||
|
<Text strong>
|
||||||
|
{step.role}
|
||||||
|
{step.name ? ` · ${step.name}` : ''}
|
||||||
|
</Text>
|
||||||
|
{step.time ? (
|
||||||
|
<Text type="secondary" className="ap-detail__timeline-time">
|
||||||
|
{step.time}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{index === 0 ? (
|
||||||
|
<Text type="secondary">{item.initiatedBy}</Text>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'flow',
|
||||||
|
label: '审批流程图',
|
||||||
|
children: (
|
||||||
|
<div className="ap-detail__flow-placeholder">
|
||||||
|
<Empty description="流程图占位,后续接入 BPM 流程引擎" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="ap-detail__actions">
|
||||||
|
<Button>评论</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Empty,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tabs,
|
||||||
|
Tag,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import {
|
||||||
|
ClockCircleOutlined,
|
||||||
|
CopyOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import type { ApprovalCardItem } from '../types';
|
||||||
|
import { formatStatusLabel } from '../config/approvalTypeConfig';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
export interface InsuranceComparisonDetailProps {
|
||||||
|
item: ApprovalCardItem;
|
||||||
|
onRefresh?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFact(item: ApprovalCardItem, label: string): string {
|
||||||
|
return item.keyFacts?.find((fact) => fact.label === label)?.value ?? '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VehicleRow {
|
||||||
|
key: string;
|
||||||
|
plateNo: string;
|
||||||
|
vin: string;
|
||||||
|
insuranceType: string;
|
||||||
|
latestPaymentDate: string;
|
||||||
|
finalQuote: string;
|
||||||
|
status: string;
|
||||||
|
approver: string;
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuoteRow {
|
||||||
|
key: string;
|
||||||
|
company: string;
|
||||||
|
amount: string;
|
||||||
|
isFinal: boolean;
|
||||||
|
remark: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildComparisonNo(item: ApprovalCardItem): string {
|
||||||
|
const datePart = item.initiatedAt.replace(/[-: ]/g, '').slice(0, 8);
|
||||||
|
const seq = item.id.replace(/\D/g, '').padStart(4, '0');
|
||||||
|
return `BXBJ${datePart}${seq}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSummaryRows(item: ApprovalCardItem, comparisonNo: string) {
|
||||||
|
const finalQuote = getFact(item, '最终报价').replace('¥', '');
|
||||||
|
const insuranceType = getFact(item, '采购险种');
|
||||||
|
const companyCount = getFact(item, '保险公司数量');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ label: '比价单号', value: comparisonNo },
|
||||||
|
{ label: '采购状态', value: <Tag color="processing">审批中</Tag> },
|
||||||
|
{ label: '创建人', value: item.initiatedBy },
|
||||||
|
{ label: '创建时间', value: item.initiatedAt },
|
||||||
|
{ label: '车辆数', value: '1' },
|
||||||
|
{ label: '险种数', value: '1' },
|
||||||
|
{ label: '已确认报价数', value: '1' },
|
||||||
|
{ label: '确认报价总额', value: finalQuote },
|
||||||
|
{ label: '当前审批人', value: item.currentApprover ?? '-' },
|
||||||
|
{ label: '保险公司数量', value: companyCount },
|
||||||
|
{ label: '采购险种', value: insuranceType },
|
||||||
|
{ label: '中选保险公司', value: item.subtitle?.replace(/^中选:/, '') ?? '-' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVehicleRows(item: ApprovalCardItem): VehicleRow[] {
|
||||||
|
const finalQuote = getFact(item, '最终报价').replace('¥', '');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
plateNo: item.title,
|
||||||
|
vin: '-',
|
||||||
|
insuranceType: getFact(item, '采购险种'),
|
||||||
|
latestPaymentDate: getFact(item, '最晚付费日'),
|
||||||
|
finalQuote,
|
||||||
|
status: '审批中',
|
||||||
|
approver: item.currentApprover ?? '-',
|
||||||
|
remark: item.subtitle ?? '-',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildQuoteRows(item: ApprovalCardItem): QuoteRow[] {
|
||||||
|
const winner = item.subtitle?.replace(/^中选:/, '') ?? '国元农业保险上海分公司';
|
||||||
|
const finalAmount = getFact(item, '最终报价').replace('¥', '');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ key: '1', company: '国任财产保险股份有限公司广东分公司', amount: '111.00', isFinal: false, remark: '-' },
|
||||||
|
{ key: '2', company: winner, amount: finalAmount, isFinal: true, remark: '-' },
|
||||||
|
{ key: '3', company: '中国平安财产保险股份有限公司上海分公司', amount: '888.00', isFinal: false, remark: '-' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InsuranceComparisonDetail({ item, onRefresh }: InsuranceComparisonDetailProps) {
|
||||||
|
const [activeTab, setActiveTab] = useState('detail');
|
||||||
|
const comparisonNo = buildComparisonNo(item);
|
||||||
|
const statusLabel = formatStatusLabel(item);
|
||||||
|
const typeLabel = item.typeLabel ?? '保险比价采购';
|
||||||
|
const summaryRows = buildSummaryRows(item, comparisonNo);
|
||||||
|
const vehicleRows = buildVehicleRows(item);
|
||||||
|
const quoteRows = buildQuoteRows(item);
|
||||||
|
|
||||||
|
const vehicleColumns: ColumnsType<VehicleRow> = [
|
||||||
|
{ title: '车牌号', dataIndex: 'plateNo', width: 110 },
|
||||||
|
{ title: 'VIN', dataIndex: 'vin', width: 120 },
|
||||||
|
{ title: '险种', dataIndex: 'insuranceType', width: 90 },
|
||||||
|
{ title: '最晚付费日', dataIndex: 'latestPaymentDate', width: 120 },
|
||||||
|
{ title: '最终报价', dataIndex: 'finalQuote', width: 100 },
|
||||||
|
{
|
||||||
|
title: '采购状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
width: 90,
|
||||||
|
render: (value: string) => <Tag color="processing">{value}</Tag>,
|
||||||
|
},
|
||||||
|
{ title: '当前审批人', dataIndex: 'approver', width: 120 },
|
||||||
|
{ title: '备注', dataIndex: 'remark', ellipsis: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const quoteColumns: ColumnsType<QuoteRow> = [
|
||||||
|
{ title: '保险公司', dataIndex: 'company', ellipsis: true },
|
||||||
|
{ title: '报价金额', dataIndex: 'amount', width: 110 },
|
||||||
|
{
|
||||||
|
title: '最终',
|
||||||
|
dataIndex: 'isFinal',
|
||||||
|
width: 72,
|
||||||
|
render: (isFinal: boolean) =>
|
||||||
|
isFinal ? <Tag color="success">是</Tag> : <Text type="secondary">-</Text>,
|
||||||
|
},
|
||||||
|
{ title: '备注', dataIndex: 'remark', width: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ap-detail ap-detail--insurance">
|
||||||
|
<div className="ap-detail__toolbar">
|
||||||
|
<Space size={8}>
|
||||||
|
<Text type="secondary">编号:</Text>
|
||||||
|
<Text copyable={{ icon: <CopyOutlined />, text: comparisonNo }}>{comparisonNo}</Text>
|
||||||
|
</Space>
|
||||||
|
<Button type="text" icon={<ReloadOutlined />} onClick={onRefresh}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-detail__header">
|
||||||
|
<div className="ap-detail__header-main">
|
||||||
|
<Title level={4} className="ap-detail__title">
|
||||||
|
{typeLabel}审批-{comparisonNo}
|
||||||
|
</Title>
|
||||||
|
<span className="ap-detail__badge ap-detail__badge--pending">{statusLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div className="ap-detail__meta">
|
||||||
|
<Space size={16} wrap>
|
||||||
|
<Space size={6}>
|
||||||
|
<Avatar size={24} icon={<UserOutlined />} />
|
||||||
|
<Text>{item.initiatedBy}</Text>
|
||||||
|
</Space>
|
||||||
|
<Space size={6}>
|
||||||
|
<Text type="secondary">流程分类</Text>
|
||||||
|
<Text>{typeLabel}</Text>
|
||||||
|
</Space>
|
||||||
|
<Space size={6}>
|
||||||
|
<ClockCircleOutlined style={{ color: '#8c8c8c' }} />
|
||||||
|
<Text type="secondary">提交时间</Text>
|
||||||
|
<Text>{item.initiatedAt}</Text>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={setActiveTab}
|
||||||
|
className="ap-detail__tabs"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'detail',
|
||||||
|
label: '审批详情',
|
||||||
|
children: (
|
||||||
|
<div className="ap-detail__content">
|
||||||
|
<table className="ap-insurance-summary">
|
||||||
|
<tbody>
|
||||||
|
{Array.from({ length: Math.ceil(summaryRows.length / 2) }).map((_, rowIndex) => {
|
||||||
|
const left = summaryRows[rowIndex * 2];
|
||||||
|
const right = summaryRows[rowIndex * 2 + 1];
|
||||||
|
return (
|
||||||
|
<tr key={left.label}>
|
||||||
|
<th>{left.label}</th>
|
||||||
|
<td>{left.value}</td>
|
||||||
|
{right ? (
|
||||||
|
<>
|
||||||
|
<th>{right.label}</th>
|
||||||
|
<td>{right.value}</td>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<th />
|
||||||
|
<td />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div className="ap-insurance-table-section">
|
||||||
|
<Text strong className="ap-detail__section-title">
|
||||||
|
车辆明细
|
||||||
|
</Text>
|
||||||
|
<Table
|
||||||
|
className="ap-insurance-table"
|
||||||
|
columns={vehicleColumns}
|
||||||
|
dataSource={vehicleRows}
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: () => (
|
||||||
|
<Table
|
||||||
|
className="ap-insurance-quote-table"
|
||||||
|
columns={quoteColumns}
|
||||||
|
dataSource={quoteRows}
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
defaultExpandedRowKeys: ['1'],
|
||||||
|
}}
|
||||||
|
scroll={{ x: 900 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'flow',
|
||||||
|
label: '审批流程图',
|
||||||
|
children: (
|
||||||
|
<div className="ap-detail__flow-placeholder">
|
||||||
|
<Empty description="流程图占位,后续接入 BPM 流程引擎" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="ap-detail__actions">
|
||||||
|
<Button>评论</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
71
src/common/oneos-web-approval/config/approvalTypeConfig.ts
Normal file
71
src/common/oneos-web-approval/config/approvalTypeConfig.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import type { ApprovalCardItem, ApprovalStatus, ApprovalTypeKey, StatusFilterValue } from '../types';
|
||||||
|
|
||||||
|
export interface ApprovalTypeOption {
|
||||||
|
value: ApprovalTypeKey;
|
||||||
|
label: string;
|
||||||
|
shortLabel: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const APPROVAL_TYPE_CONFIG: Record<ApprovalTypeKey, ApprovalTypeOption> = {
|
||||||
|
parking: { value: 'parking', label: '停车场', shortLabel: '停车场', color: '#6366f1' },
|
||||||
|
vehicle_procurement: { value: 'vehicle_procurement', label: '车辆采购', shortLabel: '采购', color: '#8b5cf6' },
|
||||||
|
lease: { value: 'lease', label: '租赁合同', shortLabel: '租赁', color: '#2563eb' },
|
||||||
|
charter: { value: 'charter', label: '包车合同', shortLabel: '包车', color: '#0ea5e9' },
|
||||||
|
transfer: { value: 'transfer', label: '调拨', shortLabel: '调拨', color: '#14b8a6' },
|
||||||
|
vehicle_change: { value: 'vehicle_change', label: '异动', shortLabel: '异动', color: '#f59e0b' },
|
||||||
|
replacement: { value: 'replacement', label: '替换车', shortLabel: '替换', color: '#f97316' },
|
||||||
|
delivery: { value: 'delivery', label: '交车', shortLabel: '交车', color: '#10b981' },
|
||||||
|
return_settlement: { value: 'return_settlement', label: '还车应结款', shortLabel: '还车结款', color: '#ef4444' },
|
||||||
|
pickup_receivable: { value: 'pickup_receivable', label: '提车应收款', shortLabel: '提车应收', color: '#ec4899' },
|
||||||
|
billing: { value: 'billing', label: '租赁账单', shortLabel: '账单', color: '#a855f7' },
|
||||||
|
insurance: { value: 'insurance', label: '保险采购', shortLabel: '保险', color: '#22c55e' },
|
||||||
|
supplier: { value: 'supplier', label: '供应商准入', shortLabel: '供应商', color: '#64748b' },
|
||||||
|
customer_risk: { value: 'customer_risk', label: '客户风险标签', shortLabel: '风险标签', color: '#dc2626' },
|
||||||
|
third_party_return: { value: 'third_party_return', label: '三方退租', shortLabel: '退租', color: '#d97706' },
|
||||||
|
contract_template: { value: 'contract_template', label: '合同模板', shortLabel: '模板', color: '#7c3aed' },
|
||||||
|
annual_inspection: { value: 'annual_inspection', label: '年审', shortLabel: '年审', color: '#0891b2' },
|
||||||
|
finance_payment: { value: 'finance_payment', label: '付款申请', shortLabel: '付款', color: '#be185d' },
|
||||||
|
clearing: { value: 'clearing', label: '清分结算', shortLabel: '清分', color: '#4f46e5' },
|
||||||
|
energy_account: { value: 'energy_account', label: '能源账户', shortLabel: '能源', color: '#059669' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APPROVAL_TYPE_OPTIONS = Object.values(APPROVAL_TYPE_CONFIG);
|
||||||
|
|
||||||
|
export const STATUS_OPTIONS: { value: StatusFilterValue; label: string }[] = [
|
||||||
|
{ value: 'all', label: '全部' },
|
||||||
|
{ value: 'pending', label: '审批中' },
|
||||||
|
{ value: 'approved', label: '已通过' },
|
||||||
|
{ value: 'rejected', label: '已驳回' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const STATUS_LABEL_MAP: Record<ApprovalStatus, string> = {
|
||||||
|
pending: '审批中',
|
||||||
|
processing: '审批中',
|
||||||
|
approved: '已通过',
|
||||||
|
rejected: '已驳回',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getApprovalTypeLabel(type: ApprovalTypeKey): string {
|
||||||
|
return APPROVAL_TYPE_CONFIG[type]?.label ?? type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApprovalTypeColor(type: ApprovalTypeKey): string {
|
||||||
|
return APPROVAL_TYPE_CONFIG[type]?.color ?? '#64748b';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatStatusLabel(item: Pick<ApprovalCardItem, 'status' | 'currentApprover'>): string {
|
||||||
|
const base = STATUS_LABEL_MAP[item.status] ?? item.status;
|
||||||
|
if ((item.status === 'pending' || item.status === 'processing') && item.currentApprover) {
|
||||||
|
return `审批中:${item.currentApprover}`;
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchesStatusFilter(status: ApprovalStatus, filter: StatusFilterValue): boolean {
|
||||||
|
if (filter === 'all') return true;
|
||||||
|
if (filter === 'pending') return status === 'pending' || status === 'processing';
|
||||||
|
if (filter === 'approved') return status === 'approved';
|
||||||
|
if (filter === 'rejected') return status === 'rejected';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
50
src/common/oneos-web-approval/data/mockApprovalCases.ts
Normal file
50
src/common/oneos-web-approval/data/mockApprovalCases.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { ApprovalCardItem } from '../types';
|
||||||
|
import { CURRENT_USER } from '../types';
|
||||||
|
|
||||||
|
export const MOCK_APPROVAL_CASES: ApprovalCardItem[] = [
|
||||||
|
{
|
||||||
|
id: 'ac-22',
|
||||||
|
type: 'lease',
|
||||||
|
typeLabel: '合同审批',
|
||||||
|
status: 'rejected',
|
||||||
|
listTab: 'initiated',
|
||||||
|
title: '杭州迅达运输有限公司',
|
||||||
|
subtitle: '杭州城配项目',
|
||||||
|
keyFacts: [
|
||||||
|
{ label: '租赁车辆数', value: '5 辆' },
|
||||||
|
{ label: '租金及服务费合计', value: '192000.00 元' },
|
||||||
|
{ label: '保证金总额', value: '40000.00 元' },
|
||||||
|
{ label: '氢气预付款金额', value: '20000.00 元' },
|
||||||
|
],
|
||||||
|
showInitiatorInFooter: true,
|
||||||
|
initiatedBy: CURRENT_USER,
|
||||||
|
initiatedAt: '2026-06-10 14:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ac-23',
|
||||||
|
type: 'insurance',
|
||||||
|
typeLabel: '保险比价采购',
|
||||||
|
status: 'processing',
|
||||||
|
listTab: 'initiated',
|
||||||
|
title: '沪A51676F',
|
||||||
|
subtitle: '中选:国元农业保险上海分公司',
|
||||||
|
keyFacts: [
|
||||||
|
{ label: '采购险种', value: '商业险' },
|
||||||
|
{ label: '最终报价', value: '¥1000.00' },
|
||||||
|
{ label: '保险公司数量', value: '3家' },
|
||||||
|
{ label: '最晚付费日', value: '2026-08-09' },
|
||||||
|
],
|
||||||
|
currentApprover: '财务部-赵总',
|
||||||
|
showInitiatorInFooter: true,
|
||||||
|
initiatedBy: CURRENT_USER,
|
||||||
|
initiatedAt: '2026-06-10 14:00',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function filterCasesByTab(
|
||||||
|
cases: ApprovalCardItem[],
|
||||||
|
tabKey?: string,
|
||||||
|
): ApprovalCardItem[] {
|
||||||
|
if (!tabKey) return cases;
|
||||||
|
return cases.filter((item) => item.listTab === tabKey);
|
||||||
|
}
|
||||||
156
src/common/oneos-web-approval/pages/ApprovalCenterPage.tsx
Normal file
156
src/common/oneos-web-approval/pages/ApprovalCenterPage.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { Input, Select, Tabs } from 'antd';
|
||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import type { ApprovalCardItem, ApprovalListTab, ApprovalStatus } from '../types';
|
||||||
|
import { ApprovalCardList } from '../components/ApprovalCardList';
|
||||||
|
import { ApprovalDetailPlaceholder } from '../components/ApprovalDetailPlaceholder';
|
||||||
|
import {
|
||||||
|
filterCasesByTab,
|
||||||
|
MOCK_APPROVAL_CASES,
|
||||||
|
} from '../data/mockApprovalCases';
|
||||||
|
import {
|
||||||
|
APPROVAL_TYPE_OPTIONS,
|
||||||
|
formatStatusLabel,
|
||||||
|
} from '../config/approvalTypeConfig';
|
||||||
|
|
||||||
|
const TAB_ITEMS: { key: ApprovalListTab; label: string }[] = [
|
||||||
|
{ key: 'todo', label: '我的待办' },
|
||||||
|
{ key: 'initiated', label: '我发起的' },
|
||||||
|
{ key: 'done', label: '我的已办' },
|
||||||
|
{ key: 'cc', label: '我的抄送' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const STATUS_OPTIONS: { value: ApprovalStatus | 'all'; label: string }[] = [
|
||||||
|
{ value: 'all', label: '全部状态' },
|
||||||
|
{ value: 'pending', label: '审批中' },
|
||||||
|
{ value: 'approved', label: '已通过' },
|
||||||
|
{ value: 'rejected', label: '已驳回' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ApprovalCenterPageProps {
|
||||||
|
tabKey?: ApprovalListTab;
|
||||||
|
pageTitle?: string;
|
||||||
|
showTabs?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApprovalCenterPage({
|
||||||
|
tabKey = 'todo',
|
||||||
|
pageTitle,
|
||||||
|
showTabs = false,
|
||||||
|
}: ApprovalCenterPageProps) {
|
||||||
|
const [activeTab, setActiveTab] = useState<ApprovalListTab>(tabKey);
|
||||||
|
const [statusFilter, setStatusFilter] = useState<ApprovalStatus | 'all'>('all');
|
||||||
|
const [typeFilter, setTypeFilter] = useState<string>('all');
|
||||||
|
const [keyword, setKeyword] = useState('');
|
||||||
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveTab(tabKey);
|
||||||
|
}, [tabKey]);
|
||||||
|
|
||||||
|
const tabItems = useMemo(() => {
|
||||||
|
let list = filterCasesByTab(MOCK_APPROVAL_CASES, activeTab);
|
||||||
|
|
||||||
|
if (statusFilter !== 'all') {
|
||||||
|
list = list.filter((item) => item.status === statusFilter);
|
||||||
|
}
|
||||||
|
if (typeFilter !== 'all') {
|
||||||
|
list = list.filter((item) => item.type === typeFilter);
|
||||||
|
}
|
||||||
|
if (keyword.trim()) {
|
||||||
|
const q = keyword.trim().toLowerCase();
|
||||||
|
list = list.filter(
|
||||||
|
(item) =>
|
||||||
|
item.title.toLowerCase().includes(q) ||
|
||||||
|
(item.subtitle?.toLowerCase().includes(q) ?? false) ||
|
||||||
|
item.initiatedBy.toLowerCase().includes(q) ||
|
||||||
|
formatStatusLabel(item).toLowerCase().includes(q),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}, [activeTab, statusFilter, typeFilter, keyword]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabItems.length === 0) {
|
||||||
|
setSelectedId(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selectedId || !tabItems.some((item) => item.id === selectedId)) {
|
||||||
|
setSelectedId(tabItems[0].id);
|
||||||
|
}
|
||||||
|
}, [tabItems, selectedId]);
|
||||||
|
|
||||||
|
const selectedItem = useMemo(
|
||||||
|
() => tabItems.find((item) => item.id === selectedId) ?? null,
|
||||||
|
[tabItems, selectedId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleItemClick = (item: ApprovalCardItem) => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentTabMeta = TAB_ITEMS.find((t) => t.key === activeTab);
|
||||||
|
const displayTitle = pageTitle ?? currentTabMeta?.label ?? '审批中心';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ap-page">
|
||||||
|
{showTabs ? (
|
||||||
|
<div className="ap-page__tabs-bar">
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={(key) => setActiveTab(key as ApprovalListTab)}
|
||||||
|
items={TAB_ITEMS.map((t) => ({ key: t.key, label: t.label }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="ap-page__shell">
|
||||||
|
<aside className="ap-page__sidebar">
|
||||||
|
<div className="ap-sidebar__head">
|
||||||
|
<h1 className="ap-sidebar__title">{displayTitle}</h1>
|
||||||
|
<p className="ap-sidebar__desc">按状态、流程类型与关键词筛选审批事项</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-filter ap-filter--sidebar">
|
||||||
|
<Select
|
||||||
|
className="ap-filter__control"
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={setStatusFilter}
|
||||||
|
options={STATUS_OPTIONS}
|
||||||
|
placeholder="审批状态"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
className="ap-filter__control"
|
||||||
|
value={typeFilter}
|
||||||
|
onChange={setTypeFilter}
|
||||||
|
options={[{ value: 'all', label: '全部类型' }, ...APPROVAL_TYPE_OPTIONS]}
|
||||||
|
placeholder="流程类型"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
className="ap-filter__search"
|
||||||
|
allowClear
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
placeholder="关键词搜索"
|
||||||
|
value={keyword}
|
||||||
|
onChange={(e) => setKeyword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-sidebar__list">
|
||||||
|
<ApprovalCardList
|
||||||
|
items={tabItems}
|
||||||
|
selectedId={selectedId}
|
||||||
|
onItemClick={handleItemClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ap-sidebar__foot">共 {tabItems.length} 条</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main className="ap-page__detail">
|
||||||
|
<ApprovalDetailPlaceholder item={selectedItem} />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
603
src/common/oneos-web-approval/styles/index.css
Normal file
603
src/common/oneos-web-approval/styles/index.css
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
.ap-page {
|
||||||
|
--ap-primary: #165dff;
|
||||||
|
--ap-primary-soft: rgba(22, 93, 255, 0.08);
|
||||||
|
--ap-border: #e5e7eb;
|
||||||
|
--ap-surface: #ffffff;
|
||||||
|
--ap-muted: #64748b;
|
||||||
|
--ap-text: #0f172a;
|
||||||
|
--ap-radius: 12px;
|
||||||
|
--ap-shadow: 0 1px 2px rgba(15, 23, 42, 0.06), 0 4px 16px rgba(15, 23, 42, 0.04);
|
||||||
|
--ap-sidebar-width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--vm-bg, #f5f7fa);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__tabs-bar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 12px 20px 0;
|
||||||
|
background: var(--ap-surface);
|
||||||
|
border-bottom: 1px solid var(--ap-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__tabs-bar .ant-tabs-nav {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__shell {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: var(--ap-sidebar-width);
|
||||||
|
background: var(--ap-surface);
|
||||||
|
border-right: 1px solid var(--ap-border);
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-sidebar__head {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 20px 16px 12px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-sidebar__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ap-text);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-sidebar__desc {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter--sidebar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-sidebar__list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-sidebar__foot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
text-align: center;
|
||||||
|
background: #fafbfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__detail {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--ap-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-list--loading,
|
||||||
|
.ap-list--empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 200px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px dashed var(--ap-border);
|
||||||
|
border-radius: var(--ap-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--ap-surface);
|
||||||
|
border: 1px solid var(--ap-border);
|
||||||
|
border-radius: var(--ap-radius);
|
||||||
|
box-shadow: var(--ap-shadow);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card:hover,
|
||||||
|
.ap-card:focus-visible {
|
||||||
|
border-color: rgba(22, 93, 255, 0.35);
|
||||||
|
box-shadow: 0 4px 16px rgba(22, 93, 255, 0.08);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card--selected {
|
||||||
|
border-color: rgba(22, 93, 255, 0.5);
|
||||||
|
background: var(--ap-primary-soft);
|
||||||
|
box-shadow: 0 4px 16px rgba(22, 93, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card--selected::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--ap-primary);
|
||||||
|
border-radius: var(--ap-radius) 0 0 var(--ap-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__type {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-status {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-status--pending {
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-status--approved {
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-status--rejected {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ap-text);
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__subtitle {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__facts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px 12px;
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card--selected .ap-card__facts {
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__fact {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__fact dt {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__fact dd {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ap-text);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.45;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__fact-value--emphasis {
|
||||||
|
color: var(--ap-primary);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__risks {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__risk {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #c2410c;
|
||||||
|
border: 1px solid #fed7aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__risk--high {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #b91c1c;
|
||||||
|
border-color: #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__risk--medium {
|
||||||
|
background: #fffbeb;
|
||||||
|
color: #b45309;
|
||||||
|
border-color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 8px 16px 10px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__footer-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__footer-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-card__action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ap-primary);
|
||||||
|
line-height: 1.4;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail panel */
|
||||||
|
.ap-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail--empty {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 20px 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__header-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__title {
|
||||||
|
margin: 0 !important;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__badge--pending {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__badge--approved {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__badge--rejected {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__meta {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__tabs {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__tabs .ant-tabs-content-holder {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__tabs .ant-tabs-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__content {
|
||||||
|
padding: 8px 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__subtitle {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__facts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px 16px;
|
||||||
|
margin: 0 0 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__fact dt {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__fact dd {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--ap-text);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__fact-value--emphasis {
|
||||||
|
color: var(--ap-primary);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__risks {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__timeline-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__section-title {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__timeline {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__timeline-item {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__timeline-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__timeline-time {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__flow-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 24px 20px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
background: var(--ap-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-summary {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid var(--ap-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-summary th,
|
||||||
|
.ap-insurance-summary td {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 1px solid #f1f5f9;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-summary th {
|
||||||
|
width: 14%;
|
||||||
|
background: #fafbfc;
|
||||||
|
color: var(--ap-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-summary td {
|
||||||
|
width: 36%;
|
||||||
|
color: var(--ap-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-table-section {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-table .ant-table,
|
||||||
|
.ap-insurance-quote-table .ant-table {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-quote-table {
|
||||||
|
margin: 0;
|
||||||
|
background: #fafbfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-insurance-quote-table .ant-table-thead > tr > th {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.ap-page {
|
||||||
|
height: auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__shell {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__sidebar {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 50vh;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--ap-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-page__detail {
|
||||||
|
min-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-filter__grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ap-detail__facts {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/common/oneos-web-approval/types.ts
Normal file
73
src/common/oneos-web-approval/types.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export type ApprovalStatus = 'pending' | 'processing' | 'approved' | 'rejected';
|
||||||
|
|
||||||
|
export type ApprovalTabKey = 'todo' | 'done' | 'initiated' | 'cc';
|
||||||
|
|
||||||
|
export type ApprovalTypeKey =
|
||||||
|
| 'parking'
|
||||||
|
| 'vehicle_procurement'
|
||||||
|
| 'lease'
|
||||||
|
| 'charter'
|
||||||
|
| 'transfer'
|
||||||
|
| 'vehicle_change'
|
||||||
|
| 'replacement'
|
||||||
|
| 'delivery'
|
||||||
|
| 'return_settlement'
|
||||||
|
| 'pickup_receivable'
|
||||||
|
| 'billing'
|
||||||
|
| 'insurance'
|
||||||
|
| 'supplier'
|
||||||
|
| 'customer_risk'
|
||||||
|
| 'third_party_return'
|
||||||
|
| 'contract_template'
|
||||||
|
| 'annual_inspection'
|
||||||
|
| 'finance_payment'
|
||||||
|
| 'clearing'
|
||||||
|
| 'energy_account';
|
||||||
|
|
||||||
|
export type ApprovalListTab = ApprovalTabKey;
|
||||||
|
|
||||||
|
export interface ApprovalKeyFact {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
emphasis?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApprovalRisk {
|
||||||
|
label: string;
|
||||||
|
level?: 'high' | 'medium' | 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApprovalCardItem {
|
||||||
|
id: string;
|
||||||
|
type: ApprovalTypeKey;
|
||||||
|
status: ApprovalStatus;
|
||||||
|
listTab: ApprovalListTab;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
keyFacts?: ApprovalKeyFact[];
|
||||||
|
risks?: ApprovalRisk[];
|
||||||
|
currentApprover?: string;
|
||||||
|
initiatedBy: string;
|
||||||
|
initiatedAt: string;
|
||||||
|
typeLabel?: string;
|
||||||
|
footerText?: string;
|
||||||
|
showInitiatorInFooter?: boolean;
|
||||||
|
ccUsers?: string[];
|
||||||
|
handledBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CURRENT_USER = '张明辉';
|
||||||
|
|
||||||
|
export type StatusFilterValue = 'all' | 'pending' | 'approved' | 'rejected';
|
||||||
|
|
||||||
|
export interface ApprovalFilters {
|
||||||
|
status: StatusFilterValue;
|
||||||
|
type: ApprovalTypeKey | 'all';
|
||||||
|
keyword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EMPTY_APPROVAL_FILTERS: ApprovalFilters = {
|
||||||
|
status: 'all',
|
||||||
|
type: 'all',
|
||||||
|
keyword: '',
|
||||||
|
};
|
||||||
@@ -11,6 +11,37 @@
|
|||||||
"title": "工作台",
|
"title": "工作台",
|
||||||
"itemKey": "prototypes/oneos-web-workbench"
|
"itemKey": "prototypes/oneos-web-workbench"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "folder-oneos-approval",
|
||||||
|
"kind": "folder",
|
||||||
|
"title": "审批中心",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-initiated",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我发起的",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-initiated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-todo",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的待办",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-todo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-done",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的已办",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "item:prototypes:oneos-web-approval-cc",
|
||||||
|
"kind": "item",
|
||||||
|
"title": "我的抄送",
|
||||||
|
"itemKey": "prototypes/oneos-web-approval-cc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "folder-1782874599304-roj8fd",
|
"id": "folder-1782874599304-roj8fd",
|
||||||
"kind": "folder",
|
"kind": "folder",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"updatedAt": "2026-07-14T21:21:56.182Z",
|
"updatedAt": "2026-07-16T01:04:29.807Z",
|
||||||
"title": "小羚羚",
|
"title": "小羚羚",
|
||||||
"sectionId": "folder-prototypes-xll-miniapp",
|
"sectionId": "folder-prototypes-xll-miniapp",
|
||||||
"description": "氢能车辆运营移动端原型;菜单与小羚羚「小程序」项目目录同步。",
|
"description": "氢能车辆运营移动端原型;菜单与小羚羚「小程序」项目目录同步。",
|
||||||
|
|||||||
8
src/prototypes/oneos-web-approval-cc/index.tsx
Normal file
8
src/prototypes/oneos-web-approval-cc/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @name 我的抄送
|
||||||
|
*/
|
||||||
|
import { ApprovalApp } from '../../common/oneos-web-approval/ApprovalApp';
|
||||||
|
|
||||||
|
export default function OneosWebApprovalCc() {
|
||||||
|
return <ApprovalApp tabKey="cc" pageTitle="我的抄送" />;
|
||||||
|
}
|
||||||
8
src/prototypes/oneos-web-approval-done/index.tsx
Normal file
8
src/prototypes/oneos-web-approval-done/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @name 我的已办
|
||||||
|
*/
|
||||||
|
import { ApprovalApp } from '../../common/oneos-web-approval/ApprovalApp';
|
||||||
|
|
||||||
|
export default function OneosWebApprovalDone() {
|
||||||
|
return <ApprovalApp tabKey="done" pageTitle="我的已办" />;
|
||||||
|
}
|
||||||
8
src/prototypes/oneos-web-approval-initiated/index.tsx
Normal file
8
src/prototypes/oneos-web-approval-initiated/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @name 我发起的
|
||||||
|
*/
|
||||||
|
import { ApprovalApp } from '../../common/oneos-web-approval/ApprovalApp';
|
||||||
|
|
||||||
|
export default function OneosWebApprovalInitiated() {
|
||||||
|
return <ApprovalApp tabKey="initiated" pageTitle="我发起的" />;
|
||||||
|
}
|
||||||
8
src/prototypes/oneos-web-approval-todo/index.tsx
Normal file
8
src/prototypes/oneos-web-approval-todo/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @name 我的待办
|
||||||
|
*/
|
||||||
|
import { ApprovalApp } from '../../common/oneos-web-approval/ApprovalApp';
|
||||||
|
|
||||||
|
export default function OneosWebApprovalTodo() {
|
||||||
|
return <ApprovalApp tabKey="todo" pageTitle="我的待办" />;
|
||||||
|
}
|
||||||
8
src/prototypes/oneos-web-approval/index.tsx
Normal file
8
src/prototypes/oneos-web-approval/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @name 审批中心
|
||||||
|
*/
|
||||||
|
import { ApprovalApp } from '../../common/oneos-web-approval/ApprovalApp';
|
||||||
|
|
||||||
|
export default function OneosWebApproval() {
|
||||||
|
return <ApprovalApp pageTitle="审批中心" showTabs />;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user