Procházet zdrojové kódy

✨ feat(toast): 创建TDesign Toast组件并添加样式

- 创建`mini/src/components/tdesign/toast/index.tsx`组件文件
- 实现Toast组件基础功能,支持loading/success/error/warning等主题
- 添加不同方向(row/column)和位置(top/middle/bottom)的布局支持
- 在`tcb-theme.css`中添加Toast组件样式
- 更新文档中Toast组件的任务状态和开发记录
yourname před 1 měsícem
rodič
revize
9d611d7f87

+ 6 - 2
docs/stories/001.004.homepage-ui-refactor.md

@@ -21,7 +21,7 @@ Draft
     - [x] 创建 `mini/src/components/tdesign/icon/index.tsx` - Icon组件 (对照: `mini/tdesign/icon/`)
     - [x] 创建 `mini/src/components/tdesign/search/index.tsx` - Search组件 (对照: `mini/tdesign/search/`)
     - [x] 创建 `mini/src/components/tdesign/swiper/index.tsx` - Swiper组件 (对照: `mini/tdesign/swiper/`)
-    - [ ] 创建 `mini/src/components/tdesign/toast/index.tsx` - Toast组件 (对照: `mini/tdesign/toast/`)
+    - [x] 创建 `mini/src/components/tdesign/toast/index.tsx` - Toast组件 (对照: `mini/tdesign/toast/`)
     - [ ] 创建 `mini/src/components/tdesign/tabs/index.tsx` - Tabs组件 (对照: `mini/tdesign/tabs/`)
 - [ ] 实现商品卡片组件 (AC: 3)
   - [ ] 创建 `mini/src/components/goods-card/index.tsx` 商品卡片组件
