Files
live-hub-py/web/frontend/src/components/HuyaCollabShopPicker.tsx
T

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>
);
}