best-unit 1.0.0 → 1.0.3

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 (48) hide show
  1. package/README.md +1 -0
  2. package/dist/best-unit.cjs +60 -0
  3. package/dist/best-unit.js +3988 -0
  4. package/dist/types/global.d.ts +64 -0
  5. package/dist/types/index.ts +17 -0
  6. package/dist/types/preact-custom-element.d.ts +1 -0
  7. package/index.html +2 -2
  8. package/package.json +14 -7
  9. package/src/api/axiosInstance.ts +94 -0
  10. package/src/api/index.ts +118 -15
  11. package/src/api/proxy.ts +11 -0
  12. package/src/components/business/recharge-sdk/components/Button.tsx +42 -0
  13. package/src/components/business/recharge-sdk/components/OfflineTransferForm.tsx +365 -0
  14. package/src/components/business/recharge-sdk/components/OnlineRechargeForm.tsx +389 -0
  15. package/src/components/business/recharge-sdk/components/Recharge.tsx +288 -0
  16. package/src/components/business/recharge-sdk/index.tsx +39 -0
  17. package/src/components/business/statistical-balance/index.tsx +206 -0
  18. package/src/components/common/HoverPopover.tsx +215 -0
  19. package/src/components/common/Message.tsx +324 -0
  20. package/src/components/common/Select.tsx +278 -0
  21. package/src/components/common/Upload.tsx +207 -0
  22. package/src/demo/App.tsx +429 -0
  23. package/src/demo/index.tsx +4 -0
  24. package/src/local/en.ts +61 -0
  25. package/src/local/index.ts +36 -0
  26. package/src/local/zh.ts +60 -0
  27. package/src/main.ts +10 -0
  28. package/src/types/global.d.ts +64 -0
  29. package/src/types/index.ts +17 -0
  30. package/src/types/preact-custom-element.d.ts +1 -0
  31. package/src/utils/business/index.ts +48 -0
  32. package/src/utils/common/index.ts +8 -0
  33. package/src/vite-env.d.ts +1 -0
  34. package/tsconfig.app.json +31 -0
  35. package/tsconfig.json +15 -0
  36. package/tsconfig.node.json +24 -0
  37. package/vite.config.ts +18 -0
  38. package/example/index.html +0 -24
  39. package/public/vite.svg +0 -1
  40. package/src/App.jsx +0 -6
  41. package/src/api/define-api.ts +0 -49
  42. package/src/javascript.svg +0 -1
  43. package/src/main.jsx +0 -4
  44. package/src/sdk/index.ts +0 -33
  45. package/src/style.css +0 -96
  46. package/src/views/AccountModal.jsx +0 -125
  47. package/src/views/Home.jsx +0 -53
  48. package/vite.config.js +0 -31
