Browse Source

feat(dealer): 重构分销中心页面

- 优化了分销中心首页、分销订单、提现申请和团队管理页面的视觉和功能- 新增了渐变设计指南和主题选择器组件
-改进了数据展示、功能导航和用户体验
dev
科技小王子 6 days ago
parent
commit
8efeb9a5bd
  1. 137
      docs/DEALER_OPTIMIZATION.md
  2. 206
      docs/GRADIENT_DESIGN_GUIDE.md
  3. 0
      docs/REQUEST_USAGE.md
  4. 113
      src/components/GradientThemeSelector.tsx
  5. 301
      src/dealer/index.tsx
  6. 139
      src/dealer/orders/index.tsx
  7. 252
      src/dealer/team/index.tsx
  8. 238
      src/dealer/withdraw/index.tsx
  9. 223
      src/styles/gradients.ts

137
docs/DEALER_OPTIMIZATION.md

@ -0,0 +1,137 @@
# 分销中心页面优化总结
## 🔍 原始问题分析
### 主要问题
1. **数据展示错误**:成为经销商时间显示的是 `money` 字段
2. **功能缺失**:缺少导航到其他分销功能的入口
3. **用户体验差**:页面单调,缺少视觉层次
4. **代码问题**:路径错误,硬编码数据
## 🚀 优化方案
### 1. 分销中心首页 (`/dealer/index.tsx`)
#### 优化内容
- **视觉升级**:使用渐变背景和卡片设计
- **功能导航**:添加4个核心功能的快捷入口
- **数据可视化**:佣金和团队数据的直观展示
- **状态区分**:非经销商和经销商状态的不同展示
#### 新增功能
- 用户头像和基本信息展示
- 佣金统计(可提现、冻结中、累计收益)
- 团队统计(一级、二级、三级成员)
- 功能导航网格(分销订单、提现申请、我的团队、推广二维码)
### 2. 分销订单页面 (`/dealer/orders/index.tsx`)
#### 优化内容
- **标签页设计**:全部、待结算、已完成
- **统计面板**:总订单、总佣金、待结算金额
- **下拉刷新**:支持手动刷新数据
- **订单卡片**:清晰的订单信息展示
#### 新增功能
- 订单状态标签和颜色区分
- 佣金预计和实际到账显示
- 客户信息和订单时间
### 3. 提现申请页面 (`/dealer/withdraw/index.tsx`)
#### 优化内容
- **双标签页**:申请提现 + 提现记录
- **余额卡片**:渐变设计显示可提现余额
- **快捷金额**:预设金额按钮和全部提现
- **提现方式**:微信、支付宝、银行卡选择
#### 新增功能
- 提现规则说明(最低金额、手续费)
- 提现记录状态跟踪
- 表单验证和用户体验优化
### 4. 团队管理页面 (`/dealer/team/index.tsx`)
#### 优化内容
- **团队总览**:统计卡片和层级分布图
- **成员分级**:按一级、二级、三级分类显示
- **成员卡片**:头像、等级、贡献数据
- **进度条**:可视化层级分布比例
#### 新增功能
- 成员活跃状态标识
- 贡献佣金和订单数统计
- 下级成员数量显示
- 等级图标和颜色区分
## 📊 技术改进
### 1. 数据处理
```typescript
// 格式化金额
const formatMoney = (money?: string) => {
if (!money) return '0.00'
return parseFloat(money).toFixed(2)
}
// 格式化时间
const formatTime = (time?: string) => {
if (!time) return '-'
return new Date(time).toLocaleDateString()
}
```
### 2. 状态管理
- 使用真实的 `dealerUser` 数据
- 正确的字段映射和显示
- 错误处理和加载状态
### 3. 导航优化
- 修复路径错误:`/dealer/apply/add` 而不是 `/pages/dealer/apply/add`
- 统一的页面跳转方法
- 清晰的功能入口
## 🎨 UI/UX 改进
### 1. 视觉设计
- **渐变背景**:增加视觉吸引力
- **卡片设计**:信息分组和层次感
- **图标系统**:统一的图标风格
- **颜色系统**:语义化的颜色使用
### 2. 交互体验
- **下拉刷新**:实时数据更新
- **快捷操作**:减少用户操作步骤
- **状态反馈**:清晰的状态提示
- **响应式布局**:适配不同屏幕尺寸
### 3. 信息架构
- **功能分组**:相关功能集中展示
- **数据层次**:重要信息突出显示
- **导航清晰**:明确的页面结构
## 🔧 待完善功能
### 1. 数据接口集成
- 连接真实的分销订单 API
- 实现提现申请和记录查询
- 团队成员数据的实时获取
### 2. 功能增强
- 推广二维码生成和分享
- 佣金明细和结算记录
- 团队成员邀请和管理
### 3. 性能优化
- 列表虚拟化(大量数据时)
- 图片懒加载
- 缓存策略优化
## 📱 移动端适配
- 响应式设计确保各种屏幕尺寸下的良好体验
- 触摸友好的交互元素
- 合适的字体大小和间距
- 底部安全区域处理
这次优化大幅提升了分销中心的用户体验和功能完整性,为后续的功能扩展奠定了良好基础。

206
docs/GRADIENT_DESIGN_GUIDE.md

