@aaas-pro/library 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -2
- package/dist/components/address-picker.d.ts +18 -0
- package/dist/components/address-picker.js +46 -0
- package/dist/components/card-date-filter.d.ts +12 -0
- package/dist/components/card-date-filter.js +95 -0
- package/dist/components/date-time-picker.d.ts +12 -0
- package/dist/components/date-time-picker.js +96 -0
- package/dist/components/detail-dialog.d.ts +48 -0
- package/dist/components/detail-dialog.js +97 -0
- package/dist/components/document-links.d.ts +13 -0
- package/dist/components/document-links.js +19 -0
- package/dist/components/draft-row-actions.d.ts +14 -0
- package/dist/components/draft-row-actions.js +70 -0
- package/dist/components/error-boundary.d.ts +14 -0
- package/dist/components/error-boundary.js +35 -0
- package/dist/components/glow-card.d.ts +13 -0
- package/dist/components/glow-card.js +48 -0
- package/dist/components/org-picker.d.ts +17 -0
- package/dist/components/org-picker.js +56 -0
- package/dist/components/overflow-row-actions.d.ts +29 -0
- package/dist/components/overflow-row-actions.js +135 -0
- package/dist/components/page-header.d.ts +21 -0
- package/dist/components/page-header.js +21 -0
- package/dist/components/permission-button.d.ts +4 -0
- package/dist/components/permission-button.js +17 -0
- package/dist/components/permission-context.d.ts +12 -0
- package/dist/components/permission-context.js +17 -0
- package/dist/components/person-picker.d.ts +20 -0
- package/dist/components/person-picker.js +38 -0
- package/dist/components/process-flow-bar.d.ts +15 -0
- package/dist/components/process-flow-bar.js +19 -0
- package/dist/components/product-picker.d.ts +19 -0
- package/dist/components/product-picker.js +155 -0
- package/dist/components/ref-doc-picker.d.ts +24 -0
- package/dist/components/ref-doc-picker.js +139 -0
- package/dist/components/scroll-to-top.d.ts +1 -0
- package/dist/components/scroll-to-top.js +20 -0
- package/dist/components/state-view.d.ts +26 -0
- package/dist/components/state-view.js +75 -0
- package/dist/components/theme-switcher.d.ts +27 -0
- package/dist/components/theme-switcher.js +77 -0
- package/dist/components/time-filter.d.ts +7 -0
- package/dist/components/time-filter.js +65 -0
- package/dist/components/ui/accordion.d.ts +7 -0
- package/dist/components/ui/accordion.js +64 -0
- package/dist/components/ui/alert-dialog.d.ts +15 -0
- package/dist/components/ui/alert-dialog.js +160 -0
- package/dist/components/ui/alert.d.ts +12 -0
- package/dist/components/ui/alert.js +66 -0
- package/dist/components/ui/aspect-ratio.d.ts +3 -0
- package/dist/components/ui/aspect-ratio.js +10 -0
- package/dist/components/ui/avatar.d.ts +6 -0
- package/dist/components/ui/avatar.js +53 -0
- package/dist/components/ui/badge.d.ts +12 -0
- package/dist/components/ui/badge.js +44 -0
- package/dist/components/ui/breadcrumb.d.ts +11 -0
- package/dist/components/ui/breadcrumb.js +103 -0
- package/dist/components/ui/button-group.d.ts +14 -0
- package/dist/components/ui/button-group.js +76 -0
- package/dist/components/ui/button.d.ts +20 -0
- package/dist/components/ui/button.js +93 -0
- package/dist/components/ui/calendar.d.ts +73 -0
- package/dist/components/ui/calendar.js +103 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.js +90 -0
- package/dist/components/ui/carousel.d.ts +14 -0
- package/dist/components/ui/carousel.js +200 -0
- package/dist/components/ui/chart.d.ts +43 -0
- package/dist/components/ui/chart.js +252 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.js +31 -0
- package/dist/components/ui/collapsible.d.ts +5 -0
- package/dist/components/ui/collapsible.js +34 -0
- package/dist/components/ui/command.d.ts +18 -0
- package/dist/components/ui/command.js +170 -0
- package/dist/components/ui/context-menu.d.ts +26 -0
- package/dist/components/ui/context-menu.js +237 -0
- package/dist/components/ui/dialog.d.ts +17 -0
- package/dist/components/ui/dialog.js +139 -0
- package/dist/components/ui/drawer.d.ts +13 -0
- package/dist/components/ui/drawer.js +129 -0
- package/dist/components/ui/dropdown-menu.d.ts +26 -0
- package/dist/components/ui/dropdown-menu.js +245 -0
- package/dist/components/ui/empty.d.ts +14 -0
- package/dist/components/ui/empty.js +102 -0
- package/dist/components/ui/field.d.ts +27 -0
- package/dist/components/ui/field.js +229 -0
- package/dist/components/ui/form.d.ts +26 -0
- package/dist/components/ui/form.js +118 -0
- package/dist/components/ui/hover-card.d.ts +6 -0
- package/dist/components/ui/hover-card.js +39 -0
- package/dist/components/ui/input-group.d.ts +22 -0
- package/dist/components/ui/input-group.js +156 -0
- package/dist/components/ui/input-otp.d.ts +11 -0
- package/dist/components/ui/input-otp.js +66 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/input.js +21 -0
- package/dist/components/ui/item.d.ts +29 -0
- package/dist/components/ui/item.js +187 -0
- package/dist/components/ui/kbd.d.ts +3 -0
- package/dist/components/ui/kbd.js +31 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/label.js +22 -0
- package/dist/components/ui/menubar.d.ts +26 -0
- package/dist/components/ui/menubar.js +252 -0
- package/dist/components/ui/navigation-menu.d.ts +17 -0
- package/dist/components/ui/navigation-menu.js +170 -0
- package/dist/components/ui/pagination.d.ts +13 -0
- package/dist/components/ui/pagination.js +118 -0
- package/dist/components/ui/popover.d.ts +8 -0
- package/dist/components/ui/popover.js +47 -0
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/progress.js +34 -0
- package/dist/components/ui/radio-group.d.ts +5 -0
- package/dist/components/ui/radio-group.js +45 -0
- package/dist/components/ui/resizable.d.ts +7 -0
- package/dist/components/ui/resizable.js +49 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/scroll-area.js +60 -0
- package/dist/components/ui/select.d.ts +17 -0
- package/dist/components/ui/select.js +215 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +26 -0
- package/dist/components/ui/sheet.d.ts +13 -0
- package/dist/components/ui/sheet.js +127 -0
- package/dist/components/ui/sidebar.d.ts +72 -0
- package/dist/components/ui/sidebar.js +656 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/skeleton.js +15 -0
- package/dist/components/ui/slider.d.ts +4 -0
- package/dist/components/ui/slider.js +63 -0
- package/dist/components/ui/sonner.d.ts +4 -0
- package/dist/components/ui/sonner.js +34 -0
- package/dist/components/ui/spinner.d.ts +2 -0
- package/dist/components/ui/spinner.js +17 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.js +31 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/table.js +114 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/tabs.js +67 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/textarea.js +18 -0
- package/dist/components/ui/toggle-group.d.ts +9 -0
- package/dist/components/ui/toggle-group.js +68 -0
- package/dist/components/ui/toggle.d.ts +12 -0
- package/dist/components/ui/toggle.js +43 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +56 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +18 -0
- package/dist/icons/index.d.ts +57 -0
- package/dist/icons/index.js +115 -0
- package/dist/icons/paths.d.ts +556 -0
- package/dist/icons/paths.js +231 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +399 -0
- package/dist/internal/selection-base.d.ts +37 -0
- package/dist/internal/selection-base.js +40 -0
- package/dist/lib/class-names.d.ts +6 -0
- package/dist/lib/class-names.js +159 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +7 -0
- package/dist/lib/variants.d.ts +21 -0
- package/dist/lib/variants.js +21 -0
- package/dist/primitives/accordion.d.ts +29 -0
- package/dist/primitives/accordion.js +49 -0
- package/dist/primitives/alert-dialog.d.ts +10 -0
- package/dist/primitives/alert-dialog.js +22 -0
- package/dist/primitives/aspect-ratio.d.ts +4 -0
- package/dist/primitives/aspect-ratio.js +7 -0
- package/dist/primitives/avatar.d.ts +8 -0
- package/dist/primitives/avatar.js +45 -0
- package/dist/primitives/carousel.d.ts +22 -0
- package/dist/primitives/carousel.js +78 -0
- package/dist/primitives/chart.d.ts +71 -0
- package/dist/primitives/chart.js +125 -0
- package/dist/primitives/checkbox.d.ts +13 -0
- package/dist/primitives/checkbox.js +24 -0
- package/dist/primitives/collapsible.d.ts +14 -0
- package/dist/primitives/collapsible.js +29 -0
- package/dist/primitives/command.d.ts +43 -0
- package/dist/primitives/command.js +98 -0
- package/dist/primitives/context-menu.d.ts +24 -0
- package/dist/primitives/context-menu.js +19 -0
- package/dist/primitives/core.d.ts +25 -0
- package/dist/primitives/core.js +89 -0
- package/dist/primitives/dialog.d.ts +7 -0
- package/dist/primitives/dialog.js +15 -0
- package/dist/primitives/drawer.d.ts +30 -0
- package/dist/primitives/drawer.js +39 -0
- package/dist/primitives/dropdown-menu.d.ts +24 -0
- package/dist/primitives/dropdown-menu.js +19 -0
- package/dist/primitives/form.d.ts +154 -0
- package/dist/primitives/form.js +139 -0
- package/dist/primitives/hover-card.d.ts +4 -0
- package/dist/primitives/hover-card.js +8 -0
- package/dist/primitives/input-otp.d.ts +21 -0
- package/dist/primitives/input-otp.js +78 -0
- package/dist/primitives/label.d.ts +2 -0
- package/dist/primitives/label.js +7 -0
- package/dist/primitives/layer.d.ts +84 -0
- package/dist/primitives/layer.js +300 -0
- package/dist/primitives/menu.d.ts +56 -0
- package/dist/primitives/menu.js +126 -0
- package/dist/primitives/menubar.d.ts +91 -0
- package/dist/primitives/menubar.js +44 -0
- package/dist/primitives/navigation-menu.d.ts +32 -0
- package/dist/primitives/navigation-menu.js +121 -0
- package/dist/primitives/popover.d.ts +4 -0
- package/dist/primitives/popover.js +12 -0
- package/dist/primitives/progress.d.ts +7 -0
- package/dist/primitives/progress.js +13 -0
- package/dist/primitives/radio-group.d.ts +18 -0
- package/dist/primitives/radio-group.js +36 -0
- package/dist/primitives/resizable.d.ts +19 -0
- package/dist/primitives/resizable.js +91 -0
- package/dist/primitives/scroll-area.d.ts +12 -0
- package/dist/primitives/scroll-area.js +26 -0
- package/dist/primitives/select.d.ts +42 -0
- package/dist/primitives/select.js +167 -0
- package/dist/primitives/separator.d.ts +5 -0
- package/dist/primitives/separator.js +7 -0
- package/dist/primitives/slider.d.ts +25 -0
- package/dist/primitives/slider.js +98 -0
- package/dist/primitives/slot.d.ts +1 -0
- package/dist/primitives/slot.js +4 -0
- package/dist/primitives/switch.d.ts +11 -0
- package/dist/primitives/switch.js +22 -0
- package/dist/primitives/tabs.d.ts +23 -0
- package/dist/primitives/tabs.js +38 -0
- package/dist/primitives/toast.d.ts +55 -0
- package/dist/primitives/toast.js +86 -0
- package/dist/primitives/toggle-group.d.ts +24 -0
- package/dist/primitives/toggle-group.js +33 -0
- package/dist/primitives/toggle.d.ts +7 -0
- package/dist/primitives/toggle.js +13 -0
- package/dist/primitives/tooltip.d.ts +7 -0
- package/dist/primitives/tooltip.js +10 -0
- package/dist/style-entry.d.ts +1 -0
- package/dist/style-entry.js +1 -0
- package/dist/styles.css +7016 -0
- package/dist/styles.js +1 -0
- package/dist/theme/index.d.ts +110 -0
- package/dist/theme/index.js +199 -0
- package/licenses/Lucide.txt +15 -0
- package/package.json +67 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ProductOption {
|
|
2
|
+
code: string;
|
|
3
|
+
name: string;
|
|
4
|
+
category: string;
|
|
5
|
+
spec: string;
|
|
6
|
+
unitGroup: string;
|
|
7
|
+
price: string | number;
|
|
8
|
+
}
|
|
9
|
+
export declare function ProductPickerDialog<T extends ProductOption>({ products, exists, initialSelected, onConfirm, onClose, }: {
|
|
10
|
+
/** 商品库(商品主数据) */
|
|
11
|
+
products: T[];
|
|
12
|
+
/** 不允许选择的商品编码,默认 []。 */
|
|
13
|
+
exists?: string[];
|
|
14
|
+
/** 初始勾选的商品编码(编辑/回显已选) */
|
|
15
|
+
initialSelected?: string[];
|
|
16
|
+
/** 确认添加:返回勾选的商品行(含初始勾选的累计结果) */
|
|
17
|
+
onConfirm: (picked: T[]) => void;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo } from "react";
|
|
3
|
+
import { Button } from "./ui/button.js";
|
|
4
|
+
import { Input } from "./ui/input.js";
|
|
5
|
+
import { Badge } from "./ui/badge.js";
|
|
6
|
+
import { Checkbox } from "./ui/checkbox.js";
|
|
7
|
+
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "./ui/table.js";
|
|
8
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "./ui/dialog.js";
|
|
9
|
+
import { PackagePlus, Search } from "../icons/index.js";
|
|
10
|
+
import { cn } from "../lib/utils.js";
|
|
11
|
+
function ProductPickerDialog({
|
|
12
|
+
products,
|
|
13
|
+
exists = [],
|
|
14
|
+
initialSelected,
|
|
15
|
+
onConfirm,
|
|
16
|
+
onClose
|
|
17
|
+
}) {
|
|
18
|
+
const [q, setQ] = useState("");
|
|
19
|
+
const [cat, setCat] = useState("全部分类");
|
|
20
|
+
const [selected, setSelected] = useState((initialSelected || []).filter((id) => !exists.includes(id) && products.some((p) => p.code === id)));
|
|
21
|
+
const existsSet = useMemo(() => new Set(exists), [exists]);
|
|
22
|
+
const selectedSet = useMemo(() => new Set(selected), [selected]);
|
|
23
|
+
const [position, setPosition] = useState({ filter: "", page: 1 });
|
|
24
|
+
const filterKey = `${q}\0${cat}`;
|
|
25
|
+
const categoryCounts = useMemo(() => {
|
|
26
|
+
const counts = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const p of products) if (p.category) counts.set(p.category, (counts.get(p.category) ?? 0) + 1);
|
|
28
|
+
return counts;
|
|
29
|
+
}, [products]);
|
|
30
|
+
const cats = useMemo(() => ["全部分类", ...categoryCounts.keys()], [categoryCounts]);
|
|
31
|
+
const filtered = useMemo(() => {
|
|
32
|
+
const kw = q.trim().toLowerCase();
|
|
33
|
+
return products.filter((p) => (cat === "全部分类" || p.category === cat) && (!kw || p.name.toLowerCase().includes(kw) || p.code.toLowerCase().includes(kw) || p.spec.toLowerCase().includes(kw)));
|
|
34
|
+
}, [products, q, cat]);
|
|
35
|
+
const pageSize = 50;
|
|
36
|
+
const pages = Math.max(1, Math.ceil(filtered.length / pageSize));
|
|
37
|
+
const page = position.filter === filterKey ? Math.min(position.page, pages) : 1;
|
|
38
|
+
const visibleProducts = filtered.slice((page - 1) * pageSize, page * pageSize);
|
|
39
|
+
const selectable = useMemo(() => filtered.filter((p) => !existsSet.has(p.code)).map((p) => p.code), [filtered, existsSet]);
|
|
40
|
+
const allSelected = selectable.length > 0 && selectable.every((c) => selectedSet.has(c));
|
|
41
|
+
function toggle(code) {
|
|
42
|
+
setSelected((prev) => prev.includes(code) ? prev.filter((x) => x !== code) : [...prev, code]);
|
|
43
|
+
}
|
|
44
|
+
function toggleAll() {
|
|
45
|
+
const selectableSet = new Set(selectable);
|
|
46
|
+
setSelected((prev) => allSelected ? prev.filter((id) => !selectableSet.has(id)) : [.../* @__PURE__ */ new Set([...prev, ...selectable])]);
|
|
47
|
+
}
|
|
48
|
+
function confirm() {
|
|
49
|
+
const picked = products.filter((p) => selectedSet.has(p.code) && !existsSet.has(p.code));
|
|
50
|
+
if (picked.length === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
onConfirm(picked);
|
|
54
|
+
}
|
|
55
|
+
return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (v) => !v && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[96vw] sm:max-w-6xl h-[88vh] flex flex-col", children: [
|
|
56
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(PackagePlus, { className: "w-4 h-4 text-primary" }),
|
|
58
|
+
"添加商品",
|
|
59
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground", children: "从商品库按分类 + 名称 / 编码 / 规格快速搜索,勾选后返回给应用" })
|
|
60
|
+
] }) }),
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-muted-foreground absolute left-2.5 top-1/2 -translate-y-1/2" }),
|
|
65
|
+
/* @__PURE__ */ jsx(Input, { value: q, onChange: (e) => setQ(e.target.value), placeholder: "搜索商品名称 / 编码 / 规格...", className: "h-9 text-sm bg-card pl-8", autoFocus: true })
|
|
66
|
+
] }),
|
|
67
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", className: "h-9 text-xs shrink-0", onClick: toggleAll, disabled: selectable.length === 0, children: allSelected ? "清空勾选" : "全选结果" })
|
|
68
|
+
] }),
|
|
69
|
+
/* @__PURE__ */ jsx("div", { className: "flex max-h-24 overflow-y-auto items-center gap-1.5 flex-wrap", children: cats.map((c) => /* @__PURE__ */ jsxs(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
"aria-label": c,
|
|
74
|
+
onClick: () => setCat(c),
|
|
75
|
+
className: cn(
|
|
76
|
+
"px-3 py-1.5 rounded-full text-xs font-medium transition-colors border",
|
|
77
|
+
cat === c ? "bg-primary text-white border-primary" : "bg-card border-border text-muted-foreground hover:border-primary hover:text-primary"
|
|
78
|
+
),
|
|
79
|
+
children: [
|
|
80
|
+
c,
|
|
81
|
+
c !== "全部分类" && /* @__PURE__ */ jsxs("span", { className: cn("ml-1", cat === c ? "text-blue-100" : "text-muted-foreground"), children: [
|
|
82
|
+
"(",
|
|
83
|
+
categoryCounts.get(c),
|
|
84
|
+
")"
|
|
85
|
+
] })
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
c
|
|
89
|
+
)) })
|
|
90
|
+
] }),
|
|
91
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-y-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
93
|
+
/* @__PURE__ */ jsx(TableHead, { className: "w-9" }),
|
|
94
|
+
/* @__PURE__ */ jsx(TableHead, { children: "商品" }),
|
|
95
|
+
/* @__PURE__ */ jsx(TableHead, { children: "分类" }),
|
|
96
|
+
/* @__PURE__ */ jsx(TableHead, { children: "规格" }),
|
|
97
|
+
/* @__PURE__ */ jsx(TableHead, { children: "单位" }),
|
|
98
|
+
/* @__PURE__ */ jsx(TableHead, { className: "text-right", children: "参考价格" })
|
|
99
|
+
] }) }),
|
|
100
|
+
/* @__PURE__ */ jsxs(TableBody, { children: [
|
|
101
|
+
visibleProducts.map((p) => {
|
|
102
|
+
const inScheme = existsSet.has(p.code);
|
|
103
|
+
const on = selectedSet.has(p.code);
|
|
104
|
+
return /* @__PURE__ */ jsxs(TableRow, { className: cn(inScheme && "opacity-55"), children: [
|
|
105
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Checkbox, { "aria-label": `选择${p.name}`, checked: inScheme || on, disabled: inScheme, onCheckedChange: () => toggle(p.code) }) }),
|
|
106
|
+
/* @__PURE__ */ jsxs(TableCell, { className: "text-sm", children: [
|
|
107
|
+
p.name,
|
|
108
|
+
/* @__PURE__ */ jsx("span", { className: "ml-1.5 font-mono text-[10px] text-slate-300", children: p.code }),
|
|
109
|
+
inScheme && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "ml-1.5 text-[10px] bg-muted text-muted-foreground border-border", children: "不可选择" })
|
|
110
|
+
] }),
|
|
111
|
+
/* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-[10px] bg-muted text-muted-foreground border-border", children: p.category }) }),
|
|
112
|
+
/* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: p.spec }),
|
|
113
|
+
/* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: p.unitGroup }),
|
|
114
|
+
/* @__PURE__ */ jsx(TableCell, { className: "text-right text-xs tabular-nums text-muted-foreground", children: p.price })
|
|
115
|
+
] }, p.code);
|
|
116
|
+
}),
|
|
117
|
+
filtered.length === 0 && /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, { colSpan: 6, className: "text-center text-xs text-muted-foreground py-6", children: "当前分类下未搜到匹配商品,换个分类或关键词试试" }) })
|
|
118
|
+
] })
|
|
119
|
+
] }) }),
|
|
120
|
+
/* @__PURE__ */ jsxs("nav", { "aria-label": "商品分页", className: "flex items-center justify-between gap-2 text-xs text-muted-foreground", children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
122
|
+
"共 ",
|
|
123
|
+
filtered.length,
|
|
124
|
+
" 个商品 · 第 ",
|
|
125
|
+
page,
|
|
126
|
+
"/",
|
|
127
|
+
pages,
|
|
128
|
+
" 页 · 每页 ",
|
|
129
|
+
pageSize,
|
|
130
|
+
" 个"
|
|
131
|
+
] }),
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
133
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", "aria-label": "上一页商品", disabled: page <= 1, onClick: () => setPosition({ filter: filterKey, page: page - 1 }), children: "上一页" }),
|
|
134
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", "aria-label": "下一页商品", disabled: page >= pages, onClick: () => setPosition({ filter: filterKey, page: page + 1 }), children: "下一页" })
|
|
135
|
+
] })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxs(DialogFooter, { className: "gap-2", children: [
|
|
138
|
+
/* @__PURE__ */ jsxs("span", { className: "mr-auto text-xs text-muted-foreground", children: [
|
|
139
|
+
"共勾选 ",
|
|
140
|
+
selected.length,
|
|
141
|
+
" 个商品",
|
|
142
|
+
cat !== "全部分类" && ` · 当前分类 ${cat}`
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "h-8 text-xs", onClick: onClose, children: "取消" }),
|
|
145
|
+
/* @__PURE__ */ jsxs(Button, { size: "sm", className: "h-8 text-xs", disabled: selected.length === 0, onClick: confirm, children: [
|
|
146
|
+
"添加选中(",
|
|
147
|
+
selected.length,
|
|
148
|
+
")"
|
|
149
|
+
] })
|
|
150
|
+
] })
|
|
151
|
+
] }) });
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
ProductPickerDialog
|
|
155
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface RefDocItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
sub?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function RefDocPicker({ label, items, value, editable, onPick, onRemove, renderDetail, onOpenDoc, closeOnPick, hint, compact, }: {
|
|
8
|
+
label: string;
|
|
9
|
+
items: RefDocItem[];
|
|
10
|
+
value: string[];
|
|
11
|
+
editable: boolean;
|
|
12
|
+
onPick?: (id: string) => void;
|
|
13
|
+
onRemove?: (id: string) => void;
|
|
14
|
+
renderDetail?: (id: string) => ReactNode;
|
|
15
|
+
/** 给了就=点关联单据直接开它的原生表单(优先于摘要弹窗) */
|
|
16
|
+
onOpenDoc?: (id: string) => void;
|
|
17
|
+
closeOnPick?: boolean;
|
|
18
|
+
hint?: string;
|
|
19
|
+
compact?: boolean;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
export declare function DetailRow({ label: k, children }: {
|
|
22
|
+
label: string;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog.js";
|
|
4
|
+
import { Button } from "./ui/button.js";
|
|
5
|
+
import { FileText, X, Search, FileDown } from "../icons/index.js";
|
|
6
|
+
import { cn } from "../lib/utils.js";
|
|
7
|
+
function RefDocPicker({
|
|
8
|
+
label,
|
|
9
|
+
items,
|
|
10
|
+
value,
|
|
11
|
+
editable,
|
|
12
|
+
onPick,
|
|
13
|
+
onRemove,
|
|
14
|
+
renderDetail,
|
|
15
|
+
onOpenDoc,
|
|
16
|
+
closeOnPick = true,
|
|
17
|
+
hint,
|
|
18
|
+
compact = false
|
|
19
|
+
}) {
|
|
20
|
+
const [pickOpen, setPickOpen] = useState(false);
|
|
21
|
+
const [detailId, setDetailId] = useState(null);
|
|
22
|
+
const labelOf = (id) => items.find((i) => i.id === id)?.label || id;
|
|
23
|
+
const candidates = items.filter((i) => !value.includes(i.id));
|
|
24
|
+
const openDetail = (id) => {
|
|
25
|
+
if (onOpenDoc) onOpenDoc(id);
|
|
26
|
+
else setDetailId(id);
|
|
27
|
+
};
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { className: compact ? "flex items-center gap-1.5 flex-wrap" : "space-y-1.5", children: [
|
|
29
|
+
compact ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground shrink-0", children: label }),
|
|
31
|
+
value.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "未关联" }),
|
|
32
|
+
value.map((id) => /* @__PURE__ */ jsxs("span", { className: cn(
|
|
33
|
+
"inline-flex items-center gap-1 px-2 py-1 rounded-md border text-xs max-w-56",
|
|
34
|
+
"bg-card border-border text-primary hover:border-primary"
|
|
35
|
+
), children: [
|
|
36
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "truncate underline-offset-2 hover:underline", onClick: () => openDetail(id), title: onOpenDoc ? "打开原生表单" : "查看摘要", children: [
|
|
37
|
+
/* @__PURE__ */ jsx(FileText, { className: "w-3 h-3 inline mr-0.5 -mt-0.5 text-primary" }),
|
|
38
|
+
labelOf(id)
|
|
39
|
+
] }),
|
|
40
|
+
editable && onRemove && /* @__PURE__ */ jsx("button", { type: "button", className: "text-slate-300 hover:text-red-500 shrink-0", "aria-label": `移除${labelOf(id)}`, onClick: () => onRemove(id), children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" }) })
|
|
41
|
+
] }, id)),
|
|
42
|
+
editable && onPick && items.length > 0 && /* @__PURE__ */ jsxs(
|
|
43
|
+
"button",
|
|
44
|
+
{
|
|
45
|
+
type: "button",
|
|
46
|
+
onClick: () => setPickOpen(true),
|
|
47
|
+
className: "inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium text-primary hover:bg-primary/5 border border-dashed border-border",
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ jsx(Search, { className: "w-3 h-3" }),
|
|
50
|
+
"选择"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground w-[84px] shrink-0 text-right pt-1.5", children: label }),
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center gap-1.5 flex-wrap min-h-8", children: [
|
|
58
|
+
value.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: items.length > 0 ? "未关联" : "—" }),
|
|
59
|
+
value.map((id) => /* @__PURE__ */ jsxs("span", { className: cn(
|
|
60
|
+
"inline-flex items-center gap-1 px-2 py-1 rounded-md border text-xs max-w-72",
|
|
61
|
+
"bg-card border-border text-primary hover:border-primary"
|
|
62
|
+
), children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "truncate underline-offset-2 hover:underline", onClick: () => openDetail(id), title: onOpenDoc ? "打开原生表单" : "查看摘要", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(FileText, { className: "w-3 h-3 inline mr-0.5 -mt-0.5 text-primary" }),
|
|
65
|
+
labelOf(id)
|
|
66
|
+
] }),
|
|
67
|
+
editable && onRemove && /* @__PURE__ */ jsx("button", { type: "button", className: "text-slate-300 hover:text-red-500 shrink-0", "aria-label": `移除${labelOf(id)}`, onClick: () => onRemove(id), children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" }) })
|
|
68
|
+
] }, id)),
|
|
69
|
+
editable && onPick && items.length > 0 && /* @__PURE__ */ jsxs(
|
|
70
|
+
"button",
|
|
71
|
+
{
|
|
72
|
+
type: "button",
|
|
73
|
+
onClick: () => setPickOpen(true),
|
|
74
|
+
className: "inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-medium text-primary hover:bg-primary/5 border border-dashed border-border",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsx(Search, { className: "w-3 h-3" }),
|
|
77
|
+
"选择关联"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
] })
|
|
82
|
+
] }),
|
|
83
|
+
hint && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground pl-[92px]", children: hint })
|
|
84
|
+
] }),
|
|
85
|
+
/* @__PURE__ */ jsx(Dialog, { open: pickOpen, onOpenChange: setPickOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-xl", children: [
|
|
86
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
|
|
87
|
+
/* @__PURE__ */ jsx(FileDown, { className: "w-4 h-4 text-rose-400" }),
|
|
88
|
+
"选择关联",
|
|
89
|
+
label
|
|
90
|
+
] }) }),
|
|
91
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 max-h-[50vh] overflow-y-auto", children: [
|
|
92
|
+
items.map((it) => {
|
|
93
|
+
const picked = value.includes(it.id);
|
|
94
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
95
|
+
"flex items-center gap-2 rounded border px-3 py-2 text-xs transition-colors",
|
|
96
|
+
picked ? "border-border bg-muted" : "border-border hover:border-primary hover:bg-primary/5"
|
|
97
|
+
), children: [
|
|
98
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-muted-foreground w-36 truncate", children: it.id }),
|
|
99
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground flex-1 truncate", children: it.sub || it.label }),
|
|
100
|
+
picked ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "已关联" }) : /* @__PURE__ */ jsx(Button, { size: "sm", className: "h-7 text-xs bg-primary hover:bg-primary/90", onClick: () => {
|
|
101
|
+
onPick?.(it.id);
|
|
102
|
+
if (closeOnPick) setPickOpen(false);
|
|
103
|
+
}, children: "选择" })
|
|
104
|
+
] }, it.id);
|
|
105
|
+
}),
|
|
106
|
+
items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "text-center text-xs text-muted-foreground py-8", children: [
|
|
107
|
+
"暂无可关联的",
|
|
108
|
+
label
|
|
109
|
+
] }),
|
|
110
|
+
candidates.length === 0 && items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center text-xs text-muted-foreground py-8", children: [
|
|
111
|
+
"可关联的",
|
|
112
|
+
label,
|
|
113
|
+
"均已关联"
|
|
114
|
+
] })
|
|
115
|
+
] })
|
|
116
|
+
] }) }),
|
|
117
|
+
/* @__PURE__ */ jsx(Dialog, { open: !!detailId, onOpenChange: (v) => {
|
|
118
|
+
if (!v) setDetailId(null);
|
|
119
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-md", children: [
|
|
120
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs(DialogTitle, { className: "flex items-center gap-2", children: [
|
|
121
|
+
/* @__PURE__ */ jsx(FileText, { className: "w-4 h-4 text-primary" }),
|
|
122
|
+
label,
|
|
123
|
+
"详情 · ",
|
|
124
|
+
detailId
|
|
125
|
+
] }) }),
|
|
126
|
+
detailId && /* @__PURE__ */ jsx("div", { className: "text-xs space-y-3", children: renderDetail ? renderDetail(detailId) : /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "暂无摘要数据源" }) })
|
|
127
|
+
] }) })
|
|
128
|
+
] });
|
|
129
|
+
}
|
|
130
|
+
function DetailRow({ label: k, children }) {
|
|
131
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-4 border-b border-dashed border-border pb-1.5", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground shrink-0", children: k }),
|
|
133
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground text-right", children })
|
|
134
|
+
] });
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
DetailRow,
|
|
138
|
+
RefDocPicker
|
|
139
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ScrollToTop(): null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
function ScrollToTop() {
|
|
4
|
+
const pathname = useLocation().pathname;
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const container = document.getElementById("main-scroll-container");
|
|
7
|
+
if (container && typeof container.scrollTo === "function") {
|
|
8
|
+
container.scrollTo(0, 0);
|
|
9
|
+
} else if (container) {
|
|
10
|
+
try {
|
|
11
|
+
container.scrollTop = 0;
|
|
12
|
+
} catch {
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}, [pathname]);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
ScrollToTop
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type StateViewKind = 'loading' | 'empty' | 'error' | 'forbidden' | 'not-found' | 'offline';
|
|
3
|
+
export interface StateViewProps {
|
|
4
|
+
kind: StateViewKind;
|
|
5
|
+
/** 覆盖默认标题。 */
|
|
6
|
+
title?: ReactNode;
|
|
7
|
+
/** 覆盖默认说明。 */
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
/** 提供时显示“重试”按钮(error / offline 默认文案)。 */
|
|
10
|
+
onRetry?: () => void;
|
|
11
|
+
/** 额外操作,如“新建”。显示在重试按钮之后。 */
|
|
12
|
+
action?: ReactNode;
|
|
13
|
+
/** 服务端请求 ID,便于用户反馈问题时定位日志。 */
|
|
14
|
+
requestId?: string;
|
|
15
|
+
/** page:页面级(居中、留白大);section:卡片/表格内(紧凑)。默认 section。 */
|
|
16
|
+
size?: 'page' | 'section';
|
|
17
|
+
/** loading 时以骨架行代替旋转图标,适合表格与列表。 */
|
|
18
|
+
skeletonRows?: number;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 统一的数据状态视图:加载、空、失败、无权限、不存在、离线。
|
|
23
|
+
* 各状态使用不同图标、语义色与文案,避免把“无权限”“服务异常”都显示为“暂无数据”。
|
|
24
|
+
* 数据请求与重试逻辑由宿主负责,本组件只负责呈现。
|
|
25
|
+
*/
|
|
26
|
+
export declare function StateView({ kind, title, description, onRetry, action, requestId, size, skeletonRows, className }: StateViewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RotateCcw, WifiOff, FileQuestion, Lock, AlertTriangle, Inbox } from "../icons/index.js";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { Button } from "./ui/button.js";
|
|
5
|
+
import { Skeleton } from "./ui/skeleton.js";
|
|
6
|
+
import { Spinner } from "./ui/spinner.js";
|
|
7
|
+
const presets = {
|
|
8
|
+
loading: { icon: /* @__PURE__ */ jsx(Spinner, {}), title: "加载中…", description: "", tone: "text-primary" },
|
|
9
|
+
empty: { icon: /* @__PURE__ */ jsx(Inbox, {}), title: "暂无数据", description: "当前条件下没有记录,可调整筛选条件后重试。", tone: "text-muted-foreground" },
|
|
10
|
+
error: { icon: /* @__PURE__ */ jsx(AlertTriangle, {}), title: "加载失败", description: "服务暂时不可用,请稍后重试。", tone: "text-destructive" },
|
|
11
|
+
forbidden: { icon: /* @__PURE__ */ jsx(Lock, {}), title: "没有访问权限", description: "当前账号或单位无权查看此内容,如需开通请联系管理员。", tone: "text-warning" },
|
|
12
|
+
"not-found": { icon: /* @__PURE__ */ jsx(FileQuestion, {}), title: "内容不存在", description: "记录可能已被删除,或不在当前单位的可见范围内。", tone: "text-muted-foreground" },
|
|
13
|
+
offline: { icon: /* @__PURE__ */ jsx(WifiOff, {}), title: "网络连接中断", description: "请检查网络后重试。", tone: "text-muted-foreground" }
|
|
14
|
+
};
|
|
15
|
+
function StateView({ kind, title, description, onRetry, action, requestId, size = "section", skeletonRows, className }) {
|
|
16
|
+
const preset = presets[kind];
|
|
17
|
+
const isError = kind === "error" || kind === "offline";
|
|
18
|
+
if (kind === "loading" && skeletonRows) {
|
|
19
|
+
return /* @__PURE__ */ jsx("div", { role: "status", "aria-label": "加载中", "aria-busy": true, className: cn("flex flex-col gap-3 p-4", className), "data-slot": "state-view", "data-kind": kind, children: Array.from({ length: skeletonRows }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full", style: { opacity: 1 - i * (0.6 / skeletonRows) } }, i)) });
|
|
20
|
+
}
|
|
21
|
+
if (kind === "loading") {
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
role: "status",
|
|
26
|
+
"aria-label": "加载中",
|
|
27
|
+
"aria-busy": true,
|
|
28
|
+
"data-slot": "state-view",
|
|
29
|
+
"data-kind": kind,
|
|
30
|
+
className: cn(
|
|
31
|
+
"flex flex-col items-center justify-center text-center text-balance",
|
|
32
|
+
size === "page" ? "min-h-[50vh] gap-4 px-6 py-16" : "gap-3 px-4 py-10",
|
|
33
|
+
className
|
|
34
|
+
),
|
|
35
|
+
children: [
|
|
36
|
+
/* @__PURE__ */ jsx(Spinner, { className: cn("text-primary motion-reduce:animate-none", size === "page" ? "size-8" : "size-7") }),
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { className: "flex max-w-sm flex-col gap-1.5", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("p", { className: cn("font-medium text-foreground/90", size === "page" ? "text-base" : "text-sm"), children: title ?? preset.title }),
|
|
39
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
|
|
40
|
+
] })
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"div",
|
|
47
|
+
{
|
|
48
|
+
role: isError ? "alert" : "status",
|
|
49
|
+
"data-slot": "state-view",
|
|
50
|
+
"data-kind": kind,
|
|
51
|
+
className: cn("flex flex-col items-center justify-center gap-3 text-center text-balance", size === "page" ? "min-h-[50vh] px-6 py-16" : "px-4 py-10", className),
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx("div", { className: cn("grid size-12 place-items-center rounded-xl bg-muted ring-1 ring-border [&_svg:not([class*=size-])]:size-6", preset.tone), children: preset.icon }),
|
|
54
|
+
/* @__PURE__ */ jsxs("div", { className: "flex max-w-sm flex-col gap-1", children: [
|
|
55
|
+
/* @__PURE__ */ jsx("p", { className: cn("font-semibold text-foreground", size === "page" ? "text-lg" : "text-base"), children: title ?? preset.title }),
|
|
56
|
+
(description ?? preset.description) && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description ?? preset.description }),
|
|
57
|
+
requestId && /* @__PURE__ */ jsxs("p", { className: "mt-1 font-mono text-xs text-muted-foreground/80", children: [
|
|
58
|
+
"请求 ID:",
|
|
59
|
+
requestId
|
|
60
|
+
] })
|
|
61
|
+
] }),
|
|
62
|
+
(onRetry || action) && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center justify-center gap-2", children: [
|
|
63
|
+
onRetry && /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, children: [
|
|
64
|
+
/* @__PURE__ */ jsx(RotateCcw, {}),
|
|
65
|
+
"重试"
|
|
66
|
+
] }),
|
|
67
|
+
action
|
|
68
|
+
] })
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
StateView
|
|
75
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ThemeName } from '../theme';
|
|
3
|
+
export interface ThemeSwitcherProps {
|
|
4
|
+
/** 自定义触发器(会被 asChild 包裹);不传时使用幽灵图标按钮。 */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/** 默认触发器是否显示当前主题名称。 */
|
|
7
|
+
showLabel?: boolean;
|
|
8
|
+
/** 菜单对齐方式与方向,默认 end / bottom。 */
|
|
9
|
+
align?: 'start' | 'center' | 'end';
|
|
10
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
11
|
+
/** 菜单标题,默认「界面主题」。 */
|
|
12
|
+
title?: ReactNode;
|
|
13
|
+
/** 是否显示导航栏外观(浅色 / 深色)切换,默认显示。 */
|
|
14
|
+
showNavStyle?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
contentClassName?: string;
|
|
17
|
+
}
|
|
18
|
+
/** 主题色样:外圈页面底色、内圆主色,让用户一眼区分明暗与品牌色。 */
|
|
19
|
+
export declare function ThemeSwatch({ theme, className }: {
|
|
20
|
+
theme: ThemeName;
|
|
21
|
+
className?: string;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* 主题切换器:色彩预设以色样网格呈现,下方可切换导航栏外观;选中即时生效并写入本地记忆。
|
|
25
|
+
* 须位于 ThemeProvider 内;在深色导航栏中使用时请通过 children 传入与导航配色一致的触发器。
|
|
26
|
+
*/
|
|
27
|
+
export declare function ThemeSwitcher({ children, showLabel, align, side, title, showNavStyle, className, contentClassName }: ThemeSwitcherProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { themePresets, useTheme, themeDescriptions, navStyleLabels } from "../theme/index.js";
|
|
3
|
+
import { Button } from "./ui/button.js";
|
|
4
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
|
|
5
|
+
import { Moon, Sun, Palette } from "../icons/index.js";
|
|
6
|
+
import { cn } from "../lib/utils.js";
|
|
7
|
+
function ThemeSwatch({ theme, className }) {
|
|
8
|
+
const t = themePresets[theme];
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
"span",
|
|
11
|
+
{
|
|
12
|
+
"aria-hidden": true,
|
|
13
|
+
"data-slot": "theme-swatch",
|
|
14
|
+
className: cn("relative inline-grid size-5 shrink-0 place-items-center rounded-full border", className),
|
|
15
|
+
style: { background: t.colorBackground, borderColor: t.colorBorder },
|
|
16
|
+
children: /* @__PURE__ */ jsx("span", { className: "size-2.5 rounded-full", style: { background: t.colorPrimary } })
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function NavStylePreview({ style, theme }) {
|
|
21
|
+
const t = themePresets[theme];
|
|
22
|
+
const nav = style === "dark" ? "#14212e" : t.colorSurface;
|
|
23
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "inline-flex h-5 w-7 shrink-0 overflow-hidden rounded-[4px] border", style: { borderColor: t.colorBorder, background: t.colorBackground }, children: /* @__PURE__ */ jsx("span", { className: "h-full w-2.5", style: { background: nav } }) });
|
|
24
|
+
}
|
|
25
|
+
function ThemeSwitcher({ children, showLabel = false, align = "end", side = "bottom", title = "界面主题", showNavStyle = true, className, contentClassName }) {
|
|
26
|
+
const { theme, setTheme, themes, labels, navStyle, setNavStyle } = useTheme();
|
|
27
|
+
const CurrentIcon = theme === "dark" ? Moon : themes.includes("dark") && themes.length === 2 ? Sun : Palette;
|
|
28
|
+
const navLocked = theme === "dark";
|
|
29
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
30
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: children ?? /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", color: "default", size: showLabel ? "sm" : "icon-sm", "aria-label": showLabel ? void 0 : `切换主题(当前:${labels[theme]})`, title: showLabel ? void 0 : `切换主题(当前:${labels[theme]})`, className: cn("text-muted-foreground hover:text-foreground", className), children: [
|
|
31
|
+
/* @__PURE__ */ jsx(CurrentIcon, {}),
|
|
32
|
+
showLabel && /* @__PURE__ */ jsx("span", { children: labels[theme] })
|
|
33
|
+
] }) }),
|
|
34
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align, side, className: cn("w-[19rem] p-2", contentClassName), children: [
|
|
35
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "px-1 pb-1.5 pt-0.5 text-xs font-medium text-muted-foreground", children: title }),
|
|
36
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: theme, onValueChange: (v) => setTheme(v), className: "grid grid-cols-3 gap-1", children: themes.map((name) => /* @__PURE__ */ jsxs(
|
|
37
|
+
DropdownMenuRadioItem,
|
|
38
|
+
{
|
|
39
|
+
value: name,
|
|
40
|
+
"data-theme-option": name,
|
|
41
|
+
title: themeDescriptions[name],
|
|
42
|
+
className: "flex-col items-start gap-1.5 rounded-md border border-transparent px-2 py-2 pl-2 data-[state=checked]:border-primary data-[state=checked]:bg-secondary [&>span:first-child]:hidden",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx(ThemeSwatch, { theme: name, className: "size-6" }),
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs leading-none", children: labels[name] })
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
name
|
|
49
|
+
)) }),
|
|
50
|
+
showNavStyle && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, { className: "my-2" }),
|
|
52
|
+
/* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "px-1 pb-1.5 pt-0 text-xs font-medium text-muted-foreground", children: [
|
|
53
|
+
"导航栏",
|
|
54
|
+
navLocked && /* @__PURE__ */ jsx("span", { className: "ml-1 font-normal", children: "(深色主题下固定为深色)" })
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: navStyle, onValueChange: (v) => setNavStyle(v), className: "grid grid-cols-2 gap-1", children: ["light", "dark"].map((style) => /* @__PURE__ */ jsxs(
|
|
57
|
+
DropdownMenuRadioItem,
|
|
58
|
+
{
|
|
59
|
+
value: style,
|
|
60
|
+
disabled: navLocked,
|
|
61
|
+
"data-nav-option": style,
|
|
62
|
+
className: "gap-2 rounded-md border border-transparent px-2 py-1.5 pl-2 data-[state=checked]:border-primary data-[state=checked]:bg-secondary [&>span:first-child]:hidden",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx(NavStylePreview, { style, theme }),
|
|
65
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs", children: navStyleLabels[style] })
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
style
|
|
69
|
+
)) })
|
|
70
|
+
] })
|
|
71
|
+
] })
|
|
72
|
+
] });
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
ThemeSwatch,
|
|
76
|
+
ThemeSwitcher
|
|
77
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TimeRange = 'today' | 'week' | 'month' | 'quarter' | 'year' | 'custom';
|
|
2
|
+
interface TimeFilterProps {
|
|
3
|
+
value: TimeRange;
|
|
4
|
+
onChange: (value: TimeRange) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function TimeFilter({ value, onChange }: TimeFilterProps): import("react").JSX.Element;
|
|
7
|
+
export {};
|