feat(id-tags): add ParentTagAutocomplete component and enhance TagFormBody with random ID generation

This commit is contained in:
2026-03-10 21:51:41 +08:00
parent 4f9fbe13fd
commit a349286049

View File

@@ -10,6 +10,7 @@ import {
DatePicker,
EmptyState,
Input,
InputGroup,
Label,
ListBox,
Modal,
@@ -77,7 +78,7 @@ function UserAutocomplete({
return (
<Autocomplete
fullWidth
placeholder="搜索用户"
placeholder="搜索用户"
selectionMode="single"
value={userId || null}
onChange={(key) => onChange(key ? String(key) : "")}
@@ -99,7 +100,7 @@ function UserAutocomplete({
<SearchField autoFocus name="userSearch" variant="secondary">
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input placeholder="搜索姓名或邮箱" />
<SearchField.Input placeholder="搜索姓名或邮箱" />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
@@ -122,21 +123,98 @@ function UserAutocomplete({
);
}
function ParentTagAutocomplete({
value,
onChange,
tags,
excludeTag,
}: {
value: string;
onChange: (v: string) => void;
tags: IdTag[];
excludeTag?: string;
}) {
const { contains } = useFilter({ sensitivity: "base" });
const options = tags.filter((t) => t.idTag !== excludeTag);
return (
<Autocomplete
fullWidth
placeholder="搜索卡号"
selectionMode="single"
value={value || null}
onChange={(key) => onChange(key ? String(key) : "")}
>
<Autocomplete.Trigger>
<Autocomplete.Value>
{({ isPlaceholder, state }: any) => {
if (isPlaceholder || !state.selectedItems?.length)
return <span className="text-muted"></span>;
return <span className="font-mono">{state.selectedItems[0]?.key}</span>;
}}
</Autocomplete.Value>
<Autocomplete.ClearButton />
<Autocomplete.Indicator />
</Autocomplete.Trigger>
<Autocomplete.Popover>
<Autocomplete.Filter filter={contains}>
<SearchField autoFocus name="parentTagSearch" variant="secondary">
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input placeholder="搜索卡号" />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
<ListBox renderEmptyState={() => <EmptyState></EmptyState>}>
{options.map((t) => (
<ListBox.Item key={t.idTag} id={t.idTag} textValue={t.idTag}>
<span className="font-mono">{t.idTag}</span>
<ListBox.ItemIndicator />
</ListBox.Item>
))}
</ListBox>
</Autocomplete.Filter>
</Autocomplete.Popover>
</Autocomplete>
);
}
function generateIdTag(): string {
const chars = "0123456789ABCDEF";
let result = "";
for (let i = 0; i < 8; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
function TagFormBody({
form,
setForm,
isEdit,
users,
tags,
}: {
form: FormState;
setForm: (f: FormState) => void;
isEdit: boolean;
users: UserRow[];
tags: IdTag[];
}) {
return (
<>
<TextField fullWidth>
<div className="flex items-center justify-between">
<Label className="text-sm font-medium">{isEdit ? "卡号" : "卡号 (idTag)"}</Label>
{!isEdit && (
<button
type="button"
className="text-xs text-accent hover:underline"
onClick={() => setForm({ ...form, idTag: generateIdTag() })}
>
</button>
)}
</div>
<Input
disabled={isEdit}
className="font-mono"
@@ -168,7 +246,7 @@ function TagFormBody({
</Select>
</div>
<div className="flex flex-col gap-1">
<Label className="text-sm font-medium">{isEdit ? "有效期" : "有效期 (可选)"}</Label>
<Label className="text-sm font-medium"></Label>
<DatePicker
value={form.expiryDate ? parseDate(form.expiryDate) : null}
onChange={(date) => setForm({ ...form, expiryDate: date ? date.toString() : "" })}
@@ -213,28 +291,32 @@ function TagFormBody({
</DatePicker.Popover>
</DatePicker>
</div>
<TextField fullWidth>
<Label className="text-sm font-medium">{isEdit ? "父卡号" : "父卡号 (可选)"}</Label>
<Input
className="font-mono"
placeholder="parentIdTag"
<div className="flex flex-col gap-1">
<Label className="text-sm font-medium"></Label>
<ParentTagAutocomplete
value={form.parentIdTag}
onChange={(e) => setForm({ ...form, parentIdTag: e.target.value })}
onChange={(v) => setForm({ ...form, parentIdTag: v })}
tags={tags}
excludeTag={isEdit ? form.idTag : undefined}
/>
</TextField>
</div>
<TextField fullWidth>
<Label className="text-sm font-medium"></Label>
<Input
type="number"
<Label className="text-sm font-medium"></Label>
<InputGroup>
<InputGroup.Prefix></InputGroup.Prefix>
<InputGroup.Input
placeholder="0.00"
min="0"
step="0.01"
placeholder="0.00"
type="number"
value={form.balance}
onChange={(e) => setForm({ ...form, balance: e.target.value })}
/>
<InputGroup.Suffix>CNY</InputGroup.Suffix>
</InputGroup>
</TextField>
<div className="flex flex-col gap-1">
<Label className="text-sm font-medium"></Label>
<Label className="text-sm font-medium"></Label>
<UserAutocomplete
userId={form.userId}
onChange={(id) => setForm({ ...form, userId: id })}
@@ -360,7 +442,13 @@ export default function IdTagsPage() {
<Modal.Heading></Modal.Heading>
</Modal.Header>
<Modal.Body className="space-y-3">
<TagFormBody form={form} setForm={setForm} isEdit={false} users={users} />
<TagFormBody
form={form}
setForm={setForm}
isEdit={false}
users={users}
tags={tags}
/>
</Modal.Body>
<Modal.Footer className="flex justify-end gap-2">
<Button slot="close" variant="ghost">
@@ -467,6 +555,7 @@ export default function IdTagsPage() {
setForm={setForm}
isEdit={true}
users={users}
tags={tags}
/>
</Modal.Body>
<Modal.Footer className="flex justify-end gap-2">
@@ -521,7 +610,11 @@ export default function IdTagsPage() {
isDisabled={deletingTag === tag.idTag}
onPress={() => handleDelete(tag.idTag)}
>
{deletingTag === tag.idTag ? <Spinner size="sm" /> : "确认删除"}
{deletingTag === tag.idTag ? (
<Spinner size="sm" />
) : (
"确认删除"
)}
</Button>
</Modal.Footer>
</Modal.Dialog>