@ -0,0 +1,206 @@
# 🎨 渐变设计指南
## 概述
我为你的分销中心设计了一套完整的渐变主题系统,包含多种美观的渐变方案和统一的设计语言。
## 🌈 渐变主题方案
### 1. 预设主题
| 主题名称 | 颜色搭配 | 适用场景 | 视觉效果 |
|---------|---------|---------|---------|
| **ocean** | 蓝紫渐变 | 科技、专业 | 🌊 海洋般的深邃感 |
| **sunset** | 橙红渐变 | 活力、热情 | 🌅 日落般的温暖感 |
| **fresh** | 蓝绿渐变 | 清新、活力 | 🍃 清新自然的感觉 |
| **nature** | 绿青渐变 | 生机、成长 | 🌱 生机勃勃的活力 |
| **warm** | 金粉渐变 | 温馨、友好 | ☀️ 温暖亲和的感觉 |
| **elegant** | 淡彩渐变 | 优雅、精致 | 💎 优雅精致的品味 |
| **royal** | 皇家紫蓝 | 高贵、权威 | 👑 高贵典雅的气质 |
| **fire** | 火焰粉红 | 激情、浪漫 | 🔥 激情浪漫的氛围 |
### 2. 业务场景渐变
```typescript
// 分销商相关
dealer: {
header: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
card: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
success: 'linear-gradient(135deg, #10b981 0%, #34d399 100%)',
warning: 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)',
danger: 'linear-gradient(135deg, #ef4444 0%, #f87171 100%)',
info: 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)'
}
// 金额相关
money: {
available: 'linear-gradient(135deg, #10b981 0%, #059669 100%)', // 可提现 - 绿色
frozen: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', // 冻结中 - 蓝色
total: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)' // 累计 - 橙色
}
```
## 🎯 设计特点
### 1. 视觉层次
- **主背景**:动态渐变,根据用户ID自动选择
- **卡片背景**:微妙的白色渐变,增加层次感
- **装饰元素**:半透明圆形,增加空间感
### 2. 色彩心理学
- **蓝色系**:信任、专业、稳定
- **绿色系**:成长、财富、安全
- **橙色系**:活力、温暖、友好
- **紫色系**:高贵、创新、神秘
### 3. 交互反馈
- **悬停效果**:轻微的亮度变化
- **选中状态**:边框高亮
- **加载状态**:渐变动画
## 🛠️ 使用方法
### 1. 基础使用
```tsx
import { businessGradients, cardGradients } from '@/styles/gradients'
// 使用预设的业务渐变
<View style={{ background: businessGradients.dealer.header }}>
内容
</View>
// 使用卡片渐变
<View style={cardGradients.elevated}>
卡片内容
</View>
```
### 2. 动态主题
```tsx
import { gradientUtils } from '@/styles/gradients'
// 根据用户ID获取主题
const userTheme = gradientUtils.getThemeByUserId(userId)
<View style={{ background: userTheme.background }}>
个性化内容
</View>
```
### 3. 自定义渐变
```tsx
import { gradientUtils } from '@/styles/gradients'
// 创建自定义渐变
const customGradient = gradientUtils.createGradient('#ff6b6b', '#4ecdc4', '45deg')
<View style={{ background: customGradient }}>
自定义内容
</View>
```
## 🎨 设计原则
### 1. 一致性
- 所有页面使用统一的渐变系统
- 相同功能使用相同的颜色语义
- 保持视觉风格的连贯性
### 2. 可访问性
- 确保文字与背景有足够的对比度
- 支持深色模式适配
- 考虑色盲用户的体验
### 3. 性能优化
- 使用 CSS 渐变而非图片
- 避免过度复杂的渐变效果
- 合理使用动画和过渡
## 📱 移动端适配
### 1. 响应式设计
```css
/* 小屏幕优化 */
@media (max-width: 768px) {
.gradient-header {
background-size: 200% 200%;
animation: gradientShift 8s ease infinite;
}
}
```
### 2. 性能考虑
- 在低端设备上简化渐变效果
- 使用 `will-change` 属性优化动画
- 避免在滚动时使用复杂渐变
## 🔧 高级功能
### 1. 主题切换器
```tsx
import GradientThemeSelector from '@/components/GradientThemeSelector'
<GradientThemeSelector
visible={showThemeSelector}
onClose={() => setShowThemeSelector(false)}
onSelect={handleThemeSelect}
currentTheme={currentTheme}
/>
```
### 2. 动画渐变
```tsx
import { animatedGradients } from '@/styles/gradients'
<View style={animatedGradients.flowing}>
动态流动的渐变背景
</View>
```
### 3. 玻璃态效果
```tsx
import { cardGradients } from '@/styles/gradients'
<View style={cardGradients.glass}>
毛玻璃效果卡片
</View>
```
## 🎯 最佳实践
### 1. 渐变方向
- **135度**:最常用,自然舒适
- **90度**:垂直渐变,适合长条内容
- **45度**:对角渐变,动感活泼
### 2. 颜色选择
- 选择色相相近的颜色
- 避免过于强烈的对比
- 考虑品牌色的融入
### 3. 层次搭配
- 主背景:鲜艳渐变
- 卡片背景:淡雅渐变
- 文字:纯色或微渐变
## 🚀 未来扩展
### 1. 主题商店
- 更多预设主题
- 用户自定义主题
- 主题分享功能
### 2. 智能推荐
- 基于使用习惯推荐主题
- 根据时间自动切换主题
- 情境感知的主题选择
### 3. 高级效果
- 3D 渐变效果
- 粒子背景
- 交互式渐变
这套渐变系统不仅美观,还具有很强的扩展性和可维护性,为你的应用提供了专业级的视觉体验!

