feat: multi-module iteration

This commit is contained in:
2026-08-04 21:38:49 +08:00
parent 9d96c73246
commit 71f508e02b
1366 changed files with 202004 additions and 0 deletions
@@ -0,0 +1,138 @@
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>
);
}