feat(id-tags): add ParentTagAutocomplete component and enhance TagFormBody with random ID generation
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
DatePicker,
|
DatePicker,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
Input,
|
Input,
|
||||||
|
InputGroup,
|
||||||
Label,
|
Label,
|
||||||
ListBox,
|
ListBox,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -77,7 +78,7 @@ function UserAutocomplete({
|
|||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="搜索用户…"
|
placeholder="搜索用户"
|
||||||
selectionMode="single"
|
selectionMode="single"
|
||||||
value={userId || null}
|
value={userId || null}
|
||||||
onChange={(key) => onChange(key ? String(key) : "")}
|
onChange={(key) => onChange(key ? String(key) : "")}
|
||||||
@@ -99,7 +100,7 @@ function UserAutocomplete({
|
|||||||
<SearchField autoFocus name="userSearch" variant="secondary">
|
<SearchField autoFocus name="userSearch" variant="secondary">
|
||||||
<SearchField.Group>
|
<SearchField.Group>
|
||||||
<SearchField.SearchIcon />
|
<SearchField.SearchIcon />
|
||||||
<SearchField.Input placeholder="搜索姓名或邮箱…" />
|
<SearchField.Input placeholder="搜索姓名或邮箱" />
|
||||||
<SearchField.ClearButton />
|
<SearchField.ClearButton />
|
||||||
</SearchField.Group>
|
</SearchField.Group>
|
||||||
</SearchField>
|
</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({
|
function TagFormBody({
|
||||||
form,
|
form,
|
||||||
setForm,
|
setForm,
|
||||||
isEdit,
|
isEdit,
|
||||||
users,
|
users,
|
||||||
|
tags,
|
||||||
}: {
|
}: {
|
||||||
form: FormState;
|
form: FormState;
|
||||||
setForm: (f: FormState) => void;
|
setForm: (f: FormState) => void;
|
||||||
isEdit: boolean;
|
isEdit: boolean;
|
||||||
users: UserRow[];
|
users: UserRow[];
|
||||||
|
tags: IdTag[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextField fullWidth>
|
<TextField fullWidth>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium">{isEdit ? "卡号" : "卡号 (idTag)"}</Label>
|
<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
|
<Input
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
className="font-mono"
|
className="font-mono"
|
||||||
@@ -168,7 +246,7 @@ function TagFormBody({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<Label className="text-sm font-medium">{isEdit ? "有效期" : "有效期 (可选)"}</Label>
|
<Label className="text-sm font-medium">有效期</Label>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={form.expiryDate ? parseDate(form.expiryDate) : null}
|
value={form.expiryDate ? parseDate(form.expiryDate) : null}
|
||||||
onChange={(date) => setForm({ ...form, expiryDate: date ? date.toString() : "" })}
|
onChange={(date) => setForm({ ...form, expiryDate: date ? date.toString() : "" })}
|
||||||
@@ -213,28 +291,32 @@ function TagFormBody({
|
|||||||
</DatePicker.Popover>
|
</DatePicker.Popover>
|
||||||
</DatePicker>
|
</DatePicker>
|
||||||
</div>
|
</div>
|
||||||
<TextField fullWidth>
|
<div className="flex flex-col gap-1">
|
||||||
<Label className="text-sm font-medium">{isEdit ? "父卡号" : "父卡号 (可选)"}</Label>
|
<Label className="text-sm font-medium">父卡号</Label>
|
||||||
<Input
|
<ParentTagAutocomplete
|
||||||
className="font-mono"
|
|
||||||
placeholder="parentIdTag"
|
|
||||||
value={form.parentIdTag}
|
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>
|
<TextField fullWidth>
|
||||||
<Label className="text-sm font-medium">余额(元)</Label>
|
<Label className="text-sm font-medium">余额</Label>
|
||||||
<Input
|
<InputGroup>
|
||||||
type="number"
|
<InputGroup.Prefix>¥</InputGroup.Prefix>
|
||||||
|
<InputGroup.Input
|
||||||
|
placeholder="0.00"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="0.00"
|
type="number"
|
||||||
value={form.balance}
|
value={form.balance}
|
||||||
onChange={(e) => setForm({ ...form, balance: e.target.value })}
|
onChange={(e) => setForm({ ...form, balance: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
<InputGroup.Suffix>CNY</InputGroup.Suffix>
|
||||||
|
</InputGroup>
|
||||||
</TextField>
|
</TextField>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<Label className="text-sm font-medium">关联用户(可选)</Label>
|
<Label className="text-sm font-medium">关联用户</Label>
|
||||||
<UserAutocomplete
|
<UserAutocomplete
|
||||||
userId={form.userId}
|
userId={form.userId}
|
||||||
onChange={(id) => setForm({ ...form, userId: id })}
|
onChange={(id) => setForm({ ...form, userId: id })}
|
||||||
@@ -360,7 +442,13 @@ export default function IdTagsPage() {
|
|||||||
<Modal.Heading>新增储值卡</Modal.Heading>
|
<Modal.Heading>新增储值卡</Modal.Heading>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className="space-y-3">
|
<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.Body>
|
||||||
<Modal.Footer className="flex justify-end gap-2">
|
<Modal.Footer className="flex justify-end gap-2">
|
||||||
<Button slot="close" variant="ghost">
|
<Button slot="close" variant="ghost">
|
||||||
@@ -467,6 +555,7 @@ export default function IdTagsPage() {
|
|||||||
setForm={setForm}
|
setForm={setForm}
|
||||||
isEdit={true}
|
isEdit={true}
|
||||||
users={users}
|
users={users}
|
||||||
|
tags={tags}
|
||||||
/>
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer className="flex justify-end gap-2">
|
<Modal.Footer className="flex justify-end gap-2">
|
||||||
@@ -521,7 +610,11 @@ export default function IdTagsPage() {
|
|||||||
isDisabled={deletingTag === tag.idTag}
|
isDisabled={deletingTag === tag.idTag}
|
||||||
onPress={() => handleDelete(tag.idTag)}
|
onPress={() => handleDelete(tag.idTag)}
|
||||||
>
|
>
|
||||||
{deletingTag === tag.idTag ? <Spinner size="sm" /> : "确认删除"}
|
{deletingTag === tag.idTag ? (
|
||||||
|
<Spinner size="sm" />
|
||||||
|
) : (
|
||||||
|
"确认删除"
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal.Dialog>
|
</Modal.Dialog>
|
||||||
|
|||||||
Reference in New Issue
Block a user