139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { Button, Space } from 'antd';
|
||
import { ArrowDownOutlined, ArrowUpOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||
import {
|
||
parseMiniHomeBanners,
|
||
serializeMiniHomeBanners,
|
||
} from '@dukang/shared-types';
|
||
import OssUpload from './OssUpload';
|
||
|
||
const MAX_BANNERS = 8;
|
||
|
||
/** 编辑态保留空位;下发/入库仍用 parseMiniHomeBanners 过滤空串 */
|
||
function parseBannersForEdit(raw?: string | null): string[] {
|
||
if (!raw?.trim()) return [];
|
||
try {
|
||
const parsed = JSON.parse(raw.trim()) as unknown;
|
||
if (!Array.isArray(parsed)) return parseMiniHomeBanners(raw);
|
||
return parsed
|
||
.filter((u): u is string => typeof u === 'string')
|
||
.map((u) => u.trim())
|
||
.slice(0, MAX_BANNERS);
|
||
} catch {
|
||
return parseMiniHomeBanners(raw);
|
||
}
|
||
}
|
||
|
||
/** Ant Form 控件:单图 URL 字符串(默认 OSS 路径 footer) */
|
||
export function ConfigImageField({
|
||
value,
|
||
onChange,
|
||
bizType = 'footer',
|
||
}: {
|
||
value?: string;
|
||
onChange?: (url: string) => void;
|
||
bizType?: string;
|
||
}) {
|
||
return (
|
||
<OssUpload
|
||
bizType={bizType}
|
||
mediaType="IMAGE"
|
||
value={value ?? ''}
|
||
onChange={(url) => onChange?.(url ?? '')}
|
||
placeholder="上传或粘贴图片 URL"
|
||
/>
|
||
);
|
||
}
|
||
|
||
/** Ant Form 控件:多图 JSON 字符串(默认 OSS 路径 swiper) */
|
||
export function ConfigImageListField({
|
||
value,
|
||
onChange,
|
||
bizType = 'swiper',
|
||
}: {
|
||
value?: string;
|
||
onChange?: (json: string) => void;
|
||
bizType?: string;
|
||
}) {
|
||
const [urls, setUrls] = useState<string[]>(() => parseBannersForEdit(value));
|
||
|
||
useEffect(() => {
|
||
setUrls(parseBannersForEdit(value));
|
||
}, [value]);
|
||
|
||
/** 本地可含空位;写入 Form 时去掉空串 */
|
||
function commit(next: string[]) {
|
||
const clipped = next.slice(0, MAX_BANNERS);
|
||
setUrls(clipped);
|
||
onChange?.(serializeMiniHomeBanners(clipped));
|
||
}
|
||
|
||
function updateAt(index: number, url: string) {
|
||
const next = [...urls];
|
||
next[index] = url;
|
||
commit(next);
|
||
}
|
||
|
||
function removeAt(index: number) {
|
||
commit(urls.filter((_, i) => i !== index));
|
||
}
|
||
|
||
function move(index: number, delta: number) {
|
||
const target = index + delta;
|
||
if (target < 0 || target >= urls.length) return;
|
||
const next = [...urls];
|
||
const tmp = next[index];
|
||
next[index] = next[target];
|
||
next[target] = tmp;
|
||
commit(next);
|
||
}
|
||
|
||
function add() {
|
||
if (urls.length >= MAX_BANNERS) return;
|
||
// 只加本地空位,避免 serialize 过滤空串导致「点击无反应」
|
||
setUrls((prev) => [...prev, '']);
|
||
}
|
||
|
||
return (
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
{urls.map((url, index) => (
|
||
<Space key={`banner-${index}`} align="start" style={{ width: '100%' }} wrap>
|
||
<div style={{ flex: 1, minWidth: 240 }}>
|
||
<OssUpload
|
||
bizType={bizType}
|
||
mediaType="IMAGE"
|
||
value={url}
|
||
onChange={(u) => updateAt(index, u)}
|
||
placeholder="上传或粘贴图片 URL"
|
||
/>
|
||
</div>
|
||
<Space>
|
||
<Button
|
||
type="text"
|
||
icon={<ArrowUpOutlined />}
|
||
disabled={index === 0}
|
||
onClick={() => move(index, -1)}
|
||
/>
|
||
<Button
|
||
type="text"
|
||
icon={<ArrowDownOutlined />}
|
||
disabled={index === urls.length - 1}
|
||
onClick={() => move(index, 1)}
|
||
/>
|
||
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => removeAt(index)} />
|
||
</Space>
|
||
</Space>
|
||
))}
|
||
<Button
|
||
type="dashed"
|
||
block
|
||
icon={<PlusOutlined />}
|
||
disabled={urls.length >= MAX_BANNERS}
|
||
onClick={add}
|
||
>
|
||
添加轮播图({urls.length}/{MAX_BANNERS})
|
||
</Button>
|
||
</Space>
|
||
);
|
||
}
|