best-unit 0.0.13 → 0.0.15

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.
@@ -0,0 +1,104 @@
1
+ import type { FunctionalComponent } from "preact";
2
+ import { useState, useRef, useEffect } from "preact/hooks";
3
+
4
+ interface Option {
5
+ value: string;
6
+ label: string;
7
+ }
8
+
9
+ interface SelectProps {
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ options: Option[];
13
+ placeholder?: string;
14
+ style?: any;
15
+ error?: boolean;
16
+ }
17
+
18
+ export const Select: FunctionalComponent<SelectProps> = ({
19
+ value,
20
+ onChange,
21
+ options,
22
+ placeholder,
23
+ style,
24
+ error,
25
+ }) => {
26
+ const [open, setOpen] = useState(false);
27
+ const ref = useRef<HTMLDivElement>(null);
28
+
29
+ useEffect(() => {
30
+ const handleClick = (e: MouseEvent) => {
31
+ if (ref.current && !ref.current.contains(e.target as Node)) {
32
+ setOpen(false);
33
+ }
34
+ };
35
+ document.addEventListener("mousedown", handleClick);
36
+ return () => document.removeEventListener("mousedown", handleClick);
37
+ }, []);
38
+
39
+ const selected = options.find((opt) => opt.value === value);
40
+
41
+ return (
42
+ <div ref={ref} style={{ position: "relative", ...style }}>
43
+ <div
44
+ onClick={() => setOpen((o) => !o)}
45
+ style={{
46
+ minHeight: 40,
47
+ border: error ? "1px solid #ff4d4f" : "1px solid #d9d9d9",
48
+ borderRadius: 6,
49
+ background: "#fff",
50
+ display: "flex",
51
+ alignItems: "center",
52
+ padding: "0 12px",
53
+ fontSize: 15,
54
+ color: value ? "#222" : "#bfbfbf",
55
+ cursor: "pointer",
56
+ boxSizing: "border-box",
57
+ transition: "border 0.2s",
58
+ ...style,
59
+ }}
60
+ >
61
+ <span style={{ flex: 1 }}>
62
+ {selected ? selected.label : placeholder || "请选择"}
63
+ </span>
64
+ <span style={{ marginLeft: 8, fontSize: 12, color: "#bfbfbf" }}>▼</span>
65
+ </div>
66
+ {open && (
67
+ <div
68
+ style={{
69
+ position: "absolute",
70
+ left: 0,
71
+ right: 0,
72
+ top: 44,
73
+ zIndex: 10,
74
+ background: "#fff",
75
+ border: "1px solid #d9d9d9",
76
+ borderRadius: 6,
77
+ boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
78
+ maxHeight: 220,
79
+ overflowY: "auto",
80
+ }}
81
+ >
82
+ {options.map((opt) => (
83
+ <div
84
+ key={opt.value}
85
+ onClick={() => {
86
+ setOpen(false);
87
+ onChange(opt.value);
88
+ }}
89
+ style={{
90
+ padding: "10px 16px",
91
+ cursor: "pointer",
92
+ color: "#222",
93
+ background: value === opt.value ? "#f5f5f5" : "#fff",
94
+ fontWeight: value === opt.value ? 600 : 400,
95
+ }}
96
+ >
97
+ {opt.label}
98
+ </div>
99
+ ))}
100
+ </div>
101
+ )}
102
+ </div>
103
+ );
104
+ };
@@ -0,0 +1,153 @@
1
+ import type { FunctionalComponent } from "preact";
2
+ import { useRef, useState } from "preact/hooks";
3
+ import { uploadFile } from "../../api";
4
+
5
+ interface UploadProps {
6
+ value?: string[];
7
+ onChange?: (urls: string[]) => void;
8
+ maxCount?: number;
9
+ accept?: string;
10
+ multiple?: boolean;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export const Upload: FunctionalComponent<UploadProps> = ({
15
+ value = [],
16
+ onChange,
17
+ maxCount = 10,
18
+ accept = ".jpg,.jpeg,.png,.pdf",
19
+ multiple = true,
20
+ disabled = false,
21
+ }) => {
22
+ const fileInputRef = useRef<HTMLInputElement>(null);
23
+ const [uploading, setUploading] = useState(false);
24
+ const [progress, setProgress] = useState(0);
25
+
26
+ const handleFileChange = async (e: any) => {
27
+ const files: File[] = Array.from(e.target.files as FileList).slice(
28
+ 0,
29
+ maxCount
30
+ );
31
+ if (!files.length) return;
32
+ setUploading(true);
33
+ const urls: string[] = [];
34
+ for (let i = 0; i < files.length; i++) {
35
+ setProgress(0);
36
+ const formData = new FormData();
37
+ formData.append("file", files[i]);
38
+ try {
39
+ const url = await uploadFile(formData, (percent) =>
40
+ setProgress(percent)
41
+ );
42
+ if (url) urls.push(url);
43
+ } catch (err) {
44
+ // 可扩展错误处理
45
+ }
46
+ }
47
+ setUploading(false);
48
+ setProgress(0);
49
+ onChange?.([...value, ...urls]);
50
+ if (fileInputRef.current) fileInputRef.current.value = "";
51
+ };
52
+
53
+ const handleRemove = (idx: number) => {
54
+ const newArr = value.filter((_, i) => i !== idx);
55
+ onChange?.(newArr);
56
+ };
57
+
58
+ return (
59
+ <div>
60
+ <div
61
+ style={{
62
+ border: "1px dashed #E5E6EB",
63
+ borderRadius: 8,
64
+ background: "#FCFCFD",
65
+ padding: 24,
66
+ textAlign: "center",
67
+ cursor: disabled ? "not-allowed" : "pointer",
68
+ minHeight: 120,
69
+ display: "flex",
70
+ flexDirection: "column",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ color: "#999",
74
+ fontSize: 15,
75
+ opacity: disabled ? 0.6 : 1,
76
+ }}
77
+ onClick={() => !disabled && fileInputRef.current?.click()}
78
+ >
79
+ <div style={{ fontSize: 48, marginBottom: 12 }}>📁</div>
80
+ <div style={{ color: "#222", fontSize: 15, marginBottom: 4 }}>
81
+ 点击或拖拽文件到此处上传
82
+ </div>
83
+ <div style={{ color: "#999", fontSize: 13 }}>
84
+ 支持 JPG、PNG、PDF 格式,单个文件不超过 20MB,最多上传 {maxCount}{" "}
85
+ 个文件
86
+ </div>
87
+ <input
88
+ ref={fileInputRef}
89
+ type="file"
90
+ multiple={multiple}
91
+ accept={accept}
92
+ style={{ display: "none" }}
93
+ onChange={handleFileChange}
94
+ disabled={disabled}
95
+ />
96
+ {uploading && (
97
+ <div style={{ marginTop: 12, color: "#1677ff" }}>
98
+ 正在上传... {progress}%
99
+ </div>
100
+ )}
101
+ </div>
102
+ {value && value.length > 0 && (
103
+ <div style={{ marginTop: 12 }}>
104
+ {value.map((url, idx) => (
105
+ <div
106
+ key={url}
107
+ style={{
108
+ display: "flex",
109
+ alignItems: "center",
110
+ background: "#F7F8FA",
111
+ borderRadius: 8,
112
+ padding: "12px 16px",
113
+ marginBottom: 8,
114
+ fontSize: 15,
115
+ color: "#222",
116
+ justifyContent: "space-between",
117
+ }}
118
+ >
119
+ <div style={{ display: "flex", alignItems: "center", flex: 1 }}>
120
+ <span style={{ fontWeight: 500, wordBreak: "break-all" }}>
121
+ {url.split("/").pop()}
122
+ </span>
123
+ <span style={{ color: "#8C8F93", fontSize: 13, marginLeft: 8 }}>
124
+ [已上传]
125
+ </span>
126
+ </div>
127
+ <button
128
+ type="button"
129
+ onClick={(e) => {
130
+ e.stopPropagation();
131
+ handleRemove(idx);
132
+ }}
133
+ style={{
134
+ background: "#FF4D4F",
135
+ color: "#fff",
136
+ border: "none",
137
+ borderRadius: 6,
138
+ padding: "4px 14px",
139
+ fontSize: 15,
140
+ marginLeft: 16,
141
+ cursor: "pointer",
142
+ }}
143
+ disabled={disabled}
144
+ >
145
+ 移除
146
+ </button>
147
+ </div>
148
+ ))}
149
+ </div>
150
+ )}
151
+ </div>
152
+ );
153
+ };
package/src/demo/App.tsx CHANGED
@@ -1,10 +1,21 @@
1
1
  import { npmTest, printCurrentTime } from "../main";
2
- import { BestUnit } from "../components/ModalForm";
2
+ import { BestUnit } from "../components/business/recharge-sdk";
3
+ import { initFundUnit } from "../main";
4
+ import StatisticalBalance from "../components/business/statistical-balance";
3
5
 
4
6
  export default function DemoApp() {
7
+ initFundUnit({
8
+ token:
9
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTM0MTM1MjksIm1lcmNoYW50X2lkIjoxMTI4LCJ0aW1lc3RhbXAiOjE3NTMxNTQzMjl9.UAvzq0P4HCnbJR1Ga3CgF6q3vk2RHiZRvnAFohBTHpw",
10
+ merchant_id: "1128",
11
+ biz_type: "ad",
12
+ user_id: "123",
13
+ });
14
+
5
15
  return (
6
16
  <div>
7
17
  <h2>组件库可视化测试</h2>
18
+
8
19
  <div>
9
20
  <h3>BestUnit 组件演示:</h3>
10
21
  <BestUnit
@@ -21,6 +32,10 @@ export default function DemoApp() {
21
32
  </button>
22
33
  <button onClick={printCurrentTime}>调用 printCurrentTime()</button>
23
34
  </div>
35
+ <div>
36
+ <h3>余额卡片组件演示:</h3>
37
+ <StatisticalBalance />
38
+ </div>
24
39
  </div>
25
40
  );
26
41
  }
package/src/main.ts CHANGED
@@ -1,14 +1,13 @@
1
+ import { npmTest, printCurrentTime } from "./utils/common/index";
2
+ import { initFundUnit } from "./utils/business/index";
1
3
 
2
- import { npmTest, printCurrentTime } from './index.ts'
3
- import './components/ModalForm'
4
+ import "./components/business/recharge-sdk";
5
+ import "./components/business/statistical-balance";
4
6
 
5
7
  // 可选:导出组件列表或版本信息
6
- export const components = ['x-greeting', 'x-best-modal-form'];
7
- export {
8
- npmTest,
9
- printCurrentTime,
10
- }
11
-
12
-
13
-
14
-
8
+ export const components = [
9
+ "x-greeting",
10
+ "x-best-modal-form",
11
+ "best-statistical-balance",
12
+ ];
13
+ export { npmTest, printCurrentTime, initFundUnit };
@@ -0,0 +1,27 @@
1
+ export function initFundUnit(params: {
2
+ token: string;
3
+ merchant_id: string;
4
+ biz_type: string;
5
+ user_id: string;
6
+ theme?: string;
7
+ }) {
8
+ const { merchant_id, biz_type, theme, user_id } = params;
9
+ const token = "Bearer " + params.token;
10
+ sessionStorage.setItem(
11
+ "fund_unit_params",
12
+ JSON.stringify({
13
+ merchantId: merchant_id,
14
+ bizType: biz_type,
15
+ userId: user_id,
16
+ token,
17
+ theme,
18
+ })
19
+ );
20
+ return {
21
+ token,
22
+ merchantId: merchant_id,
23
+ bizType: biz_type,
24
+ theme,
25
+ userId: user_id,
26
+ };
27
+ }
File without changes