管理端门店详情(审核抽屉)加上「审核材料」区
This commit is contained in:
@@ -19,8 +19,15 @@ import {
|
|||||||
message,
|
message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import { FilePdfOutlined, LinkOutlined } from '@ant-design/icons';
|
||||||
import { request, type Paginated } from '../lib/api';
|
import { request, type Paginated } from '../lib/api';
|
||||||
import { ADMIN_OPTIONS_PAGE_SIZE, STORE_AUDIT_STATUS_LABELS, STORE_STATUS_LABELS, fmtTime } from '../lib/constants';
|
import {
|
||||||
|
ADMIN_OPTIONS_PAGE_SIZE,
|
||||||
|
RESOURCE_BIZ_TYPE_LABELS,
|
||||||
|
STORE_AUDIT_STATUS_LABELS,
|
||||||
|
STORE_STATUS_LABELS,
|
||||||
|
fmtTime,
|
||||||
|
} from '../lib/constants';
|
||||||
import {
|
import {
|
||||||
validateStoreCreateStep1,
|
validateStoreCreateStep1,
|
||||||
validateStoreCreateStep3,
|
validateStoreCreateStep3,
|
||||||
@@ -37,6 +44,209 @@ const CREATE_STEPS = [
|
|||||||
{ title: '结算资质' },
|
{ title: '结算资质' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
type StoreMediaItem = {
|
||||||
|
id?: string;
|
||||||
|
bizType?: string;
|
||||||
|
mediaType?: string;
|
||||||
|
url?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isImageMedia(url: string, mediaType?: string) {
|
||||||
|
if (mediaType === 'IMAGE') return true;
|
||||||
|
if (mediaType === 'VIDEO' || mediaType === 'FILE') {
|
||||||
|
return /\.(png|jpe?g|gif|webp|bmp|heic)(\?|#|$)/i.test(url);
|
||||||
|
}
|
||||||
|
return /\.(png|jpe?g|gif|webp|bmp|heic)(\?|#|$)/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPdfUrl(url: string) {
|
||||||
|
return /\.pdf(\?|#|$)/i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectMediaUrls(detail: Record<string, unknown>) {
|
||||||
|
const media = Array.isArray(detail.media) ? (detail.media as StoreMediaItem[]) : [];
|
||||||
|
const byType = (bizType: string) =>
|
||||||
|
media
|
||||||
|
.filter((item) => String(item.bizType || '').toUpperCase() === bizType)
|
||||||
|
.map((item) => ({
|
||||||
|
id: String(item.id || item.url || ''),
|
||||||
|
url: String(item.url || '').trim(),
|
||||||
|
mediaType: item.mediaType ? String(item.mediaType) : undefined,
|
||||||
|
}))
|
||||||
|
.filter((item) => item.url);
|
||||||
|
|
||||||
|
const covers = byType('COVER');
|
||||||
|
const coverUrl = detail.coverUrl ? String(detail.coverUrl).trim() : '';
|
||||||
|
if (coverUrl && !covers.some((item) => item.url === coverUrl)) {
|
||||||
|
covers.unshift({ id: 'cover', url: coverUrl, mediaType: 'IMAGE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
covers,
|
||||||
|
envs: byType('ENV'),
|
||||||
|
contracts: byType('CONTRACT'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function StoreAuditMediaSection({ detail }: { detail: Record<string, unknown> }) {
|
||||||
|
const { covers, envs, contracts } = collectMediaUrls(detail);
|
||||||
|
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||||
|
const gallery = [...covers, ...envs].filter((item) => isImageMedia(item.url, item.mediaType));
|
||||||
|
|
||||||
|
if (covers.length === 0 && envs.length === 0 && contracts.length === 0) {
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
message="暂无门头照 / 环境照 / 签约合同,请谨慎审核"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Title level={5} style={{ marginTop: 0, marginBottom: 12 }}>
|
||||||
|
审核材料
|
||||||
|
</Typography.Title>
|
||||||
|
|
||||||
|
{(covers.length > 0 || envs.length > 0) && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||||
|
门头照 / 环境照(点击可放大浏览)
|
||||||
|
</Typography.Text>
|
||||||
|
<Image.PreviewGroup>
|
||||||
|
<Space wrap size={12}>
|
||||||
|
{gallery.map((item) => (
|
||||||
|
<div key={item.id} style={{ textAlign: 'center' }}>
|
||||||
|
<Image
|
||||||
|
src={item.url}
|
||||||
|
width={112}
|
||||||
|
height={84}
|
||||||
|
style={{ objectFit: 'cover', borderRadius: 6, border: '1px solid #f0f0f0' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: 12, color: '#8c8c8c', marginTop: 4 }}>
|
||||||
|
{covers.some((c) => c.id === item.id) ? '门头照' : '环境照'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Image.PreviewGroup>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{contracts.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||||
|
签约合同
|
||||||
|
</Typography.Text>
|
||||||
|
<Space direction="vertical" size={12} style={{ width: '100%' }}>
|
||||||
|
{contracts.map((item, index) => {
|
||||||
|
const imageLike = isImageMedia(item.url, item.mediaType);
|
||||||
|
const pdf = isPdfUrl(item.url);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id || `${item.url}-${index}`}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 12,
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: 8,
|
||||||
|
background: '#fafafa',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{imageLike ? (
|
||||||
|
<Image.PreviewGroup>
|
||||||
|
<Image
|
||||||
|
src={item.url}
|
||||||
|
width={96}
|
||||||
|
height={72}
|
||||||
|
style={{ objectFit: 'cover', borderRadius: 6 }}
|
||||||
|
/>
|
||||||
|
</Image.PreviewGroup>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 96,
|
||||||
|
height: 72,
|
||||||
|
borderRadius: 6,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px dashed #d9d9d9',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: '#cf1322',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FilePdfOutlined style={{ fontSize: 28 }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Space direction="vertical" size={4} style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<Typography.Text strong>
|
||||||
|
{RESOURCE_BIZ_TYPE_LABELS.CONTRACT || '合同'}
|
||||||
|
{contracts.length > 1 ? ` ${index + 1}` : ''}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text type="secondary" ellipsis style={{ maxWidth: '100%' }}>
|
||||||
|
{item.url}
|
||||||
|
</Typography.Text>
|
||||||
|
<Space wrap>
|
||||||
|
{imageLike ? (
|
||||||
|
<Typography.Text type="secondary">点击缩略图放大查看</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
{pdf ? (
|
||||||
|
<Button type="link" size="small" style={{ padding: 0 }} onClick={() => setPdfUrl(item.url)}>
|
||||||
|
页内预览 PDF
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<LinkOutlined />}
|
||||||
|
style={{ padding: 0 }}
|
||||||
|
href={item.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
新窗口打开
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="合同预览"
|
||||||
|
open={!!pdfUrl}
|
||||||
|
onCancel={() => setPdfUrl(null)}
|
||||||
|
width={900}
|
||||||
|
footer={[
|
||||||
|
<Button key="open" href={pdfUrl || undefined} target="_blank" rel="noreferrer">
|
||||||
|
新窗口打开
|
||||||
|
</Button>,
|
||||||
|
<Button key="close" type="primary" onClick={() => setPdfUrl(null)}>
|
||||||
|
关闭
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
{pdfUrl ? (
|
||||||
|
<iframe
|
||||||
|
title="合同 PDF 预览"
|
||||||
|
src={pdfUrl}
|
||||||
|
style={{ width: '100%', height: '70vh', border: 'none', borderRadius: 8 }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type StoreRow = {
|
type StoreRow = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -307,7 +517,7 @@ export default function StoresPage() {
|
|||||||
</Form>
|
</Form>
|
||||||
<Table rowKey="id" className="admin-table-nowrap" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 1200 }}
|
<Table rowKey="id" className="admin-table-nowrap" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 1200 }}
|
||||||
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
||||||
<Drawer title="门店详情" width={600} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
<Drawer title="门店详情" width={760} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
||||||
extra={detail && (
|
extra={detail && (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{String(detail.auditStatus || '') === 'PENDING' || String(detail.auditStatus || '') === 'REJECTED' ? (
|
{String(detail.auditStatus || '') === 'PENDING' || String(detail.auditStatus || '') === 'REJECTED' ? (
|
||||||
@@ -367,6 +577,7 @@ export default function StoresPage() {
|
|||||||
)}>
|
)}>
|
||||||
{detail && (
|
{detail && (
|
||||||
<>
|
<>
|
||||||
|
<StoreAuditMediaSection detail={detail} />
|
||||||
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
||||||
<Descriptions.Item label="ID">{String(detail.id)}</Descriptions.Item>
|
<Descriptions.Item label="ID">{String(detail.id)}</Descriptions.Item>
|
||||||
<Descriptions.Item label="审核状态">
|
<Descriptions.Item label="审核状态">
|
||||||
@@ -390,11 +601,6 @@ export default function StoresPage() {
|
|||||||
查看商户日志
|
查看商户日志
|
||||||
</Button>
|
</Button>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
{detail.coverUrl ? (
|
|
||||||
<Descriptions.Item label="封面">
|
|
||||||
<Image src={String(detail.coverUrl)} width={120} />
|
|
||||||
</Descriptions.Item>
|
|
||||||
) : null}
|
|
||||||
{Array.isArray(detail.audits) && (detail.audits as Array<Record<string, unknown>>).length > 0 ? (
|
{Array.isArray(detail.audits) && (detail.audits as Array<Record<string, unknown>>).length > 0 ? (
|
||||||
<Descriptions.Item label="审核记录">
|
<Descriptions.Item label="审核记录">
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user