@svton/taro-ui 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,680 @@
1
+ // src/components/TabBar/index.tsx
2
+ import React2, { useState, useEffect } from "react";
3
+ import { View, Text } from "@tarojs/components";
4
+ import { usePersistFn } from "@svton/hooks";
5
+ function TabBar(props) {
6
+ var _a;
7
+ const {
8
+ items,
9
+ activeKey: controlledActiveKey,
10
+ defaultActiveKey,
11
+ onChange,
12
+ className = "",
13
+ style,
14
+ indicatorWidth = 48,
15
+ showIndicator = true,
16
+ sticky = true
17
+ } = props;
18
+ const [internalActiveKey, setInternalActiveKey] = useState(defaultActiveKey || ((_a = items[0]) == null ? void 0 : _a.key));
19
+ const activeKey = controlledActiveKey !== void 0 ? controlledActiveKey : internalActiveKey;
20
+ const activeIndex = items.findIndex((item) => item.key === activeKey);
21
+ const indicatorLeft = items.length > 0 ? `${(activeIndex + 0.5) * (100 / items.length)}%` : "50%";
22
+ const handleTabChange = usePersistFn((key, disabled) => {
23
+ if (disabled) return;
24
+ if (controlledActiveKey === void 0) {
25
+ setInternalActiveKey(key);
26
+ }
27
+ onChange == null ? void 0 : onChange(key);
28
+ });
29
+ useEffect(() => {
30
+ if (controlledActiveKey !== void 0) {
31
+ setInternalActiveKey(controlledActiveKey);
32
+ }
33
+ }, [controlledActiveKey]);
34
+ return /* @__PURE__ */ React2.createElement(View, { className: `svton-tab-bar ${sticky ? "sticky" : ""} ${className}`, style }, /* @__PURE__ */ React2.createElement(View, { className: "svton-tab-bar__list" }, items.map((item) => /* @__PURE__ */ React2.createElement(
35
+ View,
36
+ {
37
+ key: item.key,
38
+ className: `svton-tab-bar__item ${activeKey === item.key ? "active" : ""} ${item.disabled ? "disabled" : ""}`,
39
+ onClick: () => handleTabChange(item.key, item.disabled)
40
+ },
41
+ item.render ? item.render() : /* @__PURE__ */ React2.createElement(Text, { className: "svton-tab-bar__text" }, item.label)
42
+ ))), showIndicator && items.length > 0 && /* @__PURE__ */ React2.createElement(View, { className: "svton-tab-bar__indicator-wrapper" }, /* @__PURE__ */ React2.createElement(
43
+ View,
44
+ {
45
+ className: "svton-tab-bar__indicator",
46
+ style: {
47
+ left: indicatorLeft,
48
+ width: `${indicatorWidth}px`,
49
+ marginLeft: `-${indicatorWidth / 2}px`
50
+ }
51
+ }
52
+ )));
53
+ }
54
+
55
+ // src/components/Button/index.tsx
56
+ import React3 from "react";
57
+ import { View as View2, Text as Text2 } from "@tarojs/components";
58
+ import { usePersistFn as usePersistFn2 } from "@svton/hooks";
59
+ function Button(props) {
60
+ const {
61
+ type = "default",
62
+ size = "medium",
63
+ loading = false,
64
+ disabled = false,
65
+ block = false,
66
+ children,
67
+ className = "",
68
+ style,
69
+ onClick
70
+ } = props;
71
+ const handleClick = usePersistFn2(() => {
72
+ if (disabled || loading) return;
73
+ onClick == null ? void 0 : onClick();
74
+ });
75
+ return /* @__PURE__ */ React3.createElement(
76
+ View2,
77
+ {
78
+ className: `svton-button svton-button--${type} svton-button--${size} ${block ? "svton-button--block" : ""} ${disabled ? "svton-button--disabled" : ""} ${loading ? "svton-button--loading" : ""} ${className}`,
79
+ style,
80
+ onClick: handleClick
81
+ },
82
+ loading && /* @__PURE__ */ React3.createElement(View2, { className: "svton-button__loading" }, /* @__PURE__ */ React3.createElement(Text2, { className: "svton-button__loading-icon" }, "\u23F3")),
83
+ /* @__PURE__ */ React3.createElement(Text2, { className: "svton-button__text" }, children)
84
+ );
85
+ }
86
+
87
+ // src/components/List/index.tsx
88
+ import React4 from "react";
89
+ import { View as View3, ScrollView, Text as Text3 } from "@tarojs/components";
90
+ import Taro, { usePullDownRefresh, useReachBottom } from "@tarojs/taro";
91
+ function List(props) {
92
+ const {
93
+ data,
94
+ renderItem,
95
+ keyExtractor = (_, index) => String(index),
96
+ loading = false,
97
+ hasMore = true,
98
+ onRefresh,
99
+ onLoadMore,
100
+ renderEmpty,
101
+ emptyText = "\u6682\u65E0\u6570\u636E",
102
+ loadingText = "\u52A0\u8F7D\u4E2D...",
103
+ noMoreText = "\u6CA1\u6709\u66F4\u591A\u4E86",
104
+ className = "",
105
+ style,
106
+ enableRefresh = true,
107
+ enableLoadMore = true,
108
+ header,
109
+ footer
110
+ } = props;
111
+ usePullDownRefresh(async () => {
112
+ if (!enableRefresh || !onRefresh) {
113
+ Taro.stopPullDownRefresh();
114
+ return;
115
+ }
116
+ try {
117
+ await (onRefresh == null ? void 0 : onRefresh());
118
+ } finally {
119
+ Taro.stopPullDownRefresh();
120
+ }
121
+ });
122
+ useReachBottom(async () => {
123
+ if (!enableLoadMore || !hasMore || loading || !onLoadMore) return;
124
+ await (onLoadMore == null ? void 0 : onLoadMore());
125
+ });
126
+ const renderEmptyContent = () => {
127
+ if (renderEmpty) {
128
+ return renderEmpty();
129
+ }
130
+ return /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__empty" }, /* @__PURE__ */ React4.createElement(Text3, { className: "svton-list__empty-text" }, emptyText));
131
+ };
132
+ const renderLoadingTip = () => {
133
+ if (!loading && !hasMore) {
134
+ return /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__tip svton-list__tip--no-more" }, /* @__PURE__ */ React4.createElement(Text3, null, noMoreText));
135
+ }
136
+ if (loading && data.length > 0) {
137
+ return /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__tip svton-list__tip--loading" }, /* @__PURE__ */ React4.createElement(Text3, null, loadingText));
138
+ }
139
+ return null;
140
+ };
141
+ return /* @__PURE__ */ React4.createElement(ScrollView, { className: `svton-list ${className}`, style, scrollY: true, enableBackToTop: true }, header && /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__header" }, header), data.length === 0 && !loading ? renderEmptyContent() : /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__content" }, data.map((item, index) => /* @__PURE__ */ React4.createElement(View3, { key: keyExtractor(item, index), className: "svton-list__item" }, renderItem(item, index)))), renderLoadingTip(), footer && /* @__PURE__ */ React4.createElement(View3, { className: "svton-list__footer" }, footer));
142
+ }
143
+
144
+ // src/components/NavBar/index.tsx
145
+ import React6, { useEffect as useEffect3, useState as useState3 } from "react";
146
+ import { View as View5, Text as Text4, Image } from "@tarojs/components";
147
+ import Taro3 from "@tarojs/taro";
148
+
149
+ // src/utils/systemInfo.ts
150
+ import Taro2 from "@tarojs/taro";
151
+ var SystemInfoManager = class {
152
+ info = null;
153
+ async init() {
154
+ try {
155
+ const systemInfo = await Taro2.getSystemInfo();
156
+ const statusBarHeight = systemInfo.statusBarHeight || 44;
157
+ const windowWidth = systemInfo.windowWidth || 375;
158
+ const windowHeight = systemInfo.windowHeight || 667;
159
+ const safeArea = systemInfo.safeArea || {
160
+ top: statusBarHeight,
161
+ right: windowWidth,
162
+ bottom: windowHeight,
163
+ left: 0
164
+ };
165
+ const safeAreaInsets = {
166
+ top: safeArea.top,
167
+ right: windowWidth - safeArea.right,
168
+ bottom: windowHeight - safeArea.bottom,
169
+ left: safeArea.left
170
+ };
171
+ let menuButton;
172
+ let navBarHeight = statusBarHeight + 44;
173
+ try {
174
+ if (Taro2.getEnv() === Taro2.ENV_TYPE.WEAPP) {
175
+ menuButton = Taro2.getMenuButtonBoundingClientRect();
176
+ navBarHeight = menuButton.bottom + (menuButton.top - statusBarHeight);
177
+ }
178
+ } catch (e) {
179
+ console.warn("\u83B7\u53D6\u80F6\u56CA\u6309\u94AE\u4FE1\u606F\u5931\u8D25", e);
180
+ }
181
+ this.info = {
182
+ statusBarHeight,
183
+ safeAreaInsets,
184
+ menuButton,
185
+ navBarHeight,
186
+ windowWidth,
187
+ windowHeight
188
+ };
189
+ this.setCSSVariables();
190
+ console.log("\u7CFB\u7EDF\u4FE1\u606F\u521D\u59CB\u5316\u6210\u529F:", this.info);
191
+ return this.info;
192
+ } catch (error) {
193
+ console.error("\u83B7\u53D6\u7CFB\u7EDF\u4FE1\u606F\u5931\u8D25", error);
194
+ this.info = {
195
+ statusBarHeight: 44,
196
+ safeAreaInsets: { top: 44, right: 0, bottom: 0, left: 0 },
197
+ navBarHeight: 88,
198
+ windowWidth: 375,
199
+ windowHeight: 667
200
+ };
201
+ this.setCSSVariables();
202
+ return this.info;
203
+ }
204
+ }
205
+ setCSSVariables() {
206
+ if (!this.info) return;
207
+ try {
208
+ if (typeof document !== "undefined" && document.documentElement) {
209
+ const root = document.documentElement;
210
+ root.style.setProperty("--status-bar-height", `${this.info.statusBarHeight}px`);
211
+ root.style.setProperty("--safe-area-top", `${this.info.safeAreaInsets.top}px`);
212
+ root.style.setProperty("--safe-area-right", `${this.info.safeAreaInsets.right}px`);
213
+ root.style.setProperty("--safe-area-bottom", `${this.info.safeAreaInsets.bottom}px`);
214
+ root.style.setProperty("--safe-area-left", `${this.info.safeAreaInsets.left}px`);
215
+ root.style.setProperty("--nav-bar-height", `${this.info.navBarHeight}px`);
216
+ if (this.info.menuButton) {
217
+ root.style.setProperty("--menu-button-width", `${this.info.menuButton.width}px`);
218
+ root.style.setProperty("--menu-button-height", `${this.info.menuButton.height}px`);
219
+ root.style.setProperty("--menu-button-top", `${this.info.menuButton.top}px`);
220
+ root.style.setProperty("--menu-button-right", `${this.info.menuButton.right}px`);
221
+ root.style.setProperty("--menu-button-bottom", `${this.info.menuButton.bottom}px`);
222
+ root.style.setProperty("--menu-button-left", `${this.info.menuButton.left}px`);
223
+ }
224
+ console.log("CSS \u53D8\u91CF\u8BBE\u7F6E\u6210\u529F");
225
+ }
226
+ } catch (e) {
227
+ console.warn("\u8BBE\u7F6E CSS \u53D8\u91CF\u5931\u8D25", e);
228
+ }
229
+ }
230
+ getInfo() {
231
+ return this.info;
232
+ }
233
+ getStatusBarHeight() {
234
+ var _a;
235
+ return ((_a = this.info) == null ? void 0 : _a.statusBarHeight) || 44;
236
+ }
237
+ getNavBarHeight() {
238
+ var _a;
239
+ return ((_a = this.info) == null ? void 0 : _a.navBarHeight) || 88;
240
+ }
241
+ getSafeAreaInsets() {
242
+ var _a;
243
+ return ((_a = this.info) == null ? void 0 : _a.safeAreaInsets) || { top: 44, right: 0, bottom: 0, left: 0 };
244
+ }
245
+ };
246
+ var systemInfoManager = new SystemInfoManager();
247
+
248
+ // src/components/StatusBar/index.tsx
249
+ import React5, { useEffect as useEffect2, useState as useState2 } from "react";
250
+ import { View as View4 } from "@tarojs/components";
251
+ function StatusBar({ backgroundColor, className = "" }) {
252
+ const [height, setHeight] = useState2(44);
253
+ useEffect2(() => {
254
+ const info = systemInfoManager.getInfo();
255
+ if (info) {
256
+ setHeight(info.statusBarHeight);
257
+ }
258
+ }, []);
259
+ return /* @__PURE__ */ React5.createElement(
260
+ View4,
261
+ {
262
+ className: `status-bar ${className}`,
263
+ style: {
264
+ height: `${height}px`,
265
+ backgroundColor: backgroundColor || "transparent"
266
+ }
267
+ }
268
+ );
269
+ }
270
+
271
+ // src/components/NavBar/index.tsx
272
+ var BACK_ICON_SVG = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMTUgMThMOSAxMkwxNSA2IiBzdHJva2U9IiMzMzMzMzMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=";
273
+ function CustomNavBar({
274
+ title = "",
275
+ showBack = false,
276
+ showClose = false,
277
+ backgroundColor = "#ffffff",
278
+ textColor = "#333333",
279
+ onBack,
280
+ onClose,
281
+ rightContent,
282
+ fixed = false,
283
+ scrollOpacity = 1
284
+ }) {
285
+ const [navBarContentHeight, setNavBarContentHeight] = useState3(44);
286
+ const [statusBarHeight, setStatusBarHeight] = useState3(44);
287
+ const [menuButtonLeft, setMenuButtonLeft] = useState3(0);
288
+ const getBackgroundColor = () => {
289
+ if (scrollOpacity >= 1) {
290
+ return backgroundColor;
291
+ }
292
+ const hex = backgroundColor.replace("#", "");
293
+ const r = parseInt(hex.substring(0, 2), 16);
294
+ const g = parseInt(hex.substring(2, 4), 16);
295
+ const b = parseInt(hex.substring(4, 6), 16);
296
+ return `rgba(${r}, ${g}, ${b}, ${scrollOpacity})`;
297
+ };
298
+ useEffect3(() => {
299
+ const info = systemInfoManager.getInfo();
300
+ if (info) {
301
+ setNavBarContentHeight(info.navBarHeight - info.statusBarHeight);
302
+ setStatusBarHeight(info.statusBarHeight);
303
+ if (info.menuButton) {
304
+ setMenuButtonLeft(info.menuButton.left);
305
+ }
306
+ }
307
+ }, []);
308
+ const handleBack = () => {
309
+ if (onBack) {
310
+ onBack();
311
+ } else {
312
+ const pages = Taro3.getCurrentPages();
313
+ if (pages.length > 1) {
314
+ Taro3.navigateBack().catch((err) => {
315
+ console.warn("\u8FD4\u56DE\u5931\u8D25:", err);
316
+ Taro3.switchTab({ url: "/pages/index/index" }).catch(() => {
317
+ console.error("\u8DF3\u8F6C\u9996\u9875\u5931\u8D25");
318
+ });
319
+ });
320
+ } else {
321
+ Taro3.switchTab({ url: "/pages/index/index" }).catch(() => {
322
+ console.error("\u8DF3\u8F6C\u9996\u9875\u5931\u8D25");
323
+ });
324
+ }
325
+ }
326
+ };
327
+ const handleClose = () => {
328
+ if (onClose) {
329
+ onClose();
330
+ } else {
331
+ const pages = Taro3.getCurrentPages();
332
+ if (pages.length > 1) {
333
+ Taro3.navigateBack().catch((err) => {
334
+ console.warn("\u5173\u95ED\u5931\u8D25:", err);
335
+ Taro3.switchTab({ url: "/pages/index/index" }).catch(() => {
336
+ console.error("\u8DF3\u8F6C\u9996\u9875\u5931\u8D25");
337
+ });
338
+ });
339
+ } else {
340
+ Taro3.switchTab({ url: "/pages/index/index" }).catch(() => {
341
+ console.error("\u8DF3\u8F6C\u9996\u9875\u5931\u8D25");
342
+ });
343
+ }
344
+ }
345
+ };
346
+ const getRightSafeDistance = () => {
347
+ if (menuButtonLeft > 0) {
348
+ const info = systemInfoManager.getInfo();
349
+ if (info) {
350
+ return info.windowWidth - menuButtonLeft + 8;
351
+ }
352
+ }
353
+ return 100;
354
+ };
355
+ const navBarClass = `custom-nav-bar ${fixed ? "fixed" : ""}`;
356
+ const actualBgColor = getBackgroundColor();
357
+ const totalHeight = statusBarHeight + navBarContentHeight;
358
+ const navBarContent = /* @__PURE__ */ React6.createElement(View5, { className: navBarClass, style: { backgroundColor: actualBgColor } }, /* @__PURE__ */ React6.createElement(StatusBar, { backgroundColor: actualBgColor }), /* @__PURE__ */ React6.createElement(
359
+ View5,
360
+ {
361
+ className: "nav-bar-content",
362
+ style: {
363
+ height: `${navBarContentHeight}px`,
364
+ backgroundColor: actualBgColor
365
+ }
366
+ },
367
+ (showBack || showClose) && /* @__PURE__ */ React6.createElement(View5, { className: "nav-left" }, showBack && /* @__PURE__ */ React6.createElement(View5, { className: "nav-btn", onClick: handleBack }, /* @__PURE__ */ React6.createElement(
368
+ Image,
369
+ {
370
+ className: "nav-icon-img back-icon-img",
371
+ src: BACK_ICON_SVG,
372
+ mode: "aspectFit"
373
+ }
374
+ )), showClose && /* @__PURE__ */ React6.createElement(View5, { className: "nav-btn", onClick: handleClose }, /* @__PURE__ */ React6.createElement(Text4, { className: "nav-icon close-icon", style: { color: textColor } }, "\u2715"))),
375
+ title && /* @__PURE__ */ React6.createElement(View5, { className: "nav-title" }, /* @__PURE__ */ React6.createElement(Text4, { className: "title-text", style: { color: textColor } }, title)),
376
+ rightContent && /* @__PURE__ */ React6.createElement(View5, { className: "nav-right", style: { right: `${getRightSafeDistance()}px` } }, rightContent)
377
+ ));
378
+ if (fixed) {
379
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(
380
+ View5,
381
+ {
382
+ className: "nav-bar-placeholder",
383
+ style: { height: `${totalHeight}px` }
384
+ }
385
+ ), navBarContent);
386
+ }
387
+ return navBarContent;
388
+ }
389
+
390
+ // src/components/ImageUploader/index.tsx
391
+ import React7, { useState as useState4 } from "react";
392
+ import { View as View6, Image as Image2 } from "@tarojs/components";
393
+ import Taro4 from "@tarojs/taro";
394
+ function ImageUploader({
395
+ value = [],
396
+ onChange,
397
+ maxCount = 9,
398
+ uploadUrl = process.env.TARO_APP_API + "/upload/image"
399
+ }) {
400
+ const [uploading, setUploading] = useState4(false);
401
+ const handleChooseImage = async () => {
402
+ try {
403
+ const res = await Taro4.chooseImage({
404
+ count: maxCount - value.length,
405
+ sizeType: ["compressed"],
406
+ sourceType: ["album", "camera"]
407
+ });
408
+ setUploading(true);
409
+ const uploadPromises = res.tempFilePaths.map(async (filePath) => {
410
+ const token = Taro4.getStorageSync("token");
411
+ const uploadRes = await Taro4.uploadFile({
412
+ url: uploadUrl,
413
+ filePath,
414
+ name: "file",
415
+ header: {
416
+ Authorization: `Bearer ${token}`
417
+ }
418
+ });
419
+ const data = JSON.parse(uploadRes.data);
420
+ return data.url;
421
+ });
422
+ const urls = await Promise.all(uploadPromises);
423
+ const newImages = [...value, ...urls];
424
+ onChange == null ? void 0 : onChange(newImages);
425
+ Taro4.showToast({
426
+ title: "\u4E0A\u4F20\u6210\u529F",
427
+ icon: "success"
428
+ });
429
+ } catch (error) {
430
+ Taro4.showToast({
431
+ title: error.errMsg || "\u4E0A\u4F20\u5931\u8D25",
432
+ icon: "none"
433
+ });
434
+ } finally {
435
+ setUploading(false);
436
+ }
437
+ };
438
+ const handlePreview = (index) => {
439
+ Taro4.previewImage({
440
+ urls: value,
441
+ current: value[index]
442
+ });
443
+ };
444
+ const handleDelete = (index) => {
445
+ Taro4.showModal({
446
+ title: "\u63D0\u793A",
447
+ content: "\u786E\u5B9A\u5220\u9664\u8FD9\u5F20\u56FE\u7247\u5417\uFF1F",
448
+ success: (res) => {
449
+ if (res.confirm) {
450
+ const newImages = value.filter((_, i) => i !== index);
451
+ onChange == null ? void 0 : onChange(newImages);
452
+ }
453
+ }
454
+ });
455
+ };
456
+ return /* @__PURE__ */ React7.createElement(View6, { className: "image-uploader" }, /* @__PURE__ */ React7.createElement(View6, { className: "image-list" }, value.map((url, index) => /* @__PURE__ */ React7.createElement(View6, { key: index, className: "image-item" }, /* @__PURE__ */ React7.createElement(
457
+ Image2,
458
+ {
459
+ src: url,
460
+ mode: "aspectFill",
461
+ className: "image",
462
+ onClick: () => handlePreview(index)
463
+ }
464
+ ), /* @__PURE__ */ React7.createElement(View6, { className: "delete-btn", onClick: () => handleDelete(index) }, "\xD7"))), value.length < maxCount && /* @__PURE__ */ React7.createElement(
465
+ View6,
466
+ {
467
+ className: `add-btn ${uploading ? "disabled" : ""}`,
468
+ onClick: uploading ? void 0 : handleChooseImage
469
+ },
470
+ uploading ? /* @__PURE__ */ React7.createElement(View6, { className: "loading" }, "\u4E0A\u4F20\u4E2D...") : /* @__PURE__ */ React7.createElement(View6, { className: "add-icon" }, "+")
471
+ )), /* @__PURE__ */ React7.createElement(View6, { className: "tip" }, "\u6700\u591A\u4E0A\u4F20 ", maxCount, " \u5F20\u56FE\u7247"));
472
+ }
473
+
474
+ // src/components/ImageGrid/index.tsx
475
+ import React8 from "react";
476
+ import { View as View7, Image as Image3 } from "@tarojs/components";
477
+ import Taro5 from "@tarojs/taro";
478
+ function ImageGrid({ images, maxCount = 9, onImageClick }) {
479
+ const displayImages = images.slice(0, maxCount);
480
+ const count = displayImages.length;
481
+ const getGridClass = () => {
482
+ if (count === 1) return "grid-single";
483
+ if (count === 2 || count === 4) return "grid-2";
484
+ return "grid-3";
485
+ };
486
+ const handleImageClick = (index) => {
487
+ if (onImageClick) {
488
+ onImageClick(index);
489
+ } else {
490
+ Taro5.previewImage({
491
+ urls: images,
492
+ current: images[index]
493
+ });
494
+ }
495
+ };
496
+ return /* @__PURE__ */ React8.createElement(View7, { className: `image-grid ${getGridClass()}` }, displayImages.map((url, index) => /* @__PURE__ */ React8.createElement(View7, { key: index, className: "image-item", onClick: () => handleImageClick(index) }, /* @__PURE__ */ React8.createElement(
497
+ Image3,
498
+ {
499
+ src: url,
500
+ mode: count === 1 ? "widthFix" : "aspectFill",
501
+ className: "image",
502
+ lazyLoad: true
503
+ }
504
+ ), index === maxCount - 1 && images.length > maxCount && /* @__PURE__ */ React8.createElement(View7, { className: "image-overlay" }, /* @__PURE__ */ React8.createElement(View7, { className: "image-count" }, "+", images.length - maxCount + 1)))));
505
+ }
506
+
507
+ // src/components/Tabs/index.tsx
508
+ import { View as View8, Text as Text5 } from "@tarojs/components";
509
+ function Tabs({ activeKey, items, onChange, className = "" }) {
510
+ const handleItemClick = (e) => {
511
+ const key = e.currentTarget.dataset.key;
512
+ if (key && key !== activeKey) {
513
+ onChange(key);
514
+ }
515
+ };
516
+ return /* @__PURE__ */ React.createElement(View8, { className: `svton-tabs ${className}` }, items.map((item) => /* @__PURE__ */ React.createElement(
517
+ View8,
518
+ {
519
+ key: item.key,
520
+ "data-key": item.key,
521
+ className: `svton-tabs__item ${activeKey === item.key ? "svton-tabs__item--active" : ""}`,
522
+ onClick: handleItemClick
523
+ },
524
+ /* @__PURE__ */ React.createElement(Text5, { className: "svton-tabs__label" }, item.label),
525
+ item.count !== void 0 && /* @__PURE__ */ React.createElement(Text5, { className: "svton-tabs__count" }, "(", item.count, ")")
526
+ )));
527
+ }
528
+ var Tabs_default = Tabs;
529
+
530
+ // src/components/ContentActionBar/index.tsx
531
+ import React9, { useState as useState5 } from "react";
532
+ import { View as View9, Text as Text6, Textarea, Image as Image4 } from "@tarojs/components";
533
+ var ICONS = {
534
+ // 点赞图标(未点赞) - 空心心形
535
+ like: "https://miaoduo.fbcontent.cn/private/resource/image/19a9ba374ebbee0-0aa9c734-e868-4861-9bfd-37de7ed3a123.svg",
536
+ // 点赞图标(已点赞) - 实心红色心形
537
+ liked: "https://miaoduo.fbcontent.cn/private/resource/image/19a9d0909e270e7-fdf153f2-b3d2-4800-a63a-213c242beed8.svg",
538
+ // 收藏图标(未收藏) - 星形
539
+ favorite: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMTIgMi41TDE1LjA5IDguMjZMMjEuMTggOS4yN0wxNi41OSAxMy45N0wxNy42NCAyMEwxMiAxNy4yN0w2LjM2IDIwTDcuNDEgMTMuOTdMMi44MiA5LjI3TDguOTEgOC4yNkwxMiAyLjV6IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+",
540
+ // 收藏图标(已收藏) - 金黄色星形
541
+ favorited: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMTIgMi41TDE1LjA5IDguMjZMMjEuMTggOS4yN0wxNi41OSAxMy45N0wxNy42NCAyMEwxMiAxNy4yN0w2LjM2IDIwTDcuNDEgMTMuOTdMMi44MiA5LjI3TDguOTEgOC4yNkwxMiAyLjV6IiBmaWxsPSIjRkZDQzAwIiBzdHJva2U9IiNGRkNDMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+",
542
+ // 分享图标
543
+ share: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjUiIHI9IjMiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8Y2lyY2xlIGN4PSI2IiBjeT0iMTIiIHI9IjMiIHN0cm9rZT0iIzMzMyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjE5IiByPSIzIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgPHBhdGggZD0iTTguNTkgMTMuNTFMMTUuNDIgMTcuNDlNMTUuNDEgNi41MUw4LjU5IDEwLjQ5IiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41Ii8+Cjwvc3ZnPg=="
544
+ };
545
+ function ContentActionBar({
546
+ onComment,
547
+ onLike,
548
+ onFavorite,
549
+ onShare,
550
+ liked = false,
551
+ favorited = false,
552
+ placeholder = "\u8BF4\u70B9\u4EC0\u4E48...",
553
+ maxLength = 500,
554
+ disabled = false
555
+ }) {
556
+ const [isExpanded, setIsExpanded] = useState5(false);
557
+ const [inputValue, setInputValue] = useState5("");
558
+ const [submitting, setSubmitting] = useState5(false);
559
+ const handleExpand = () => {
560
+ if (disabled) return;
561
+ setIsExpanded(true);
562
+ };
563
+ const handleCollapse = () => {
564
+ setIsExpanded(false);
565
+ setInputValue("");
566
+ };
567
+ const handleSubmit = async () => {
568
+ if (!inputValue.trim() || submitting || disabled) return;
569
+ setSubmitting(true);
570
+ try {
571
+ await (onComment == null ? void 0 : onComment(inputValue.trim()));
572
+ handleCollapse();
573
+ } catch (error) {
574
+ console.error("\u8BC4\u8BBA\u5931\u8D25:", error);
575
+ } finally {
576
+ setSubmitting(false);
577
+ }
578
+ };
579
+ const handleLike = (e) => {
580
+ e.stopPropagation();
581
+ if (disabled) return;
582
+ onLike == null ? void 0 : onLike();
583
+ };
584
+ const handleFavorite = (e) => {
585
+ e.stopPropagation();
586
+ if (disabled) return;
587
+ onFavorite == null ? void 0 : onFavorite();
588
+ };
589
+ const handleShare = (e) => {
590
+ e.stopPropagation();
591
+ if (disabled) return;
592
+ onShare == null ? void 0 : onShare();
593
+ };
594
+ return /* @__PURE__ */ React9.createElement(View9, { className: "content-action-bar" }, !isExpanded ? (
595
+ // 收起状态:输入框占位 + 操作按钮
596
+ /* @__PURE__ */ React9.createElement(View9, { className: "action-bar-collapsed" }, /* @__PURE__ */ React9.createElement(View9, { className: "input-placeholder", onClick: handleExpand }, /* @__PURE__ */ React9.createElement(Text6, { className: "placeholder-text" }, placeholder)), /* @__PURE__ */ React9.createElement(View9, { className: "action-buttons" }, /* @__PURE__ */ React9.createElement(
597
+ View9,
598
+ {
599
+ className: `action-btn ${liked ? "active" : ""}`,
600
+ onClick: handleLike
601
+ },
602
+ /* @__PURE__ */ React9.createElement(
603
+ Image4,
604
+ {
605
+ className: `action-icon-img ${liked ? "liked" : ""}`,
606
+ src: liked ? ICONS.liked : ICONS.like,
607
+ mode: "aspectFit"
608
+ }
609
+ )
610
+ ), /* @__PURE__ */ React9.createElement(
611
+ View9,
612
+ {
613
+ className: `action-btn ${favorited ? "active" : ""}`,
614
+ onClick: handleFavorite
615
+ },
616
+ /* @__PURE__ */ React9.createElement(
617
+ Image4,
618
+ {
619
+ className: `action-icon-img ${favorited ? "favorited" : ""}`,
620
+ src: favorited ? ICONS.favorited : ICONS.favorite,
621
+ mode: "aspectFit"
622
+ }
623
+ )
624
+ ), /* @__PURE__ */ React9.createElement(View9, { className: "action-btn", onClick: handleShare }, /* @__PURE__ */ React9.createElement(
625
+ Image4,
626
+ {
627
+ className: "action-icon-img",
628
+ src: ICONS.share,
629
+ mode: "aspectFit"
630
+ }
631
+ ))))
632
+ ) : (
633
+ // 展开状态:多行输入框 + 发送按钮
634
+ /* @__PURE__ */ React9.createElement(View9, { className: "action-bar-expanded" }, /* @__PURE__ */ React9.createElement(
635
+ Textarea,
636
+ {
637
+ className: "comment-textarea",
638
+ value: inputValue,
639
+ onInput: (e) => setInputValue(e.detail.value),
640
+ placeholder,
641
+ maxlength: maxLength,
642
+ autoHeight: true,
643
+ focus: true,
644
+ disabled
645
+ }
646
+ ), /* @__PURE__ */ React9.createElement(View9, { className: "expanded-actions" }, /* @__PURE__ */ React9.createElement(Text6, { className: "char-count" }, inputValue.length, "/", maxLength), /* @__PURE__ */ React9.createElement(View9, { className: "action-btns" }, /* @__PURE__ */ React9.createElement(View9, { className: "cancel-btn", onClick: handleCollapse }, /* @__PURE__ */ React9.createElement(Text6, { className: "btn-text" }, "\u53D6\u6D88")), /* @__PURE__ */ React9.createElement(
647
+ View9,
648
+ {
649
+ className: `send-btn ${inputValue.trim() && !submitting ? "active" : "disabled"}`,
650
+ onClick: handleSubmit
651
+ },
652
+ /* @__PURE__ */ React9.createElement(Text6, { className: "btn-text" }, submitting ? "\u53D1\u9001\u4E2D..." : "\u53D1\u9001")
653
+ ))))
654
+ ));
655
+ }
656
+
657
+ // src/hooks/useScrollOpacity.ts
658
+ import { useState as useState6, useCallback } from "react";
659
+ function useScrollOpacity(threshold = 200) {
660
+ const [scrollOpacity, setScrollOpacity] = useState6(1);
661
+ const handleScroll = useCallback((scrollTop) => {
662
+ const opacity = Math.max(1 - scrollTop / threshold, 0);
663
+ setScrollOpacity(opacity);
664
+ }, [threshold]);
665
+ return [scrollOpacity, handleScroll];
666
+ }
667
+ export {
668
+ Button,
669
+ ContentActionBar,
670
+ ImageGrid,
671
+ ImageUploader,
672
+ List,
673
+ CustomNavBar as NavBar,
674
+ StatusBar,
675
+ TabBar,
676
+ Tabs,
677
+ Tabs_default as TabsDefault,
678
+ systemInfoManager,
679
+ useScrollOpacity
680
+ };