Files
Public/apps/web/src/pages/MyFlowPage.tsx
T
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

136 lines
4.5 KiB
TypeScript

import { Button, Segmented, Space, Table, Tag } from 'antd';
import { useQuery } from '@tanstack/react-query';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { api } from '../api/client';
import { PageHeader, unwrapList } from './ResourcePage';
import { ApplyBucketBar, PROCESS_BUCKETS } from './ApplyBucketBar';
import { fmtCreated } from '../time';
import { zh } from '../labels';
import { HR_APPLY_LABEL } from './hrStatus';
const SOURCE_LABEL: Record<string, string> = {
HR: '人事',
SEAL: '用章',
OFFICE: '办公',
EXPENSE: '费用',
};
const KIND_LABEL: Record<string, string> = {
...HR_APPLY_LABEL,
SEAL: '用章',
ASSET_BUY: '资产购置',
ASSET_USE: '资产领用',
VEHICLE: '用车',
EXPENSE: '报销',
LOAN: '借支',
};
const BUCKET_META: Record<string, { color: string; text: string }> = {
pending: { color: 'processing', text: '待审批' },
approved: { color: 'success', text: '已通过' },
rejected: { color: 'error', text: '已驳回' },
occupying: { color: 'blue', text: '占用中' },
out: { color: 'blue', text: '外带中' },
overdue: { color: 'error', text: '逾期未还' },
returned: { color: 'success', text: '已归还' },
};
type FlowRow = {
id: string;
source: string;
kind: string;
title: string;
status: string;
bucket: string;
createdAt: string;
path: string;
};
export default function MyFlowPage() {
const [params] = useSearchParams();
const navigate = useNavigate();
const bucket = params.get('bucket') || '';
const kind = params.get('kind') || '';
const qs = new URLSearchParams();
if (bucket) qs.set('bucket', bucket);
if (kind) qs.set('kind', kind);
const endpoint = `/office/flow${qs.toString() ? `?${qs}` : ''}`;
const q = useQuery({ queryKey: [endpoint], queryFn: () => api.get(endpoint) });
const items = unwrapList(q.data).items as unknown as FlowRow[];
const setKind = (v: string) => {
const next = new URLSearchParams();
if (bucket) next.set('bucket', bucket);
if (v) next.set('kind', v);
const s = next.toString();
navigate(s ? `/office/flow?${s}` : '/office/flow');
};
return (
<>
<PageHeader
title="我的申请"
description="跟踪自己提交的人事、用章、办公和费用。要新开一单,从右侧进对应发起页。"
extra={
<ApplyBucketBar
basePath="/office/flow"
buckets={[
...PROCESS_BUCKETS,
{ value: 'occupying', label: '占用中' },
{ value: 'out', label: '外带中' },
{ value: 'overdue', label: '逾期未还' },
]}
extra={
<Space>
<Button onClick={() => navigate('/office/hr-apply')}></Button>
<Button onClick={() => navigate('/office/seal-apply')}></Button>
<Button type="primary" onClick={() => navigate('/office/apply')}>
</Button>
</Space>
}
/>
}
/>
<div style={{ marginBottom: 16 }}>
<Segmented
value={kind}
onChange={(v) => setKind(String(v))}
options={[
{ label: '全部类型', value: '' },
{ label: '人事', value: 'HR' },
{ label: '用章', value: 'SEAL' },
{ label: '办公', value: 'OFFICE' },
{ label: '费用', value: 'EXPENSE' },
]}
/>
</div>
<Table
className="ops-table"
rowKey={(r) => `${r.source}-${r.id}`}
loading={q.isLoading}
dataSource={items}
pagination={{ defaultPageSize: 10, showSizeChanger: true, showTotal: (n) => `${n}` }}
columns={[
{ title: '来源', width: 80, render: (_: unknown, r: FlowRow) => SOURCE_LABEL[r.source] || r.source },
{ title: '类型', width: 110, render: (_: unknown, r: FlowRow) => KIND_LABEL[r.kind] || r.kind },
{
title: '事项',
ellipsis: true,
render: (_: unknown, r: FlowRow) => <Link to={r.path}>{r.title}</Link>,
},
{
title: '状态',
width: 110,
render: (_: unknown, r: FlowRow) => {
const s = BUCKET_META[r.bucket] || { color: 'default', text: zh(r.status) };
return <Tag color={s.color}>{s.text}</Tag>;
},
},
{ title: '创建时间', width: 170, render: (_: unknown, r: FlowRow) => fmtCreated(r.createdAt) },
]}
/>
</>
);
}