128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import { useEffect, useState, type ReactNode } from 'react';
|
|
import { Button, Input, Space, Tag, Typography } from 'antd';
|
|
import { PlusOutlined } from '@ant-design/icons';
|
|
import type { HuyaCollabShop } from '../api/modules';
|
|
|
|
const { Text } = Typography;
|
|
type ShopOption = Pick<HuyaCollabShop, 'name' | 'is_active'>;
|
|
|
|
/**
|
|
* 店铺选择器:**一个店铺一个独立按钮**(中间留白),不做下拉。
|
|
*
|
|
* - 选中那个按钮变主题蓝(primary),没选的是白底描边,一眼看得出选的是谁;
|
|
* - `allowClear` 为真时多一个「不填 / 全部」按钮(筛选里传"全部"),
|
|
* 店铺必填的地方传 `false` 把空值这条路堵掉;
|
|
* - `allowCustom`:点「+ 新店」直接输新名字,选中即用(后端会自动登记进字典);
|
|
* - `includeInactive`:停用的店铺也列出来(筛选/详情里要能看到历史值);
|
|
* - 当前值不在字典里(老数据/刚自定义的名字)也会显示成一个按钮,不会被吞掉。
|
|
*/
|
|
export default function HuyaCollabShopPicker({
|
|
value,
|
|
shops,
|
|
disabled,
|
|
includeInactive = false,
|
|
allowCustom = true,
|
|
allowClear = true,
|
|
clearLabel = '不填',
|
|
size = 'small',
|
|
onChange,
|
|
}: {
|
|
value?: string | null;
|
|
shops: ShopOption[];
|
|
disabled?: boolean;
|
|
includeInactive?: boolean;
|
|
allowCustom?: boolean;
|
|
/** 是否给一个「清空」按钮:店铺必填的地方传 false,别让人点出个空值 */
|
|
allowClear?: boolean;
|
|
clearLabel?: string;
|
|
/** 按钮尺寸:列表单元格 / 筛选条用小号,新建弹窗用大号(更好点) */
|
|
size?: 'small' | 'middle' | 'large';
|
|
/** 放进 Form.Item 时由 antd 注入;单独用时自己传 */
|
|
onChange?: (value: string) => void;
|
|
}) {
|
|
const [customOpen, setCustomOpen] = useState(false);
|
|
const [customName, setCustomName] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (!customOpen) setCustomName('');
|
|
}, [customOpen]);
|
|
|
|
const current = value ?? '';
|
|
const visible = shops.filter((shop) => includeInactive || shop.is_active);
|
|
const options: { value: string; label: ReactNode }[] = visible.map((shop) => ({
|
|
value: shop.name,
|
|
label: (
|
|
<Space size={4}>
|
|
{shop.name}
|
|
{!shop.is_active && <Tag style={{ margin: 0 }}>停用</Tag>}
|
|
</Space>
|
|
),
|
|
}));
|
|
if (current && !visible.some((shop) => shop.name === current)) {
|
|
// 老数据 / 刚自定义的:照样给个可点的按钮,不然界面上看不出选中了啥
|
|
options.unshift({ value: current, label: current });
|
|
}
|
|
|
|
const submitCustom = () => {
|
|
const name = customName.trim();
|
|
if (!name) {
|
|
setCustomOpen(false);
|
|
return;
|
|
}
|
|
onChange?.(name);
|
|
setCustomOpen(false);
|
|
};
|
|
|
|
return (
|
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
|
<Space wrap size={size === 'large' ? 12 : 8}>
|
|
{/* 一个店铺一个独立按钮,中间留白(原来是连成一排的分段控件,太小不好点) */}
|
|
{options.map((option) => (
|
|
<Button
|
|
key={option.value}
|
|
size={size}
|
|
type={option.value === current ? 'primary' : 'default'}
|
|
disabled={disabled}
|
|
onClick={() => onChange?.(option.value)}
|
|
>
|
|
{option.label}
|
|
</Button>
|
|
))}
|
|
{allowClear && (
|
|
<Button
|
|
size={size}
|
|
type={current === '' ? 'primary' : 'default'}
|
|
disabled={disabled}
|
|
onClick={() => onChange?.('')}
|
|
>
|
|
<Text type={current === '' ? undefined : 'secondary'}>{clearLabel}</Text>
|
|
</Button>
|
|
)}
|
|
{allowCustom && !disabled && !customOpen && (
|
|
<Button size={size} type="link" icon={<PlusOutlined />} onClick={() => setCustomOpen(true)}>
|
|
新店
|
|
</Button>
|
|
)}
|
|
</Space>
|
|
{customOpen && (
|
|
<Input
|
|
size={size}
|
|
autoFocus
|
|
maxLength={64}
|
|
placeholder="输入新店铺名,回车确认"
|
|
value={customName}
|
|
onChange={(event) => setCustomName(event.target.value)}
|
|
onPressEnter={submitCustom}
|
|
onBlur={submitCustom}
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Escape') {
|
|
setCustomName('');
|
|
setCustomOpen(false);
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
</Space>
|
|
);
|
|
}
|