0
REQUEST_USAGE.md → docs/REQUEST_USAGE.md

113
src/components/GradientThemeSelector.tsx

@ -0,0 +1,113 @@
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Popup, Button } from '@nutui/nutui-react-taro'
import { gradientThemes, GradientTheme } from '@/styles/gradients'
interface GradientThemeSelectorProps {
visible: boolean
onClose: () => void
onSelect: (theme: GradientTheme) => void
currentTheme?: GradientTheme
}
const GradientThemeSelector: React.FC<GradientThemeSelectorProps> = ({
visible,
onClose,
onSelect,
currentTheme
}) => {
const [selectedTheme, setSelectedTheme] = useState<GradientTheme | null>(currentTheme || null)
const handleThemeSelect = (theme: GradientTheme) => {
setSelectedTheme(theme)
}
const handleConfirm = () => {
if (selectedTheme) {
onSelect(selectedTheme)
onClose()
}
}
const renderThemeItem = (theme: GradientTheme) => {
const isSelected = selectedTheme?.name === theme.name
return (
<View
key={theme.name}
className={`p-3 rounded-lg border-2 ${isSelected ? 'border-blue-500' : 'border-gray-200'}`}
onClick={() => handleThemeSelect(theme)}
>
{/* 渐变预览 */}
<View
className="w-full h-16 rounded-lg mb-2"
style={{
background: theme.background
}}
>
<View className="w-full h-full flex items-center justify-center">
<Text className="text-white text-xs font-bold drop-shadow-sm">
</Text>
</View>
</View>
{/* 主题信息 */}
<View className="text-center">
<Text className="text-sm font-semibold text-gray-800 mb-1">
{theme.description.split(' - ')[0]}
</Text>
<Text className="text-xs text-gray-500">
{theme.description.split(' - ')[1]}
</Text>
</View>
{/* 选中标识 */}
{isSelected && (
<View className="absolute top-1 right-1 w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
<Text className="text-white text-xs"></Text>
</View>
)}
</View>
)
}
return (
<Popup
visible={visible}
position="bottom"
onClose={onClose}
style={{ height: '70vh' }}
>
<View className="p-4">
<View className="flex items-center justify-between mb-4">
<Text className="text-lg font-bold"></Text>
<Button size="small" fill="outline" onClick={onClose}>
</Button>
</View>
<Text className="text-sm text-gray-600 mb-4">
</Text>
{/* 主题网格 */}
<View className="grid grid-cols-2 gap-3 mb-6">
{gradientThemes.map(renderThemeItem)}
</View>
{/* 确认按钮 */}
<Button
type="primary"
block
disabled={!selectedTheme}
onClick={handleConfirm}
>
</Button>
</View>
</Popup>
)
}
export default GradientThemeSelector

301
src/dealer/index.tsx

