best-unit 1.2.19 → 1.2.21

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.
Files changed (49) hide show
  1. package/BEST_UNIT_USAGE.md +402 -0
  2. package/dist/best-unit.cjs +13 -13
  3. package/dist/best-unit.js +1352 -1328
  4. package/index.html +13 -0
  5. package/package.json +1 -4
  6. package/src/api/axiosInstance.ts +111 -0
  7. package/src/api/index.ts +136 -0
  8. package/src/api/proxy.ts +11 -0
  9. package/src/components/business/recharge-sdk/components/offline-transfer-form/index.tsx +158 -0
  10. package/src/components/business/recharge-sdk/components/offline-transfer-form/theme.tsx +238 -0
  11. package/src/components/business/recharge-sdk/components/online-recharge-form/index.tsx +199 -0
  12. package/src/components/business/recharge-sdk/components/online-recharge-form/theme.tsx +159 -0
  13. package/src/components/business/recharge-sdk/components/recharge/index.tsx +152 -0
  14. package/src/components/business/recharge-sdk/components/recharge/theme.tsx +68 -0
  15. package/src/components/business/recharge-sdk/index.tsx +37 -0
  16. package/src/components/business/refresh-button/index.tsx +99 -0
  17. package/src/components/business/refresh-button/theme.tsx +58 -0
  18. package/src/components/business/statistical-balance/index.tsx +190 -0
  19. package/src/components/business/statistical-balance/theme.tsx +117 -0
  20. package/src/components/common/button/index.tsx +17 -0
  21. package/src/components/common/button/theme.tsx +56 -0
  22. package/src/components/common/hover-popover/index.tsx +182 -0
  23. package/src/components/common/hover-popover/theme.tsx +39 -0
  24. package/src/components/common/message/index.tsx +321 -0
  25. package/src/components/common/message/theme.tsx +25 -0
  26. package/src/components/common/modal/index.tsx +99 -0
  27. package/src/components/common/modal/theme.tsx +99 -0
  28. package/src/components/common/select/index.tsx +229 -0
  29. package/src/components/common/select/theme.tsx +104 -0
  30. package/src/components/common/upload/index.tsx +140 -0
  31. package/src/components/common/upload/theme.tsx +95 -0
  32. package/src/demo/App.tsx +685 -0
  33. package/src/demo/index.tsx +4 -0
  34. package/src/demo/testBalanceData.tsx +79 -0
  35. package/src/demo/theme-config-example.tsx +1 -0
  36. package/src/local/en.ts +64 -0
  37. package/src/local/index.ts +36 -0
  38. package/src/local/zh.ts +63 -0
  39. package/src/main.ts +26 -0
  40. package/src/types/global.d.ts +146 -0
  41. package/src/types/index.ts +31 -0
  42. package/src/types/preact-custom-element.d.ts +1 -0
  43. package/src/utils/business/index.ts +132 -0
  44. package/src/utils/common/index.ts +8 -0
  45. package/src/vite-env.d.ts +8 -0
  46. package/tsconfig.app.json +33 -0
  47. package/tsconfig.json +15 -0
  48. package/tsconfig.node.json +24 -0
  49. package/vite.config.ts +24 -0