@@ -0,0 +1,39 @@
1
+ import { useState } from "preact/hooks";
2
+ import { ThemedButton } from "./components/Button";
3
+ import { Recharge } from "./components/Recharge";
4
+ import register from "preact-custom-element";
5
+ import { createOnlineRecharge } from "../../../api";
6
+ import { t } from "../../../local";
7
+
8
+ export function BestUnit() {
9
+ const [visible, setVisible] = useState(false);
10
+ const handleSubmit = async (form: {
11
+ amount: string;
12
+ rechargeChannel: string;
13
+ currency: string;
14
+ }) => {
15
+ const result = await createOnlineRecharge({
16
+ amount: form.amount,
17
+ currency: form.currency,
18
+ rechargeChannel: form.rechargeChannel,
19
+ });
20
+ window.open(result, "_blank");
21
+ };
22
+
23
+ return (
24
+ <div>
25
+ <ThemedButton onClick={() => setVisible(true)}>
26
+ {t("充值/转账")}
27
+ </ThemedButton>
28
+ <Recharge
29
+ visible={visible}
30
+ onClose={() => setVisible(false)}
31
+ onSubmit={handleSubmit}
32
+ />
33
+ </div>
34
+ );
35
+ }
36
+
37
+ register(BestUnit, "best-recharge", ["theme"], { shadow: false });
38
+
39
+ export default BestUnit;
@@ -0,0 +1,206 @@
1
+ import { useState, useEffect } from "preact/hooks";
2
+ import HoverPopover, { type PopoverPosition } from "../../common/HoverPopover";
3
+ import { getBalance } from "../../../api";
4
+ import { t } from "../../../local";
5
+ import register from "preact-custom-element";
6
+ import { Theme } from "../../../types";
7
+
8
+ function formatNumber(num: number) {
9
+ return num.toLocaleString("en-US", {
10
+ minimumFractionDigits: 2,
11
+ maximumFractionDigits: 2,
12
+ });
13
+ }
14
+
15
+ const balanceTheme = {
16
+ white: {
17
+ popoverTitle: {
18
+ fontSize: 16,
19
+ fontWeight: 600,
20
+ color: "#222",
21
+ marginBottom: 16,
22
+ textAlign: "center",
23
+ },
24
+ detailRow: {
25
+ display: "flex",
26
+ justifyContent: "space-between",
27
+ alignItems: "center",
28
+ padding: "8px 0",
29
+ borderBottom: "1px solid #e5e7eb",
30
+ fontSize: 15,
31
+ },
32
+ detailLabel: {
33
+ display: "flex",
34
+ alignItems: "center",
35
+ color: "#6b7280",
36
+ fontWeight: 500,
37
+ },
38
+ detailDot: (color: string) => ({
39
+ display: "inline-block",
40
+ width: 8,
41
+ height: 8,
42
+ borderRadius: "50%",
43
+ background: color,
44
+ marginRight: 8,
45
+ }),
46
+ detailValue: (color: string) => ({
47
+ color,
48
+ fontWeight: 600,
49
+ fontSize: 15,
50
+ }),
51
+ main: {
52
+ fontSize: 24,
53
+ fontWeight: 800,
54
+ color: "#111827",
55
+ display: "inline-block",
56
+ },
57
+ currency: {
58
+ fontSize: 18,
59
+ color: "#6b7280",
60
+ marginLeft: 8,
61
+ fontWeight: 600,
62
+ },
63
+ },
64
+ dark: {
65
+ popoverTitle: {
66
+ fontSize: 16,
67
+ fontWeight: 600,
68
+ color: "#fff",
69
+ marginBottom: 16,
70
+ textAlign: "center",
71
+ },
72
+ detailRow: {
73
+ display: "flex",
74
+ justifyContent: "space-between",
75
+ alignItems: "center",
76
+ padding: "8px 0",
77
+ borderBottom: "1px solid #23262F",
78
+ fontSize: 15,
79
+ },
80
+ detailLabel: {
81
+ display: "flex",
82
+ alignItems: "center",
83
+ color: "#B5B8BE",
84
+ fontWeight: 500,
85
+ },
86
+ detailDot: (color: string) => ({
87
+ display: "inline-block",
88
+ width: 8,
89
+ height: 8,
90
+ borderRadius: "50%",
91
+ background: color,
92
+ marginRight: 8,
93
+ }),
94
+ detailValue: (color: string) => ({
95
+ color,
96
+ fontWeight: 600,
97
+ fontSize: 15,
98
+ }),
99
+ main: {
100
+ fontSize: 24,
101
+ fontWeight: 800,
102
+ color: "#fff",
103
+ display: "inline-block",
104
+ },
105
+ currency: {
106
+ fontSize: 18,
107
+ color: "#B5B8BE",
108
+ marginLeft: 8,
109
+ fontWeight: 600,
110
+ },
111
+ },
112
+ };
113
+
114
+ function StatisticalBalance(props: { popoverPosition?: PopoverPosition }) {
115
+ const [balanceData, setBalanceData] = useState({
116
+ available: 0,
117
+ currency: "USD",
118
+ symbol: "$",
119
+ details: [
120
+ { label: t("真实金额"), value: 0, color: "#15b36b", dot: "#15b36b" },
121
+ { label: t("冻结金额"), value: 0, color: "#f59e0b", dot: "#f59e0b" },
122
+ { label: t("总可用"), value: 0, color: "#1890ff", dot: "#15b36b" },
123
+ ],
124
+ });
125
+
126
+ useEffect(() => {
127
+ const fetchBalance = async () => {
128
+ try {
129
+ const balance = await getBalance();
130
+
131
+ // 根据 API 返回的数据构建 balanceData
132
+ const newBalanceData = {
133
+ available: balance.availableAmount,
134
+ currency: "USD", // 可以根据实际 API 返回调整
135
+ symbol: "$",
136
+ details: [
137
+ {
138
+ label: t("真实金额"),
139
+ value: balance.availableAmount,
140
+ color: "#15b36b",
141
+ dot: "#15b36b",
142
+ },
143
+ {
144
+ label: t("冻结金额"),
145
+ value: balance.frozenAmount,
146
+ color: "#f59e0b",
147
+ dot: "#f59e0b",
148
+ },
149
+ {
150
+ label: t("总可用"),
151
+ value: balance.totalAmount,
152
+ color: "#1890ff",
153
+ dot: "#15b36b",
154
+ },
155
+ ],
156
+ };
157
+
158
+ setBalanceData(newBalanceData);
159
+ } catch (err) {
160
+ console.error("获取余额失败:", err);
161
+ // 获取失败时保持默认的 $0 USD 显示
162
+ }
163
+ };
164
+
165
+ fetchBalance();
166
+ }, []);
167
+
168
+ const fundUnitParams = JSON.parse(
169
+ sessionStorage.getItem("fund_unit_params") || "{}"
170
+ );
171
+ const whiteTheme = fundUnitParams.theme === Theme.WHITE;
172
+ const theme = balanceTheme[whiteTheme ? "white" : "dark"];
173
+
174
+ return (
175
+ <HoverPopover
176
+ popover={
177
+ <>
178
+ <div style={theme.popoverTitle}>{t("余额详情")}</div>
179
+ {balanceData.details.map((item) => (
180
+ <div key={item.label} style={theme.detailRow}>
181
+ <span style={theme.detailLabel}>
182
+ <span style={theme.detailDot(item.dot)} />
183
+ {item.label}
184
+ </span>
185
+ <span style={theme.detailValue(item.color)}>
186
+ {balanceData.symbol}
187
+ {formatNumber(item.value)}
188
+ </span>
189
+ </div>
190
+ ))}
191
+ </>
192
+ }
193
+ popoverPosition={props.popoverPosition || "bottom"}
194
+ >
195
+ <div style={theme.main}>
196
+ {balanceData.symbol}
197
+ {formatNumber(balanceData.available)}
198
+ <span style={theme.currency}>{balanceData.currency}</span>
199
+ </div>
200
+ </HoverPopover>
201
+ );
202
+ }
203
+
204
+ register(StatisticalBalance, "best-statistical-balance");
205
+
206
+ export default StatisticalBalance;
@@ -0,0 +1,215 @@
1
+ import { useState, useRef } from "preact/hooks";
2
+ import type { FunctionalComponent, JSX } from "preact";
3
+ import { Theme } from "../../types";
4
+
5
+ export type PopoverPosition = "top" | "bottom" | "leftTop" | "rightTop";
6
+ interface HoverPopoverProps {
7
+ popover: JSX.Element;
8
+ children: JSX.Element;
9
+ popoverWidth?: number;
10
+ popoverMinWidth?: number;
11
+ offsetY?: number; // 弹层与目标元素的垂直间距
12
+ offsetX?: number; // 弹层与目标元素的水平间距
13
+ popoverPosition?: "top" | "bottom" | "leftTop" | "rightTop";
14
+ }
15
+
16
+ /**
17
+ * 通用 HoverPopover 组件,支持上下、左上、右上浮层、箭头、位置自适应
18
+ */
19
+ const HoverPopover: FunctionalComponent<HoverPopoverProps> = ({
20
+ popover,
21
+ children,
22
+ popoverWidth = 300,
23
+ popoverMinWidth = 200,
24
+ offsetY = 16,
25
+ offsetX = 16,
26
+ popoverPosition = "top",
27
+ }) => {
28
+ const [show, setShow] = useState(false);
29
+ const [position, setPosition] = useState<
30
+ "top" | "bottom" | "leftTop" | "rightTop"
31
+ >(popoverPosition);
32
+ const ref = useRef<HTMLDivElement>(null);
33
+ const timerRef = useRef<number | null>(null);
34
+
35
+ const handleMouseEnter = () => {
36
+ if (timerRef.current) {
37
+ clearTimeout(timerRef.current);
38
+ timerRef.current = null;
39
+ }
40
+ if (popoverPosition === "top" || popoverPosition === "bottom") {
41
+ if (ref.current) {
42
+ const rect = ref.current.getBoundingClientRect();
43
+ if (popoverPosition === "top" && rect.top < 100) {
44
+ setPosition("bottom");
45
+ } else if (
46
+ popoverPosition === "bottom" &&
47
+ window.innerHeight - rect.bottom < 100
48
+ ) {
49
+ setPosition("top");
50
+ } else {
51
+ setPosition(popoverPosition);
52
+ }
53
+ } else {
54
+ setPosition(popoverPosition);
55
+ }
56
+ } else {
57
+ setPosition(popoverPosition);
58
+ }
59
+ setShow(true);
60
+ };
61
+
62
+ const handleMouseLeave = () => {
63
+ // 延迟关闭,防止鼠标快速移动到弹窗内容时闪烁
64
+ timerRef.current = window.setTimeout(() => {
65
+ setShow(false);
66
+ }, 120);
67
+ };
68
+
69
+ const popoverTheme = {
70
+ white: {
71
+ popover: {
72
+ background: "#fff",
73
+ color: "#222",
74
+ boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
75
+ border: "none",
76
+ },
77
+ arrow: {
78
+ top: "#fff",
79
+ bottom: "#fff",
80
+ left: "#fff",
81
+ right: "#fff",
82
+ },
83
+ },
84
+ dark: {
85
+ popover: {
86
+ background: "#23262F",
87
+ color: "#fff",
88
+ boxShadow: "0 4px 16px rgba(0,0,0,0.32)",
89
+ border: "1px solid #444C5C",
90
+ },
91
+ arrow: {
92
+ top: "#23262F",
93
+ bottom: "#23262F",
94
+ left: "#23262F",
95
+ right: "#23262F",
96
+ },
97
+ },
98
+ };
99
+
100
+ // 弹层定位样式
101
+ const fundUnitParams = JSON.parse(
102
+ sessionStorage.getItem("fund_unit_params") || "{}"
103
+ );
104
+ const whiteTheme = fundUnitParams.theme === Theme.WHITE;
105
+ const theme = popoverTheme[whiteTheme ? "white" : "dark"];
106
+
107
+ let popoverStyle: any = {
108
+ position: "absolute",
109
+ zIndex: 999,
110
+ borderRadius: 6,
111
+ fontSize: 15,
112
+ minWidth: popoverMinWidth,
113
+ width: popoverWidth,
114
+ padding: "8px 14px",
115
+ pointerEvents: "auto",
116
+ textAlign: "center",
117
+ animation: "fadeInUp 0.3s",
118
+ ...theme.popover,
119
+ };
120
+ let arrowStyle: any = {
121
+ position: "absolute",
122
+ zIndex: 11,
123
+ width: 0,
124
+ height: 0,
125
+ };
126
+ if (position === "top") {
127
+ popoverStyle = {
128
+ ...popoverStyle,
129
+ left: "50%",
130
+ top: -48,
131
+ transform: "translateX(-50%)",
132
+ };
133
+ arrowStyle = {
134
+ ...arrowStyle,
135
+ left: "50%",
136
+ bottom: -8,
137
+ transform: "translateX(-50%)",
138
+ borderLeft: "8px solid transparent",
139
+ borderRight: "8px solid transparent",
140
+ borderTop: `8px solid ${theme.arrow.top}`,
141
+ };
142
+ } else if (position === "bottom") {
143
+ popoverStyle = {
144
+ ...popoverStyle,
145
+ left: "50%",
146
+ top: "100%",
147
+ marginTop: offsetY,
148
+ transform: "translateX(-50%)",
149
+ };
150
+ arrowStyle = {
151
+ ...arrowStyle,
152
+ left: "50%",
153
+ top: -8,
154
+ transform: "translateX(-50%)",
155
+ borderLeft: "8px solid transparent",
156
+ borderRight: "8px solid transparent",
157
+ borderBottom: `8px solid ${theme.arrow.bottom}`,
158
+ };
159
+ } else if (position === "leftTop") {
160
+ popoverStyle = {
161
+ ...popoverStyle,
162
+ right: "100%",
163
+ top: 0,
164
+ marginRight: offsetX,
165
+ transform: "none",
166
+ };
167
+ arrowStyle = {
168
+ ...arrowStyle,
169
+ right: -8,
170
+ top: 12,
171
+ borderTop: "8px solid transparent",
172
+ borderBottom: "8px solid transparent",
173
+ borderLeft: `8px solid ${theme.arrow.left}`,
174
+ };
175
+ } else if (position === "rightTop") {
176
+ popoverStyle = {
177
+ ...popoverStyle,
178
+ left: "100%",
179
+ top: 0,
180
+ marginLeft: offsetX,
181
+ transform: "translateY(0)", // 右上角对齐
182
+ };
183
+ arrowStyle = {
184
+ ...arrowStyle,
185
+ left: -8,
186
+ top: 12,
187
+ borderTop: "8px solid transparent",
188
+ borderBottom: "8px solid transparent",
189
+ borderRight: `8px solid ${theme.arrow.right}`,
190
+ };
191
+ }
192
+
193
+ return (
194
+ <div
195
+ ref={ref}
196
+ style={{ position: "relative", display: "inline-block" }}
197
+ onMouseEnter={handleMouseEnter}
198
+ onMouseLeave={handleMouseLeave}
199
+ >
200
+ {children}
201
+ {show && (
202
+ <div
203
+ style={popoverStyle}
204
+ onMouseEnter={handleMouseEnter}
205
+ onMouseLeave={handleMouseLeave}
206
+ >
207
+ {popover}
208
+ <div style={arrowStyle} />
209
+ </div>
210
+ )}
211
+ </div>
212
+ );
213
+ };
214
+
215
+ export default HoverPopover;