@ -1,7 +1,18 @@
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Cell, CellGroup, Tag } from '@nutui/nutui-react-taro'
import { useDealerUser } from '@/hooks/useDealerUser'
import {View, Text} from '@tarojs/components'
import {Button, Cell, CellGroup, Tag, Grid, Avatar, Divider} from '@nutui/nutui-react-taro'
import {
User,
Shopping,
Dongdong,
Share,
Service,
ArrowRight,
Purse,
People
} from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser'
import { gradientUtils, businessGradients, cardGradients, textGradients } from '@/styles/gradients'
import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => {
@ -15,10 +26,39 @@ const DealerIndex: React.FC = () => {
// 跳转到申请页面
const navigateToApply = () => {
Taro.navigateTo({
url: '/pages/dealer/apply/add'
url: '/dealer/apply/add'
})
}
// 导航到各个功能页面
const navigateToPage = (url: string) => {
Taro.navigateTo({url})
}
// 格式化金额
const formatMoney = (money?: string) => {
if (!money) return '0.00'
return parseFloat(money).toFixed(2)
}
// 格式化时间
const formatTime = (time?: string) => {
if (!time) return '-'
return new Date(time).toLocaleDateString()
}
// 获取用户主题
const userTheme = gradientUtils.getThemeByUserId(dealerUser?.userId)
// 获取渐变背景
const getGradientBackground = (themeColor?: string) => {
if (themeColor) {
const darkerColor = gradientUtils.adjustColorBrightness(themeColor, -30)
return gradientUtils.createGradient(themeColor, darkerColor)
}
return userTheme.background
}
if (error) {
return (
<View className="p-4">
@ -34,123 +74,178 @@ const DealerIndex: React.FC = () => {
return (
<View className="bg-gray-50 min-h-screen">
{/* 页面标题 */}
<View className="bg-white px-4 py-3 border-b border-gray-100 flex justify-between items-center">
<Text className="text-lg font-bold text-center">
{dealerUser?.realName}
</Text>
<Text className={'text-gray-400 text-xs'}>ID{dealerUser?.refereeId}</Text>
</View>
{!dealerUser ? (
// 非经销商状态
<View className="bg-white mx-4 mt-4 rounded-lg p-6">
<View className="text-center py-8">
<Text className="text-gray-500 mb-4"></Text>
<Text className="text-sm text-gray-400 mb-6">
</Text>
<Button
type="primary"
size="large"
onClick={navigateToApply}
>
</Button>
</View>
</View>
) : (
// 经销商信息展示
<View>
{/* 状态卡片 */}
<View className="bg-white mx-4 mt-4 rounded-lg p-4">
<View className="flex items-center justify-between mb-4">
<Text className="text-lg font-semibold"></Text>
<Tag>
{dealerUser.realName}
</Tag>
</View>
{/* 基本信息 */}
<CellGroup>
<Cell
title="经销商ID"
extra={dealerUser.userId || '-'}
/>
<Cell
title="refereeId"
extra={dealerUser.refereeId || '-'}
/>
<Cell
title="成为经销商时间"
extra={
dealerUser.money
}
<View>
{/*头部信息*/}
{dealerUser && (
<View className="px-4 py-6 relative overflow-hidden" style={{
background: getGradientBackground(dealerUser?.themeColor)
}}>
{/* 装饰性背景元素 */}
<View className="absolute top-0 right-0 w-32 h-32 rounded-full opacity-10" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 100%)',
transform: 'translate(50%, -50%)'
}}></View>
<View className="absolute bottom-0 left-0 w-24 h-24 rounded-full opacity-10" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.05) 100%)',
transform: 'translate(-50%, 50%)'
}}></View>
<View className="absolute top-1/2 left-1/2 w-16 h-16 rounded-full opacity-5" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.4) 0%, transparent 70%)',
transform: 'translate(-50%, -50%)'
}}></View>
<View className="flex items-center justify-between relative z-10">
<Avatar
size="50"
src={dealerUser?.qrcode}
icon={<User/>}
className="mr-4"
style={{
border: '2px solid rgba(255, 255, 255, 0.3)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
}}
/>
</CellGroup>
{/* 操作按钮 */}
<View className="mt-6 space-y-3">
<Button
type="primary"
size="large"
loading={loading}
>
</Button>
<View className="flex-1">
<Text className="text-white text-lg font-bold mb-1" style={{
textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)'
}}>
{dealerUser?.realName || '分销商'}
</Text>
<Text className="text-sm" style={{
color: 'rgba(255, 255, 255, 0.8)'
}}>
ID: {dealerUser.userId} | : {dealerUser.refereeId || '无'}
</Text>
</View>
<View className="text-right">
<Text className="text-xs" style={{
color: 'rgba(255, 255, 255, 0.9)'
}}></Text>
<Text className="text-xs" style={{
color: 'rgba(255, 255, 255, 0.7)'
}}>
{formatTime(dealerUser.createTime)}
</Text>
</View>
</View>
</View>
)}
{/* 经销商权益 */}
<View className="bg-white mx-4 mt-4 rounded-lg p-4">
<Text className="font-semibold mb-3"></Text>
<View className="space-y-2">
<Text className="text-sm text-gray-600">
</Text>
<Text className="text-sm text-gray-600">
广
</Text>
<Text className="text-sm text-gray-600">
</Text>
<Text className="text-sm text-gray-600">
</Text>
{/* 佣金统计卡片 */}
{dealerUser && (
<View className="mx-4 -mt-6 rounded-xl p-4 relative z-10" style={cardGradients.elevated}>
<Text className="font-semibold mb-4 text-gray-800"></Text>
<View className="grid grid-cols-3 gap-4">
<View className="text-center p-3 rounded-lg" style={{
background: businessGradients.money.available,
backgroundImage: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)'
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
¥{formatMoney(dealerUser.money)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
</View>
<View className="text-center p-3 rounded-lg" style={{
background: businessGradients.money.frozen,
backgroundImage: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)'
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
¥{formatMoney(dealerUser.freezeMoney)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
</View>
<View className="text-center p-3 rounded-lg" style={{
background: businessGradients.money.total,
backgroundImage: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)'
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
¥{formatMoney(dealerUser.totalMoney)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
</View>
</View>
</View>
)}
{/* 佣金统计 */}
<View className="bg-white mx-4 mt-4 rounded-lg p-4">
<Text className="font-semibold mb-3"></Text>
{/* 团队统计 */}
{dealerUser && (
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
<View className="flex items-center justify-between mb-4">
<Text className="font-semibold text-gray-800"></Text>
<Text
className="text-blue-500 text-sm"
onClick={() => navigateToPage('/dealer/team/index')}
>
<ArrowRight size="12"/>
</Text>
</View>
<View className="grid grid-cols-3 gap-4">
<View className="text-center">
<Text className="text-lg font-bold text-blue-600">0</Text>
<Text className="text-sm text-gray-500"></Text>
<Text className="text-xl font-bold text-purple-500 mb-1">
{dealerUser.firstNum || 0}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center">
<Text className="text-lg font-bold text-green-600">0</Text>
<Text className="text-sm text-gray-500"></Text>
<Text className="text-xl font-bold text-indigo-500 mb-1">
{dealerUser.secondNum || 0}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center">
<Text className="text-lg font-bold text-orange-600">0</Text>
<Text className="text-sm text-gray-500"></Text>
<Text className="text-xl font-bold text-pink-500 mb-1">
{dealerUser.thirdNum || 0}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
</View>
</View>
)}
{/* 功能导航 */}
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
<Text className="font-semibold mb-4 text-gray-800"></Text>
<Grid columns={4} gap={16}>
<Grid.Item onClick={() => navigateToPage('/dealer/orders/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Shopping color="#3b82f6" size="20"/>
</View>
<Text className="text-xs text-gray-600"></Text>
</View>
</Grid.Item>
<Grid.Item onClick={() => navigateToPage('/dealer/withdraw/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Purse color="#10b981" size="20"/>
</View>
<Text className="text-xs text-gray-600"></Text>
</View>
</Grid.Item>
<Grid.Item onClick={() => navigateToPage('/dealer/team/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<People color="#8b5cf6" size="20"/>
</View>
<Text className="text-xs text-gray-600"></Text>
</View>
</Grid.Item>
<Grid.Item onClick={() => navigateToPage('/dealer/qrcode/index')}>
<View className="text-center">
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Dongdong color="#f59e0b" size="20"/>
</View>
<Text className="text-xs text-gray-600">广</Text>
</View>
</Grid.Item>
</Grid>
</View>
)}
{/* 刷新按钮 */}
<View className="text-center py-4">
<Text
className="text-blue-500 text-sm"
onClick={refresh}
>
</Text>
</View>
{/* 底部安全区域 */}
<View className="h-20"></View>
</View>
)
}

139
src/dealer/orders/index.tsx

@ -1,13 +1,140 @@
import React from 'react'
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Cell, Empty } from '@nutui/nutui-react-taro'
import { Cell, Empty, Tabs, Tag, Button, PullToRefresh } from '@nutui/nutui-react-taro'
const DealerOrders: React.FC = () => {
const [activeTab, setActiveTab] = useState('0')
const [refreshing, setRefreshing] = useState(false)
// 模拟订单数据
const mockOrders = [
{
id: '1',
orderNo: 'DD202412180001',
customerName: '张三',
amount: '299.00',
commission: '29.90',
status: 'completed',
createTime: '2024-12-18 10:30:00'
},
{
id: '2',
orderNo: 'DD202412180002',
customerName: '李四',
amount: '599.00',
commission: '59.90',
status: 'pending',
createTime: '2024-12-18 14:20:00'
}
]
const getStatusText = (status: string) => {
switch (status) {
case 'completed': return '已完成'
case 'pending': return '待结算'
case 'cancelled': return '已取消'
default: return '未知'
}
}
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'success'
case 'pending': return 'warning'
case 'cancelled': return 'danger'
default: return 'default'
}
}
const handleRefresh = async () => {
setRefreshing(true)
// 模拟刷新
setTimeout(() => {
setRefreshing(false)
}, 1000)
}
const renderOrderItem = (order: any) => (
<View key={order.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex justify-between items-start mb-3">
<View>
<Text className="font-semibold text-gray-800 mb-1">
{order.orderNo}
</Text>
<Text className="text-sm text-gray-500">
{order.customerName}
</Text>
</View>
<Tag type={getStatusColor(order.status)} size="small">
{getStatusText(order.status)}
</Tag>
</View>
<View className="flex justify-between items-center">
<View>
<Text className="text-sm text-gray-600">
¥{order.amount}
</Text>
<Text className="text-sm text-orange-500 font-semibold">
¥{order.commission}
</Text>
</View>
<Text className="text-xs text-gray-400">
{order.createTime}
</Text>
</View>
</View>
)
return (
<View className="p-4">
<Text className="text-lg font-bold mb-4"></Text>
<Empty description="暂无分销订单" />
<View className="bg-gray-50 min-h-screen">
{/* 统计卡片 */}
<View className="bg-white p-4 mb-4">
<View className="grid grid-cols-3 gap-4">
<View className="text-center">
<Text className="text-lg font-bold text-blue-500">2</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center">
<Text className="text-lg font-bold text-green-500">¥89.80</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View className="text-center">
<Text className="text-lg font-bold text-orange-500">¥29.90</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
</View>
</View>
{/* 订单列表 */}
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs.TabPane title="全部" value="0">
<PullToRefresh
loading={refreshing}
onRefresh={handleRefresh}
>
<View className="p-4">
{mockOrders.length > 0 ? (
mockOrders.map(renderOrderItem)
) : (
<Empty description="暂无分销订单" />
)}
</View>
</PullToRefresh>
</Tabs.TabPane>
<Tabs.TabPane title="待结算" value="1">
<View className="p-4">
{mockOrders.filter(o => o.status === 'pending').map(renderOrderItem)}
</View>
</Tabs.TabPane>
<Tabs.TabPane title="已完成" value="2">
<View className="p-4">
{mockOrders.filter(o => o.status === 'completed').map(renderOrderItem)}
</View>
</Tabs.TabPane>
</Tabs>
</View>
)
}

252
src/dealer/team/index.tsx

@ -1,13 +1,255 @@
import React from 'react'
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Empty } from '@nutui/nutui-react-taro'
import { Empty, Tabs, Avatar, Tag, Cell, Progress } from '@nutui/nutui-react-taro'
import { User, Crown, Star } from '@nutui/icons-react-taro'
import { businessGradients, cardGradients } from '@/styles/gradients'
const DealerTeam: React.FC = () => {
return (
const [activeTab, setActiveTab] = useState('0')
// 模拟团队数据
const teamStats = {
total: 28,
firstLevel: 12,
secondLevel: 10,
thirdLevel: 6,
monthlyCommission: '2,580.50'
}
const teamMembers = [
{
id: '1',
name: '张小明',
level: 1,
joinTime: '2024-11-15',
orderCount: 15,
commission: '580.50',
status: 'active',
avatar: '',
subMembers: 3
},
{
id: '2',
name: '李小红',
level: 1,
joinTime: '2024-12-01',
orderCount: 8,
commission: '320.00',
status: 'active',
avatar: '',
subMembers: 2
},
{
id: '3',
name: '王小华',
level: 2,
joinTime: '2024-12-10',
orderCount: 5,
commission: '150.00',
status: 'inactive',
avatar: '',
subMembers: 0
}
]
const getLevelColor = (level: number) => {
switch (level) {
case 1: return '#f59e0b'
case 2: return '#8b5cf6'
case 3: return '#ec4899'
default: return '#6b7280'
}
}
const getLevelIcon = (level: number) => {
switch (level) {
case 1: return <Crown color={getLevelColor(level)} size="16" />
case 2: return <Star color={getLevelColor(level)} size="16" />
case 3: return <User color={getLevelColor(level)} size="16" />
default: return <User color={getLevelColor(level)} size="16" />
}
}
const renderMemberItem = (member: any) => (
<View key={member.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3">
<Avatar
size="40"
src={member.avatar}
icon={<User />}
className="mr-3"
/>
<View className="flex-1">
<View className="flex items-center mb-1">
<Text className="font-semibold text-gray-800 mr-2">
{member.name}
</Text>
{getLevelIcon(member.level)}
<Text className="text-xs text-gray-500 ml-1">
{member.level}
</Text>
</View>
<Text className="text-xs text-gray-500">
{member.joinTime}
</Text>
</View>
<View className="text-right">
<Tag
type={member.status === 'active' ? 'success' : 'default'}
size="small"
>
{member.status === 'active' ? '活跃' : '沉默'}
</Tag>
</View>
</View>
<View className="grid grid-cols-3 gap-4 text-center">
<View>
<Text className="text-sm font-semibold text-blue-600">
{member.orderCount}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View>
<Text className="text-sm font-semibold text-green-600">
¥{member.commission}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
<View>
<Text className="text-sm font-semibold text-purple-600">
{member.subMembers}
</Text>
<Text className="text-xs text-gray-500"></Text>
</View>
</View>
</View>
)
const renderOverview = () => (
<View className="p-4">
{/* 团队统计卡片 */}
<View className="rounded-xl p-6 mb-6 text-white relative overflow-hidden" style={{
background: 'linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%)'
}}>
{/* 装饰背景 */}
<View className="absolute top-0 right-0 w-32 h-32 rounded-full opacity-10" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%)',
transform: 'translate(50%, -50%)'
}}></View>
<View className="absolute bottom-0 left-0 w-20 h-20 rounded-full opacity-5" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.4) 0%, transparent 70%)',
transform: 'translate(-50%, 50%)'
}}></View>
<View className="relative z-10">
<Text className="text-lg font-bold mb-4 text-white drop-shadow-sm"></Text>
<View className="grid grid-cols-2 gap-4">
<View>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">{teamStats.total}</Text>
<Text className="text-white text-opacity-80 text-sm"></Text>
</View>
<View>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">¥{teamStats.monthlyCommission}</Text>
<Text className="text-white text-opacity-80 text-sm"></Text>
</View>
</View>
</View>
</View>
{/* 层级分布 */}
<View className="bg-white rounded-xl p-4 mb-4">
<Text className="font-semibold mb-4 text-gray-800"></Text>
<View className="space-y-3">
<View className="flex items-center justify-between">
<View className="flex items-center">
<Crown color="#f59e0b" size="16" className="mr-2" />
<Text className="text-sm"></Text>
</View>
<View className="flex items-center">
<Text className="text-sm font-semibold mr-2">{teamStats.firstLevel}</Text>
<Progress
percentage={(teamStats.firstLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#f59e0b"
className="w-20"
/>
</View>
</View>
<View className="flex items-center justify-between">
<View className="flex items-center">
<Star color="#8b5cf6" size="16" className="mr-2" />
<Text className="text-sm"></Text>
</View>
<View className="flex items-center">
<Text className="text-sm font-semibold mr-2">{teamStats.secondLevel}</Text>
<Progress
percentage={(teamStats.secondLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#8b5cf6"
className="w-20"
/>
</View>
</View>
<View className="flex items-center justify-between">
<View className="flex items-center">
<User color="#ec4899" size="16" className="mr-2" />
<Text className="text-sm"></Text>
</View>
<View className="flex items-center">
<Text className="text-sm font-semibold mr-2">{teamStats.thirdLevel}</Text>
<Progress
percentage={(teamStats.thirdLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#ec4899"
className="w-20"
/>
</View>
</View>
</View>
</View>
{/* 最新成员 */}
<View className="bg-white rounded-xl p-4">
<Text className="font-semibold mb-4 text-gray-800"></Text>
{teamMembers.slice(0, 3).map(renderMemberItem)}
</View>
</View>
)
const renderMemberList = (level?: number) => (
<View className="p-4">
<Text className="text-lg font-bold mb-4"></Text>
{teamMembers
.filter(member => !level || member.level === level)
.map(renderMemberItem)}
{teamMembers.filter(member => !level || member.level === level).length === 0 && (
<Empty description={`暂无${level ? level + '级' : ''}团队成员`} />
)}
</View>
)
return (
<View className="bg-gray-50 min-h-screen">
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs.TabPane title="团队总览" value="0">
{renderOverview()}
</Tabs.TabPane>
<Tabs.TabPane title="一级成员" value="1">
{renderMemberList(1)}
</Tabs.TabPane>
<Tabs.TabPane title="二级成员" value="2">
{renderMemberList(2)}
</Tabs.TabPane>
<Empty description="暂无团队成员" />
<Tabs.TabPane title="三级成员" value="3">
{renderMemberList(3)}
</Tabs.TabPane>
</Tabs>
</View>
)
}

238
src/dealer/withdraw/index.tsx

@ -1,35 +1,239 @@
import React from 'react'
import React, { useState, useRef } from 'react'
import { View, Text } from '@tarojs/components'
import { Cell, Button, Form, Input } from '@nutui/nutui-react-taro'
import {
Cell,
Button,
Form,
Input,
CellGroup,
Radio,
Tabs,
Tag,
Empty
} from '@nutui/nutui-react-taro'
import { Money, ArrowRight } from '@nutui/icons-react-taro'
import { businessGradients, cardGradients } from '@/styles/gradients'
import Taro from '@tarojs/taro'
const DealerWithdraw: React.FC = () => {
return (
const [activeTab, setActiveTab] = useState('0')
const [selectedAccount, setSelectedAccount] = useState('')
const formRef = useRef<any>(null)
// 模拟可提现金额
const availableAmount = '1,288.50'
// 模拟提现记录
const withdrawRecords = [
{
id: '1',
amount: '500.00',
account: '尾号1234',
status: 'completed',
createTime: '2024-12-15 10:30:00',
completeTime: '2024-12-15 16:20:00'
},
{
id: '2',
amount: '300.00',
account: '尾号1234',
status: 'pending',
createTime: '2024-12-18 09:15:00'
}
]
const getStatusText = (status: string) => {
switch (status) {
case 'completed': return '已到账'
case 'pending': return '处理中'
case 'rejected': return '已拒绝'
default: return '未知'
}
}
const getStatusColor = (status: string) => {
switch (status) {
case 'completed': return 'success'
case 'pending': return 'warning'
case 'rejected': return 'danger'
default: return 'default'
}
}
const handleSubmit = (values: any) => {
console.log('提现申请:', values)
Taro.showToast({
title: '提现申请已提交',
icon: 'success'
})
}
const quickAmounts = ['100', '300', '500', '1000']
const setQuickAmount = (amount: string) => {
formRef.current?.setFieldsValue({ amount })
}
const setAllAmount = () => {
formRef.current?.setFieldsValue({ amount: availableAmount.replace(',', '') })
}
const renderWithdrawForm = () => (
<View className="p-4">
<Text className="text-lg font-bold mb-4"></Text>
<Form>
<Cell.Group>
{/* 余额卡片 */}
<View className="rounded-xl p-6 mb-6 text-white relative overflow-hidden" style={{
background: businessGradients.dealer.header
}}>
{/* 装饰背景 */}
<View className="absolute top-0 right-0 w-24 h-24 rounded-full opacity-10" style={{
background: 'radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, transparent 70%)',
transform: 'translate(50%, -50%)'
}}></View>
<View className="flex items-center justify-between relative z-10">
<View>
<Text className="text-white text-opacity-80 text-sm mb-1"></Text>
<Text className="text-2xl font-bold text-white drop-shadow-sm">¥{availableAmount}</Text>
</View>
<View className="p-3 rounded-full" style={{
background: 'rgba(255, 255, 255, 0.2)'
}}>
<Money color="white" size="32" />
</View>
</View>
<View className="mt-4 pt-4 relative z-10" style={{
borderTop: '1px solid rgba(255, 255, 255, 0.3)'
}}>
<Text className="text-white text-opacity-80 text-xs">
¥100 |
</Text>
</View>
</View>
<Form
ref={formRef}
onFinish={handleSubmit}
labelPosition="top"
>
<CellGroup>
<Form.Item name="amount" label="提现金额" required>
<Input placeholder="请输入提现金额" type="number" />
<Input
placeholder="请输入提现金额"
type="number"
clearable
/>
</Form.Item>
{/* 快捷金额 */}
<View className="px-4 py-2">
<Text className="text-sm text-gray-600 mb-2"></Text>
<View className="flex flex-wrap gap-2">
{quickAmounts.map(amount => (
<Button
key={amount}
size="small"
fill="outline"
onClick={() => setQuickAmount(amount)}
>
{amount}
</Button>
))}
<Button
size="small"
fill="outline"
onClick={setAllAmount}
>
</Button>
</View>
</View>
<Form.Item name="accountType" label="提现方式" required>
<Radio.Group value={selectedAccount} onChange={setSelectedAccount}>
<Cell.Group>
<Cell>
<Radio value="wechat"></Radio>
</Cell>
<Cell>
<Radio value="alipay"></Radio>
</Cell>
<Cell>
<Radio value="bank"></Radio>
</Cell>
</Cell.Group>
</Radio.Group>
</Form.Item>
<Form.Item name="account" label="账户信息" required>
<Input placeholder="请输入账户号码" />
</Form.Item>
<Form.Item name="account" label="提现账户" required>
<Input placeholder="请输入提现账户" />
<Form.Item name="accountName" label="账户姓名" required>
<Input placeholder="请输入账户姓名" />
</Form.Item>
<Form.Item name="remark" label="备注">
<Input placeholder="请输入备注信息" />
<Input placeholder="请输入备注信息(可选)" />
</Form.Item>
</Cell.Group>
<View className="mt-4">
<Button block type="primary">
</CellGroup>
<View className="mt-6 px-4">
<Button block type="primary" nativeType="submit">
</Button>
</View>
</Form>
</View>
)
const renderWithdrawRecords = () => (
<View className="p-4">
{withdrawRecords.length > 0 ? (
withdrawRecords.map(record => (
<View key={record.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex justify-between items-start mb-3">
<View>
<Text className="font-semibold text-gray-800 mb-1">
¥{record.amount}
</Text>
<Text className="text-sm text-gray-500">
{record.account}
</Text>
</View>
<Tag type={getStatusColor(record.status)} size="small">
{getStatusText(record.status)}
</Tag>
</View>
<View className="text-xs text-gray-400">
<Text>{record.createTime}</Text>
{record.completeTime && (
<Text className="block mt-1">
{record.completeTime}
</Text>
)}
</View>
</View>
))
) : (
<Empty description="暂无提现记录" />
)}
</View>
)
return (
<View className="bg-gray-50 min-h-screen">
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs.TabPane title="申请提现" value="0">
{renderWithdrawForm()}
</Tabs.TabPane>
<Tabs.TabPane title="提现记录" value="1">
{renderWithdrawRecords()}
</Tabs.TabPane>
</Tabs>
</View>
)
}
export default DealerWithdraw

223
src/styles/gradients.ts

@ -0,0 +1,223 @@
/**
*
*
*/
export interface GradientTheme {
name: string
primary: string
secondary?: string
background: string
textColor: string
description: string
}
// 预定义渐变主题
export const gradientThemes: GradientTheme[] = [
{
name: 'ocean',
primary: '#667eea',
secondary: '#764ba2',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
textColor: '#ffffff',
description: '海洋蓝紫 - 科技感与专业感'
},
{
name: 'sunset',
primary: '#f093fb',
secondary: '#f5576c',
background: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
textColor: '#ffffff',
description: '日落橙红 - 活力与热情'
},
{
name: 'fresh',
primary: '#4facfe',
secondary: '#00f2fe',
background: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
textColor: '#ffffff',
description: '清新蓝绿 - 清新与活力'
},
{
name: 'nature',
primary: '#43e97b',
secondary: '#38f9d7',
background: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
textColor: '#ffffff',
description: '自然绿青 - 生机与成长'
},
{
name: 'warm',
primary: '#fa709a',
secondary: '#fee140',
background: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
textColor: '#ffffff',
description: '温暖金粉 - 温馨与友好'
},
{
name: 'elegant',
primary: '#a8edea',
secondary: '#fed6e3',
background: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
textColor: '#374151',
description: '优雅淡彩 - 柔和与精致'
},
{
name: 'royal',
primary: '#667eea',
secondary: '#764ba2',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
textColor: '#ffffff',
description: '皇家紫蓝 - 高贵与权威'
},
{
name: 'fire',
primary: '#ff9a9e',
secondary: '#fecfef',
background: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
textColor: '#ffffff',
description: '火焰粉红 - 激情与浪漫'
}
]
// 业务场景渐变
export const businessGradients = {
// 分销商相关
dealer: {
header: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
card: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
success: 'linear-gradient(135deg, #10b981 0%, #34d399 100%)',
warning: 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)',
danger: 'linear-gradient(135deg, #ef4444 0%, #f87171 100%)',
info: 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)'
},
// 订单相关
order: {
pending: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)',
completed: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
cancelled: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
processing: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)'
},
// 金额相关
money: {
available: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
frozen: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
total: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)'
}
}
// 卡片渐变样式
export const cardGradients = {
glass: {
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)',
border: '1px solid rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(10px)'
},
subtle: {
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid rgba(255, 255, 255, 0.8)',
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05)'
},
elevated: {
background: 'linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%)',
border: '1px solid rgba(255, 255, 255, 0.9)',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.06)'
}
}
// 文字渐变样式
export const textGradients = {
primary: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
},
success: {
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
},
warning: {
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
},
danger: {
background: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
}
}
// 工具函数
export const gradientUtils = {
// 根据用户ID获取主题
getThemeByUserId: (userId?: number): GradientTheme => {
if (!userId) return gradientThemes[0]
const index = userId % gradientThemes.length
return gradientThemes[index]
},
// 根据主题名获取主题
getThemeByName: (name: string): GradientTheme | undefined => {
return gradientThemes.find(theme => theme.name === name)
},
// 调整颜色亮度
adjustColorBrightness: (color: string, percent: number): string => {
const num = parseInt(color.replace("#", ""), 16)
const amt = Math.round(2.55 * percent)
const R = (num >> 16) + amt
const G = (num >> 8 & 0x00FF) + amt
const B = (num & 0x0000FF) + amt
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
(B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1)
},
// 生成自定义渐变
createGradient: (color1: string, color2: string, direction = '135deg'): string => {
return `linear-gradient(${direction}, ${color1} 0%, ${color2} 100%)`
},
// 获取渐变的主色调
getPrimaryColor: (gradient: string): string => {
const match = gradient.match(/#[a-fA-F0-9]{6}/)
return match ? match[0] : '#667eea'
}
}
// 动画渐变
export const animatedGradients = {
flowing: {
background: 'linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #f5576c)',
backgroundSize: '400% 400%',
animation: 'gradientFlow 15s ease infinite'
},
pulse: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
animation: 'gradientPulse 3s ease-in-out infinite'
}
}
// CSS 动画关键帧(需要在全局样式中定义)
export const gradientAnimations = `
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes gradientPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
`
Loading…
Cancel
Save