@@ -0,0 +1,99 @@
1
+ import { useState } from "preact/hooks";
2
+ import register from "preact-custom-element";
3
+ import { getRefreshButtonTheme, getRefreshButtonSizeStyles } from "./theme";
4
+ import { refreshBalance } from "@/utils/business";
5
+ import { Button } from "@/components/common/button";
6
+
7
+ interface RefreshButtonProps {
8
+ color?: string;
9
+ size?: "small" | "medium" | "large";
10
+ children?: any; // slot 内容
11
+ }
12
+
13
+ function RefreshButton({
14
+ color,
15
+ size = "medium",
16
+ children,
17
+ }: RefreshButtonProps) {
18
+ const [isSpinning, setIsSpinning] = useState(false);
19
+ const theme = getRefreshButtonTheme(color);
20
+ const sizeStyles = getRefreshButtonSizeStyles(size);
21
+
22
+ const buttonStyle = {
23
+ ...theme,
24
+ ...sizeStyles,
25
+ display: "flex",
26
+ alignItems: "center",
27
+ };
28
+
29
+ // 内联 CSS 动画定义
30
+ const spinAnimation = `
31
+ @keyframes spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
39
+
40
+ .refresh-icon {
41
+ transform-origin: center;
42
+ will-change: transform;
43
+ }
44
+
45
+ /* 隐藏 fallback content */
46
+ :host {
47
+ display: inline-block;
48
+ }
49
+ `;
50
+
51
+ const handleClick = async () => {
52
+ setIsSpinning(true);
53
+ try {
54
+ // 触发刷新事件
55
+ refreshBalance();
56
+ // 确保动画至少显示一段时间,给用户视觉反馈
57
+ await new Promise((resolve) => setTimeout(resolve, 300));
58
+ } finally {
59
+ setIsSpinning(false);
60
+ }
61
+ };
62
+
63
+ return (
64
+ <>
65
+ {/* 注入 CSS 动画 */}
66
+ <style>{spinAnimation}</style>
67
+
68
+ <Button onClick={handleClick} color={color}>
69
+ <div style={buttonStyle}>
70
+ <svg
71
+ className="refresh-icon"
72
+ width={size === "small" ? "14" : size === "large" ? "24" : "18"}
73
+ height={size === "small" ? "14" : size === "large" ? "24" : "18"}
74
+ viewBox="0 0 24 24"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ strokeWidth="2"
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ style={{
81
+ animation: isSpinning ? "spin 0.6s linear infinite" : "none",
82
+ transition: "all 0.2s ease",
83
+ }}
84
+ >
85
+ <path d="M21 2v6h-6"></path>
86
+ <path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
87
+ <path d="M3 22v-6h6"></path>
88
+ <path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
89
+ </svg>
90
+ {children && <span>{children}</span>}
91
+ </div>
92
+ </Button>
93
+ </>
94
+ );
95
+ }
96
+
97
+ register(RefreshButton, "best-refresh-button", [], { shadow: true });
98
+
99
+ export default RefreshButton;
@@ -0,0 +1,58 @@
1
+ import { Theme } from "@/types";
2
+ import { getInitParams } from "@/utils/business";
3
+
4
+ export const refreshButtonThemes = {
5
+ white: {
6
+ background: "#1890ff",
7
+ color: "#fff",
8
+ border: "none",
9
+ borderRadius: 6,
10
+ cursor: "pointer",
11
+ fontWeight: 600,
12
+ transition: "all 0.2s ease",
13
+ },
14
+ dark: {
15
+ background: "#00E8C6",
16
+ color: "#fff",
17
+ border: "none",
18
+ borderRadius: 6,
19
+ cursor: "pointer",
20
+ fontWeight: 600,
21
+ transition: "all 0.2s ease",
22
+ },
23
+ };
24
+
25
+ export function getRefreshButtonTheme(color?: string) {
26
+ const theme = getInitParams<Theme>("theme");
27
+ const whiteTheme = theme === Theme.WHITE;
28
+
29
+ return whiteTheme
30
+ ? {
31
+ ...refreshButtonThemes.white,
32
+ background: color || refreshButtonThemes.white.background,
33
+ }
34
+ : {
35
+ ...refreshButtonThemes.dark,
36
+ background: color || refreshButtonThemes.dark.background,
37
+ };
38
+ }
39
+
40
+ export function getRefreshButtonSizeStyles(size: "small" | "medium" | "large") {
41
+ switch (size) {
42
+ case "small":
43
+ return {
44
+ fontSize: "12px",
45
+ gap: "2px",
46
+ };
47
+ case "large":
48
+ return {
49
+ fontSize: "18px",
50
+ gap: "6px",
51
+ };
52
+ default:
53
+ return {
54
+ fontSize: "14px",
55
+ gap: "4px",
56
+ };
57
+ }
58
+ }
@@ -0,0 +1,190 @@
1
+ import { useState, useEffect } from "preact/hooks";
2
+ import HoverPopover, {
3
+ type PopoverPosition,
4
+ } from "@/components/common/hover-popover";
5
+ import { getBalance } from "@/api";
6
+ import { t } from "@/local";
7
+ import register from "preact-custom-element";
8
+ import { getStatisticalBalanceTheme } from "./theme";
9
+
10
+ function formatNumber(num: number) {
11
+ return num.toLocaleString("en-US", {
12
+ minimumFractionDigits: 2,
13
+ maximumFractionDigits: 2,
14
+ });
15
+ }
16
+
17
+ interface DetailItem {
18
+ label: string;
19
+ value: number;
20
+ color: string;
21
+ dot: string;
22
+ }
23
+
24
+ function StatisticalBalance(props: { popoverPosition?: PopoverPosition }) {
25
+ const [balanceData, setBalanceData] = useState<{
26
+ available: number;
27
+ currency: string;
28
+ symbol: string;
29
+ details: DetailItem[];
30
+ }>({
31
+ available: 0,
32
+ currency: "USD",
33
+ symbol: "$",
34
+ details: [],
35
+ });
36
+
37
+ const fetchBalance = async () => {
38
+ try {
39
+ const balance = await getBalance();
40
+
41
+ // 根据 API 返回的数据构建 balanceData,只保存数值数据
42
+ const details: DetailItem[] = [
43
+ {
44
+ label: "", // 翻译在渲染时处理
45
+ value: balance.totalAmount,
46
+ color: "#15b36b",
47
+ dot: "#15b36b",
48
+ },
49
+ {
50
+ label: "",
51
+ value: balance.frozenAmount,
52
+ color: "#f59e0b",
53
+ dot: "#f59e0b",
54
+ },
55
+ ...(balance.isCredit
56
+ ? [
57
+ {
58
+ label: "",
59
+ value: balance.creditLimit,
60
+ color: "#1890ff",
61
+ dot: "#1890ff",
62
+ },
63
+ {
64
+ label: "",
65
+ value: balance.creditUsed,
66
+ color: "#ff0000",
67
+ dot: "#ff0000",
68
+ },
69
+ ]
70
+ : []),
71
+ {
72
+ label: "",
73
+ value: balance.availableAmount,
74
+ color: "#1890ff",
75
+ dot: "#15b36b",
76
+ },
77
+ ];
78
+
79
+ const newBalanceData = {
80
+ available: balance.availableAmount,
81
+ currency: "USD",
82
+ symbol: "$",
83
+ details,
84
+ };
85
+
86
+ setBalanceData(newBalanceData);
87
+ } catch (err) {
88
+ console.error("获取余额失败:", err);
89
+ // 获取失败时保持默认的 $0 USD 显示
90
+ }
91
+ };
92
+
93
+ useEffect(() => {
94
+ fetchBalance();
95
+ }, []);
96
+
97
+ useEffect(() => {
98
+ // 监听refresh-balance事件
99
+ const handleRefreshBalance = () => {
100
+ fetchBalance();
101
+ };
102
+
103
+ // 添加事件监听器
104
+ document.addEventListener(
105
+ "refresh-balance",
106
+ handleRefreshBalance as EventListener
107
+ );
108
+
109
+ // 清理函数
110
+ return () => {
111
+ document.removeEventListener(
112
+ "refresh-balance",
113
+ handleRefreshBalance as EventListener
114
+ );
115
+ };
116
+ }, []);
117
+
118
+ const theme = getStatisticalBalanceTheme();
119
+
120
+ // 在渲染时动态生成翻译后的详情数据
121
+ const translatedDetails = [
122
+ {
123
+ label: t("真实金额"),
124
+ value: balanceData.details[0]?.value || 0,
125
+ color: "#15b36b",
126
+ dot: "#15b36b",
127
+ },
128
+ {
129
+ label: t("冻结金额"),
130
+ value: balanceData.details[1]?.value || 0,
131
+ color: "#f59e0b",
132
+ dot: "#f59e0b",
133
+ },
134
+ ...(balanceData.details.length > 4
135
+ ? [
136
+ {
137
+ label: t("信用额度"),
138
+ value: balanceData.details[2]?.value || 0,
139
+ color: "#1890ff",
140
+ dot: "#1890ff",
141
+ },
142
+ {
143
+ label: t("已用额度"),
144
+ value: balanceData.details[3]?.value || 0,
145
+ color: "#ff0000",
146
+ dot: "#ff0000",
147
+ },
148
+ ]
149
+ : []),
150
+ {
151
+ label: t("可用余额"),
152
+ value: balanceData.details[balanceData.details.length - 1]?.value || 0,
153
+ color: "#1890ff",
154
+ dot: "#15b36b",
155
+ },
156
+ ];
157
+
158
+ return (
159
+ <HoverPopover
160
+ popover={
161
+ <>
162
+ <div style={theme.popoverTitle}>{t("余额详情")}</div>
163
+ {translatedDetails.map((item) => (
164
+ <div key={item.label} style={theme.detailRow}>
165
+ <span style={theme.detailLabel}>
166
+ <span style={theme.detailDot(item.dot)} />
167
+ {item.label}
168
+ </span>
169
+ <span style={theme.detailValue(item.color)}>
170
+ {balanceData.symbol}
171
+ {formatNumber(item.value)}
172
+ </span>
173
+ </div>
174
+ ))}
175
+ </>
176
+ }
177
+ popoverPosition={props.popoverPosition || "bottom"}
178
+ >
179
+ <div style={theme.main}>
180
+ {balanceData.symbol}
181
+ {formatNumber(balanceData.available)}
182
+ <span style={theme.currency}>{balanceData.currency}</span>
183
+ </div>
184
+ </HoverPopover>
185
+ );
186
+ }
187
+
188
+ register(StatisticalBalance, "best-statistical-balance");
189
+
190
+ export default StatisticalBalance;
@@ -0,0 +1,117 @@
1
+ import { Size, Theme } from "@/types";
2
+ import { getInitParams } from "@/utils/business";
3
+
4
+ // 动态生成主题配置的函数
5
+ function createStatisticalBalanceThemes() {
6
+ const size = getInitParams<Size>("size"); // 每次调用时重新获取最新值
7
+
8
+ return {
9
+ white: {
10
+ popoverTitle: {
11
+ fontSize: size === Size.SMALL ? 12 : 16,
12
+ fontWeight: 600,
13
+ color: "#222",
14
+ marginBottom: size === Size.SMALL ? 12 : 16,
15
+ textAlign: "center",
16
+ },
17
+ detailRow: {
18
+ display: "flex",
19
+ justifyContent: "space-between",
20
+ alignItems: "center",
21
+ padding: size === Size.SMALL ? "6px 0" : "8px 0",
22
+ borderBottom: "1px solid #e5e7eb",
23
+ fontSize: size === Size.SMALL ? 12 : 15,
24
+ },
25
+ detailLabel: {
26
+ display: "flex",
27
+ alignItems: "center",
28
+ color: "#6b7280",
29
+ fontWeight: 500,
30
+ },
31
+ detailDot: (color: string) => ({
32
+ display: "inline-block",
33
+ width: size === Size.SMALL ? 6 : 8,
34
+ height: size === Size.SMALL ? 6 : 8,
35
+ borderRadius: "50%",
36
+ background: color,
37
+ marginRight: size === Size.SMALL ? 6 : 8,
38
+ }),
39
+ detailValue: (color: string) => ({
40
+ color,
41
+ fontWeight: 600,
42
+ fontSize: size === Size.SMALL ? 12 : 15,
43
+ }),
44
+ main: {
45
+ fontSize: size === Size.SMALL ? 18 : 24,
46
+ fontWeight: 800,
47
+ color: "#111827",
48
+ display: "inline-block",
49
+ },
50
+ currency: {
51
+ fontSize: size === Size.SMALL ? 16 : 18,
52
+ color: "#6b7280",
53
+ marginLeft: size === Size.SMALL ? 6 : 8,
54
+ fontWeight: 600,
55
+ },
56
+ },
57
+ dark: {
58
+ popoverTitle: {
59
+ fontSize: size === Size.SMALL ? 14 : 16,
60
+ fontWeight: 600,
61
+ color: "#fff",
62
+ marginBottom: size === Size.SMALL ? 14 : 16,
63
+ textAlign: "center",
64
+ },
65
+ detailRow: {
66
+ display: "flex",
67
+ justifyContent: "space-between",
68
+ alignItems: "center",
69
+ padding: size === Size.SMALL ? "6px 0" : "8px 0",
70
+ borderBottom: "1px solid #374151",
71
+ fontSize: size === Size.SMALL ? 12 : 15,
72
+ },
73
+ detailLabel: {
74
+ display: "flex",
75
+ alignItems: "center",
76
+ color: "#B5B8BE",
77
+ fontWeight: 500,
78
+ },
79
+ detailDot: (color: string) => ({
80
+ display: "inline-block",
81
+ width: size === Size.SMALL ? 6 : 8,
82
+ height: size === Size.SMALL ? 6 : 8,
83
+ borderRadius: "50%",
84
+ background: color,
85
+ marginRight: size === Size.SMALL ? 6 : 8,
86
+ }),
87
+ detailValue: (color: string) => ({
88
+ color,
89
+ fontWeight: 600,
90
+ fontSize: size === Size.SMALL ? 12 : 15,
91
+ }),
92
+ main: {
93
+ fontSize: size === Size.SMALL ? 18 : 24,
94
+ fontWeight: 800,
95
+ color: "#fff",
96
+ display: "inline-block",
97
+ },
98
+ currency: {
99
+ fontSize: size === Size.SMALL ? 16 : 18,
100
+ color: "#B5B8BE",
101
+ marginLeft: size === Size.SMALL ? 6 : 8,
102
+ fontWeight: 600,
103
+ },
104
+ },
105
+ };
106
+ }
107
+
108
+ export function getStatisticalBalanceTheme() {
109
+ const theme = getInitParams<Theme>("theme");
110
+ const whiteTheme = theme === Theme.WHITE;
111
+
112
+ // 每次调用时重新生成主题配置
113
+ const statisticalBalanceThemes = createStatisticalBalanceThemes();
114
+ return whiteTheme
115
+ ? statisticalBalanceThemes.white
116
+ : statisticalBalanceThemes.dark;
117
+ }
@@ -0,0 +1,17 @@
1
+ import type { ComponentChildren } from "preact";
2
+ import { getButtonTheme } from "./theme";
3
+
4
+ interface ButtonProps {
5
+ onClick?: () => void;
6
+ color?: string;
7
+ children: ComponentChildren;
8
+ }
9
+
10
+ export function Button({ onClick, color, children }: ButtonProps) {
11
+ const style = getButtonTheme(color);
12
+ return (
13
+ <button style={style} onClick={onClick} type="button">
14
+ {children}
15
+ </button>
16
+ );
17
+ }
@@ -0,0 +1,56 @@
1
+ import { Size, Theme, type ThemeConfig } from "@/types";
2
+ import { getInitParams } from "@/utils/business";
3
+
4
+ // 动态生成主题配置的函数
5
+ function createButtonThemes() {
6
+ const size = getInitParams<Size>("size"); // 每次调用时重新获取最新值
7
+
8
+ return {
9
+ white: {
10
+ background: "#1890ff",
11
+ color: "#fff",
12
+ border: "none",
13
+ borderRadius: 6,
14
+ padding: size === Size.SMALL ? "6px 10px" : "8px 16px",
15
+ cursor: "pointer",
16
+ fontSize: size === Size.SMALL ? 12 : 16,
17
+ fontWeight: 600,
18
+ },
19
+ dark: {
20
+ background: "#00E8C6",
21
+ color: "#fff",
22
+ border: "none",
23
+ borderRadius: 6,
24
+ padding: size === Size.SMALL ? "6px 10px" : "8px 16px",
25
+ cursor: "pointer",
26
+ fontSize: size === Size.SMALL ? 12 : 16,
27
+ fontWeight: 600,
28
+ },
29
+ };
30
+ }
31
+
32
+ export function getButtonTheme(color?: string) {
33
+ const theme = getInitParams<Theme>("theme");
34
+ const themeConfig = getInitParams<ThemeConfig>("themeConfig");
35
+ const whiteTheme = theme === Theme.WHITE;
36
+
37
+ // 每次调用时重新生成主题配置
38
+ const buttonThemes = createButtonThemes();
39
+
40
+ // 优先使用传入的 color 参数,其次使用 themeConfig 中的配置
41
+ let buttonColor = color;
42
+ if (!buttonColor && themeConfig) {
43
+ const config = whiteTheme ? themeConfig.white : themeConfig.dark;
44
+ buttonColor = config?.color;
45
+ }
46
+
47
+ return whiteTheme
48
+ ? {
49
+ ...buttonThemes.white,
50
+ background: buttonColor || buttonThemes.white.background,
51
+ }
52
+ : {
53
+ ...buttonThemes.dark,
54
+ background: buttonColor || buttonThemes.dark.background,
55
+ };
56
+ }