@@ -296,11 +296,14 @@ export default function TDesignSwiper({
 - ✅ 已完成:创建 `mini/src/components/tdesign/search/index.tsx` - Search组件
 - ✅ 已完成:创建 `mini/src/components/tdesign/icon/index.tsx` - Icon组件
 - ✅ 已完成:创建 `mini/src/components/tdesign/swiper/index.tsx` - Swiper组件
+- ✅ 已完成:创建 `mini/src/components/tdesign/toast/index.tsx` - Toast组件
 - ✅ 已对照:`mini/tdesign/search/` 目录结构和功能
 - ✅ 已对照:`mini/tdesign/icon/` 目录结构和功能
 - ✅ 已对照:`mini/tdesign/swiper/` 目录结构和功能
+- ✅ 已对照:`mini/tdesign/toast/` 目录结构和功能
 - ✅ 已集成:tcb-theme.css 中的 Search 组件样式
 - ✅ 已集成:tcb-theme.css 中的 Swiper 组件样式
+- ✅ 已集成:tcb-theme.css 中的 Toast 组件样式
 - ✅ 已应用:tcb-shop-demo 设计规范(圆角32rpx,高度64rpx,主色调 #fa550f)
 - ✅ 已修复:组件依赖关系(Icon组件在Search组件之前创建)
 - ✅ 已验证:TypeScript编译正常,无TDesign相关错误
@@ -309,7 +312,8 @@ export default function TDesignSwiper({
 - **创建**: `mini/src/components/tdesign/search/index.tsx` - TDesign Search 组件
 - **创建**: `mini/src/components/tdesign/icon/index.tsx` - TDesign Icon 组件
 - **创建**: `mini/src/components/tdesign/swiper/index.tsx` - TDesign Swiper 组件
-- **修改**: `mini/src/tcb-theme.css` - 添加 Search 和 Swiper 组件样式
+- **创建**: `mini/src/components/tdesign/toast/index.tsx` - TDesign Toast 组件
+- **修改**: `mini/src/tcb-theme.css` - 添加 Search、Swiper 和 Toast 组件样式
 - **修改**: `docs/stories/001.004.homepage-ui-refactor.md` - 更新任务状态和开发记录
 
 ### 实施经验总结

+ 158 - 0
mini/src/components/tdesign/toast/index.tsx

@@ -0,0 +1,158 @@
+import { useState, useEffect } from 'react'
+import { View, Text } from '@tarojs/components'
+import TDesignIcon from '../icon'
+
+interface ToastProps {
+  visible?: boolean
+  message?: string
+  icon?: string
+  theme?: 'loading' | 'success' | 'error' | 'warning'
+  duration?: number
+  placement?: 'top' | 'middle' | 'bottom'
+  direction?: 'row' | 'column'
+  showOverlay?: boolean
+  preventScrollThrough?: boolean
+  onClose?: () => void
+}
+
+export default function TDesignToast({
+  visible = false,
+  message = '',
+  icon,
+  theme,
+  duration = 2000,
+  placement = 'middle',
+  direction = 'row',
+  showOverlay = false,
+  onClose
+}: ToastProps) {
+  const [realVisible, setRealVisible] = useState(visible)
+  const [transitionClass, setTransitionClass] = useState('')
+
+  useEffect(() => {
+    if (visible) {
+      setRealVisible(true)
+      setTransitionClass('tdesign-toast--enter')
+
+      // 自动关闭
+      if (duration > 0) {
+        const timer = setTimeout(() => {
+          handleClose()
+        }, duration)
+        return () => clearTimeout(timer)
+      }
+    } else {
+      handleClose()
+    }
+  }, [visible, duration])
+
+  const handleClose = () => {
+    setTransitionClass('tdesign-toast--leave')
+    setTimeout(() => {
+      setRealVisible(false)
+      onClose?.()
+    }, 300)
+  }
+
+  const getIconName = () => {
+    if (icon) return icon
+
+    switch (theme) {
+      case 'loading':
+        return 'loading'
+      case 'success':
+        return 'check-circle'
+      case 'error':
+        return 'error'
+      case 'warning':
+        return 'warning'
+      default:
+        return undefined
+    }
+  }
+
+  const getPlacementStyle = () => {
+    switch (placement) {
+      case 'top':
+        return { top: '25%' }
+      case 'bottom':
+        return { top: '75%' }
+      case 'middle':
+      default:
+        return { top: '45%' }
+    }
+  }
+
+  const iconName = getIconName()
+  const isLoading = theme === 'loading'
+
+  if (!realVisible) {
+    return null
+  }
+
+  return (
+    <>
+      {/* 遮罩层 */}
+      {showOverlay && (
+        <View
+          className="tdesign-toast__overlay"
+          style={{
+            position: 'fixed',
+            top: 0,
+            left: 0,
+            right: 0,
+            bottom: 0,
+            backgroundColor: 'rgba(0, 0, 0, 0.6)',
+            zIndex: 11000
+          }}
+          onClick={handleClose}
+        />
+      )}
+
+      {/* Toast 内容 */}
+      <View
+        className={`
+          tdesign-toast
+          tdesign-toast--${direction}
+          ${theme ? `tdesign-toast--${theme}` : ''}
+          ${message ? 'tdesign-toast--with-text' : ''}
+          ${transitionClass}
+        `}
+        style={{
+          position: 'fixed',
+          left: '50%',
+          transform: 'translate(-50%, -50%)',
+          zIndex: 12001,
+          ...getPlacementStyle()
+        }}
+        aria-role="alert"
+      >
+        <View className={`tdesign-toast__content tdesign-toast__content--${direction}`}>
+          {isLoading && (
+            <TDesignIcon
+              name="loading"
+              size={direction === 'row' ? '48rpx' : '64rpx'}
+              color="#fff"
+              className="tdesign-toast__loading"
+            />
+          )}
+
+          {!isLoading && iconName && (
+            <TDesignIcon
+              name={iconName}
+              size={direction === 'row' ? '48rpx' : '64rpx'}
+              color="#fff"
+              className={`tdesign-toast__icon tdesign-toast__icon--${direction}`}
+            />
+          )}
+
+          {message && (
+            <Text className={`tdesign-toast__text tdesign-toast__text--${direction}`}>
+              {message}
+            </Text>
+          )}
+        </View>
+      </View>
+    </>
+  )
+}

+ 100 - 0
mini/src/tcb-theme.css

@@ -532,6 +532,106 @@
   transition: all 0.3s ease;
 }
 
+/* ===== TDesign Toast 组件样式 ===== */
+.tdesign-toast {
+  position: fixed;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 12001;
+  opacity: 1;
+  transition: opacity 0.3s ease;
+  background-color: var(--td-toast-bg-color, rgba(0, 0, 0, 0.6));
+  border-radius: var(--td-toast-radius, 12rpx);
+  font-size: 28rpx;
+  color: var(--td-toast-color, #fff);
+  max-width: var(--td-toast-max-width, 370rpx);
+  min-height: 0;
+  width: auto;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  box-sizing: border-box;
+}
+
+.tdesign-toast--column {
+  padding: 48rpx;
+  min-width: 160rpx;
+  min-height: 160rpx;
+  border-radius: 16rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.tdesign-toast--loading.tdesign-toast--with-text {
+  min-width: 204rpx;
+  min-height: 204rpx;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.tdesign-toast__content {
+  align-items: center;
+  line-height: 44rpx;
+}
+
+.tdesign-toast__content--row {
+  display: flex;
+  text-align: left;
+  padding: 28rpx 44rpx;
+}
+
+.tdesign-toast__content--column {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.tdesign-toast__icon--row {
+  display: flex;
+  font-size: var(--td-toast-row-icon-size, 48rpx);
+}
+
+.tdesign-toast__icon--column {
+  font-size: var(--td-toast-column-icon-size, 64rpx);
+}
+
+.tdesign-toast__text {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 3;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  white-space: pre-line;
+}
+
+.tdesign-toast__text--column:not(:empty):not(:only-child) {
+  margin-top: 16rpx;
+}
+
+.tdesign-toast__text--row:not(:empty):not(:only-child) {
+  margin-left: 16rpx;
+}
+
+.tdesign-toast--enter {
+  opacity: 0;
+}
+
+.tdesign-toast--enter-active {
+  opacity: 1;
+  transition: opacity 0.3s ease;
+}
+
+.tdesign-toast--leave {
+  opacity: 1;
+}
+
+.tdesign-toast--leave-active {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+}
+
 /* ===== TDesign Search 组件样式 ===== */
 .tdesign-search {
   display: flex;