Browse Source

feat(theme): 实现主题切换系统并优化经销商相关页面

- 新增主题切换系统,支持智能主题和手动选择
- 更新经销商首页、团队、订单、提现等页面样式
- 添加主题相关的Hook和样式工具函数
- 优化部分组件样式以适配新主题
dev
科技小王子 6 days ago
parent
commit
9d9762ef17
  1. 191
      docs/THEME_SYSTEM_GUIDE.md
  2. 3
      src/app.config.ts
  3. 62
      src/components/CouponFilter.tsx
  4. 3
      src/components/CouponList.tsx
  5. 8
      src/components/FixedButton.tsx
  6. 2
      src/components/GradientThemeSelector.tsx
  7. 8
      src/dealer/index.scss
  8. 172
      src/dealer/index.tsx
  9. 4
      src/dealer/info.tsx
  10. 11
      src/dealer/orders/index.tsx
  11. 52
      src/dealer/team/index.tsx
  12. 23
      src/dealer/withdraw/index.tsx
  13. 95
      src/hooks/useTheme.ts
  14. 67
      src/pages/cart/cart.tsx
  15. 6
      src/pages/user/components/IsDealer.tsx
  16. 7
      src/pages/user/components/UserCell.tsx
  17. 4
      src/shop/orderConfirm/index.tsx
  18. 4
      src/shop/orderConfirmCart/index.tsx
  19. 60
      src/styles/gradients.ts
  20. 8
      src/user/order/components/OrderList.tsx
  21. 36
      src/user/store/verification.tsx
  22. 4
      src/user/theme/index.config.ts
  23. 179
      src/user/theme/index.tsx

191
docs/THEME_SYSTEM_GUIDE.md

@ -0,0 +1,191 @@
# 🎨 主题切换系统使用指南
## 📖 功能概述
我们为你的小程序实现了一套完整的主题切换系统,用户可以选择不同的渐变主题来个性化界面。
## 🎯 功能特点
### ✨ 智能主题
- **自动选择**:根据用户ID自动分配个性化主题
- **8种精美主题**:海洋蓝紫、日落橙红、清新蓝绿、自然绿青、温暖橙黄、梦幻紫粉、经典蓝白、优雅灰黑
- **持久化存储**:用户选择会自动保存
### 🎨 手动选择
- **实时预览**:选择主题时立即看到效果
- **一键保存**:保存后自动返回上级页面
- **全局应用**:主题会应用到所有支持的页面
## 🚀 如何使用
### 1. 访问主题设置页面
用户可以通过以下方式进入主题设置:
```javascript
// 在任何页面中跳转到主题设置
Taro.navigateTo({
url: '/user/theme/index'
})
```
### 2. 在用户中心添加入口
你可以在用户中心页面添加"主题设置"入口:
```jsx
<Cell
title="主题设置"
extra="个性化界面"
onClick={() => Taro.navigateTo({ url: '/user/theme/index' })}
/>
```
### 3. 在组件中使用主题
#### 使用 useThemeStyles Hook
```jsx
import { useThemeStyles } from '@/hooks/useTheme'
const MyComponent = () => {
const themeStyles = useThemeStyles()
return (
<View style={themeStyles.primaryBackground}>
<Text style={{ color: themeStyles.textColor }}>
这里会应用当前主题的样式
</Text>
<Button style={themeStyles.primaryButton}>
主题按钮
</Button>
</View>
)
}
```
#### 使用 useTheme Hook
```jsx
import { useTheme } from '@/hooks/useTheme'
const MyComponent = () => {
const { currentTheme, setTheme, isAutoTheme } = useTheme()
return (
<View style={{ background: currentTheme.background }}>
<Text style={{ color: currentTheme.textColor }}>
当前主题:{currentTheme.description}
</Text>
{isAutoTheme && <Text>使用智能主题</Text>}
</View>
)
}
```
## 🎨 可用主题列表
| 主题名称 | 主色调 | 描述 | 适用场景 |
|---------|--------|------|----------|
| ocean | 蓝紫色 | 海洋蓝紫 - 科技感与专业感 | 商务、科技类应用 |
| sunset | 橙红色 | 日落橙红 - 活力与热情 | 社交、娱乐类应用 |
| fresh | 蓝绿色 | 清新蓝绿 - 清新与活力 | 健康、运动类应用 |
| nature | 绿青色 | 自然绿青 - 生机与成长 | 环保、教育类应用 |
| warm | 橙黄色 | 温暖橙黄 - 温馨与舒适 | 生活、家居类应用 |
| dream | 紫粉色 | 梦幻紫粉 - 浪漫与梦幻 | 时尚、美妆类应用 |
| classic | 蓝白色 | 经典蓝白 - 简约与专业 | 办公、工具类应用 |
| elegant | 灰黑色 | 优雅灰黑 - 高端与品质 | 奢侈品、艺术类应用 |
## 🔧 开发者指南
### 添加新主题
`src/styles/gradients.ts` 中添加新主题:
```typescript
export const gradientThemes: GradientTheme[] = [
// 现有主题...
{
name: 'custom',
primary: '#your-primary-color',
secondary: '#your-secondary-color',
background: 'linear-gradient(135deg, #color1 0%, #color2 100%)',
textColor: '#ffffff',
description: '自定义主题 - 你的描述'
}
]
```
### 在新页面中应用主题
```jsx
import { useThemeStyles } from '@/hooks/useTheme'
const NewPage = () => {
const themeStyles = useThemeStyles()
return (
<View className="min-h-screen bg-gray-50">
{/* 使用主题背景的头部 */}
<View
className="px-4 py-6 relative overflow-hidden"
style={themeStyles.primaryBackground}
>
<Text className="text-lg font-bold" style={{ color: themeStyles.textColor }}>
页面标题
</Text>
</View>
{/* 其他内容 */}
<View className="p-4">
<Button style={themeStyles.primaryButton}>
主题按钮
</Button>
</View>
</View>
)
}
```
## 📱 用户体验
### 智能主题算法
系统会根据用户ID生成一个稳定的主题选择:
```typescript
// 用户ID为 "12345" 的用户总是会得到相同的主题
const theme = gradientUtils.getThemeByUserId("12345")
```
### 主题持久化
- 用户选择会保存在本地存储中
- 重新打开应用时会自动应用上次选择的主题
- 支持"智能主题"和"手动选择"两种模式
## 🎉 效果展示
### 智能主题模式
- 每个用户都有独特的个性化主题
- 基于用户ID算法分配,确保稳定性
- 提升用户归属感和个性化体验
### 手动选择模式
- 用户可以自由选择喜欢的主题
- 实时预览效果
- 一键保存并应用
## 🔄 更新日志
### v1.0.0 (2025-01-18)
- ✅ 实现8种精美渐变主题
- ✅ 智能主题自动分配算法
- ✅ 主题切换页面UI
- ✅ useTheme 和 useThemeStyles Hooks
- ✅ 主题持久化存储
- ✅ 小程序兼容性优化
---
**现在你的用户可以享受个性化的主题体验了!** 🎨✨

3
src/app.config.ts

@ -43,7 +43,8 @@ export default defineAppConfig({
"gift/index",
"gift/redeem",
"gift/detail",
"store/verification"
"store/verification",
"theme/index"
]
},
{

62
src/components/CouponFilter.tsx

@ -1,7 +1,7 @@
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Popup, Radio, RadioGroup, Divider } from '@nutui/nutui-react-taro'
import { Filter, Close } from '@nutui/icons-react-taro'
import React, {useState} from 'react'
import {View, Text} from '@tarojs/components'
import {Button, Popup, Radio, RadioGroup, Divider} from '@nutui/nutui-react-taro'
import {Filter, Close} from '@nutui/icons-react-taro'
export interface CouponFilterProps {
/** 是否显示筛选器 */
@ -20,41 +20,41 @@ export interface CouponFilterProps {
}
const CouponFilter: React.FC<CouponFilterProps> = ({
visible,
filters,
onFiltersChange,
onClose
}) => {
visible,
filters,
onFiltersChange,
onClose
}) => {
const [tempFilters, setTempFilters] = useState(filters)
// 优惠券类型选项
const typeOptions = [
{ label: '全部类型', value: '' },
{ label: '满减券', value: '10' },
{ label: '折扣券', value: '20' },
{ label: '免费券', value: '30' }
{label: '全部类型', value: ''},
{label: '满减券', value: '10'},
{label: '折扣券', value: '20'},
{label: '免费券', value: '30'}
]
// 最低金额选项
const minAmountOptions = [
{ label: '不限', value: '' },
{ label: '10元以上', value: '10' },
{ label: '50元以上', value: '50' },
{ label: '100元以上', value: '100' },
{ label: '200元以上', value: '200' }
{label: '不限', value: ''},
{label: '10元以上', value: '10'},
{label: '50元以上', value: '50'},
{label: '100元以上', value: '100'},
{label: '200元以上', value: '200'}
]
// 排序选项
const sortOptions = [
{ label: '创建时间', value: 'createTime' },
{ label: '优惠金额', value: 'amount' },
{ label: '到期时间', value: 'expireTime' }
{label: '创建时间', value: 'createTime'},
{label: '优惠金额', value: 'amount'},
{label: '到期时间', value: 'expireTime'}
]
// 排序方向选项
const sortOrderOptions = [
{ label: '升序', value: 'asc' },
{ label: '降序', value: 'desc' }
{label: '升序', value: 'asc'},
{label: '降序', value: 'desc'}
]
// 重置筛选条件
@ -86,17 +86,17 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
<Popup
visible={visible}
position="right"
style={{ width: '80%', height: '100%' }}
style={{width: '80%', height: '100%'}}
>
<View className="h-full flex flex-col">
{/* 头部 */}
<View className="flex items-center justify-between p-4 border-b border-gray-100">
<View className="flex items-center">
<Filter size="20" className="text-gray-600 mr-2" />
<Filter size="20" className="text-gray-600 mr-2"/>
<Text className="text-lg font-semibold"></Text>
</View>
<View onClick={onClose}>
<Close size="20" className="text-gray-600" />
<Close size="20" className="text-gray-600"/>
</View>
</View>
@ -109,7 +109,7 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
</Text>
<RadioGroup
value={tempFilters.type?.[0]?.toString() || ''}
onChange={(value) => {
onChange={(value: any) => {
updateTempFilters('type', value ? [parseInt(value)] : [])
}}
>
@ -121,7 +121,7 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
</RadioGroup>
</View>
<Divider />
<Divider/>
{/* 最低消费金额 */}
<View className="mb-6">
@ -130,7 +130,7 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
</Text>
<RadioGroup
value={tempFilters.minAmount?.toString() || ''}
onChange={(value) => {
onChange={(value: any) => {
updateTempFilters('minAmount', value ? parseInt(value) : undefined)
}}
>
@ -142,7 +142,7 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
</RadioGroup>
</View>
<Divider />
<Divider/>
{/* 排序方式 */}
<View className="mb-6">
@ -161,7 +161,7 @@ const CouponFilter: React.FC<CouponFilterProps> = ({
</RadioGroup>
</View>
<Divider />
<Divider/>
{/* 排序方向 */}
<View className="mb-6">

3
src/components/CouponList.tsx

@ -77,9 +77,6 @@ const CouponList: React.FC<CouponListProps> = ({
scrollX
className="flex p-4 gap-2 overflow-x-auto"
showScrollbar={false}
style={{
WebkitOverflowScrolling: 'touch'
}}
>
{coupons.map((coupon, index) => (
<View

8
src/components/FixedButton.tsx

@ -7,9 +7,10 @@ interface FixedButtonProps {
onClick?: () => void;
icon?: React.ReactNode;
disabled?: boolean;
background?: string;
}
function FixedButton({text, onClick, icon, disabled}: FixedButtonProps) {
function FixedButton({text, onClick, icon, disabled, background}: FixedButtonProps) {
return (
<>
{/* 底部安全区域占位 */}
@ -17,7 +18,10 @@ function FixedButton({text, onClick, icon, disabled}: FixedButtonProps) {
<View
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
<Button
type="success"
type="primary"
style={{
background
}}
size="large"
block
icon={icon}

2
src/components/GradientThemeSelector.tsx

@ -46,7 +46,7 @@ const GradientThemeSelector: React.FC<GradientThemeSelectorProps> = ({
}}
>
<View className="w-full h-full flex items-center justify-center">
<Text className="text-white text-xs font-bold drop-shadow-sm">
<Text className="text-white text-xs font-bold">
</Text>
</View>

8
src/dealer/index.scss

@ -0,0 +1,8 @@
/* 添加这段样式后,Primary Button 会变成绿色 */
:root {
--nutui-color-primary: green;
--nutui-color-primary-stop1: green;
--nutui-color-primary-stop2: green;
// 间隔线/容错线用于结构或信息分割
--nutui-black-2: rgba(255, 0, 0, 0.08);
}

172
src/dealer/index.tsx

@ -1,34 +1,28 @@
import React from 'react'
import {View, Text} from '@tarojs/components'
import {Button, Cell, CellGroup, Tag, Grid, Avatar, Divider} from '@nutui/nutui-react-taro'
import {ConfigProvider, Button, Grid, Avatar} 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 { useThemeStyles } from '@/hooks/useTheme'
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => {
const {
dealerUser,
loading,
error,
refresh,
} = useDealerUser()
// 跳转到申请页面
const navigateToApply = () => {
Taro.navigateTo({
url: '/dealer/apply/add'
})
}
// 使用主题样式
const themeStyles = useThemeStyles()
// 导航到各个功能页面
const navigateToPage = (url: string) => {
@ -59,6 +53,8 @@ const DealerIndex: React.FC = () => {
return userTheme.background
}
console.log(getGradientBackground(),'getGradientBackground()')
if (error) {
return (
<View className="p-4">
@ -77,46 +73,45 @@ const DealerIndex: React.FC = () => {
<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 className="px-4 py-6 relative overflow-hidden" style={themeStyles.primaryBackground}>
{/* 装饰性背景元素 - 小程序兼容版本 */}
<View className="absolute w-32 h-32 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
top: '-16px',
right: '-16px'
}}></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 className="absolute w-24 h-24 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.08)',
bottom: '-12px',
left: '-12px'
}}></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 className="absolute w-16 h-16 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.05)',
top: '60px',
left: '120px'
}}></View>
<View className="flex items-center justify-between relative z-10">
<View className="flex items-center justify-between relative z-10 mb-4">
<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)'
border: '2px solid rgba(255, 255, 255, 0.3)'
}}
/>
<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)'
<View className="flex-1 flex-col">
<View className="text-white text-lg font-bold mb-1" style={{
}}>
{dealerUser?.realName || '分销商'}
</Text>
<Text className="text-sm" style={{
</View>
<View className="text-sm" style={{
color: 'rgba(255, 255, 255, 0.8)'
}}>
ID: {dealerUser.userId} | : {dealerUser.refereeId || '无'}
</Text>
</View>
</View>
<View className="text-right">
<View className="text-right hidden">
<Text className="text-xs" style={{
color: 'rgba(255, 255, 255, 0.9)'
}}></Text>
@ -133,34 +128,33 @@ const DealerIndex: React.FC = () => {
{/* 佣金统计卡片 */}
{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="mb-4">
<Text className="font-semibold text-gray-800"></Text>
</View>
<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%)'
background: businessGradients.money.available
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
<Text className="text-2xl font-bold mb-1 text-white">
¥{formatMoney(dealerUser.money)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></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%)'
background: businessGradients.money.frozen
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
<Text className="text-2xl font-bold mb-1 text-white">
¥{formatMoney(dealerUser.freezeMoney)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></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%)'
background: businessGradients.money.total
}}>
<Text className="text-2xl font-bold mb-1 text-white drop-shadow-sm">
<Text className="text-2xl font-bold mb-1 text-white">
¥{formatMoney(dealerUser.totalMoney)}
</Text>
<Text className="text-xs text-white text-opacity-90"></Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View>
</View>
</View>
@ -171,27 +165,28 @@ const DealerIndex: React.FC = () => {
<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"
<View
className="text-gray-400 text-sm flex items-center"
onClick={() => navigateToPage('/dealer/team/index')}
>
<ArrowRight size="12"/>
</Text>
<Text></Text>
<ArrowRight size="12"/>
</View>
</View>
<View className="grid grid-cols-3 gap-4">
<View className="text-center">
<View className="text-center grid">
<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">
<View className="text-center grid">
<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">
<View className="text-center grid">
<Text className="text-xl font-bold text-pink-500 mb-1">
{dealerUser.thirdNum || 0}
</Text>
@ -203,44 +198,45 @@ const DealerIndex: React.FC = () => {
{/* 功能导航 */}
<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 className="font-semibold mb-4 text-gray-800"></View>
<ConfigProvider>
<Grid columns={4}>
<Grid.Item text="分销订单" 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>
</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"/>
</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>
<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"/>
</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>
<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"/>
</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>
<Text className="text-xs text-gray-600">广</Text>
</View>
</Grid.Item>
</Grid>
</Grid.Item>
</Grid>
</ConfigProvider>
</View>
</View>

4
src/dealer/info.tsx

@ -90,7 +90,7 @@ const DealerInfo: React.FC = () => {
</CellGroup>
{/* 操作按钮 */}
<View className="mt-6 space-y-3">
<View className="mt-6 gap-2">
<Button
type="primary"
size="large"
@ -104,7 +104,7 @@ const DealerInfo: React.FC = () => {
{/* 经销商权益 */}
<View className="bg-white mx-4 mt-4 rounded-lg p-4">
<Text className="font-semibold mb-3"></Text>
<View className="space-y-2">
<View className="gap-2">
<Text className="text-sm text-gray-600">
</Text>

11
src/dealer/orders/index.tsx

@ -1,10 +1,10 @@
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Cell, Empty, Tabs, Tag, Button, PullToRefresh } from '@nutui/nutui-react-taro'
import { Empty, Tabs, Tag, PullToRefresh } from '@nutui/nutui-react-taro'
const DealerOrders: React.FC = () => {
const [activeTab, setActiveTab] = useState('0')
const [refreshing, setRefreshing] = useState(false)
const [activeTab, setActiveTab] = useState<string>('0')
const [refreshing, setRefreshing] = useState<boolean>(false)
// 模拟订单数据
const mockOrders = [
@ -65,7 +65,7 @@ const DealerOrders: React.FC = () => {
{order.customerName}
</Text>
</View>
<Tag type={getStatusColor(order.status)} size="small">
<Tag type={getStatusColor(order.status)}>
{getStatusText(order.status)}
</Tag>
</View>
@ -107,9 +107,10 @@ const DealerOrders: React.FC = () => {
</View>
{/* 订单列表 */}
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs value={activeTab} onChange={() => setActiveTab}>
<Tabs.TabPane title="全部" value="0">
<PullToRefresh
// @ts-ignore
loading={refreshing}
onRefresh={handleRefresh}
>

52
src/dealer/team/index.tsx

@ -1,8 +1,7 @@
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
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'
import { Empty, Tabs, Avatar, Tag, Progress } from '@nutui/nutui-react-taro'
import { User, Star, StarFill } from '@nutui/icons-react-taro'
const DealerTeam: React.FC = () => {
const [activeTab, setActiveTab] = useState('0')
@ -63,7 +62,7 @@ const DealerTeam: React.FC = () => {
const getLevelIcon = (level: number) => {
switch (level) {
case 1: return <Crown color={getLevelColor(level)} size="16" />
case 1: return <StarFill 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" />
@ -96,7 +95,6 @@ const DealerTeam: React.FC = () => {
<View className="text-right">
<Tag
type={member.status === 'active' ? 'success' : 'default'}
size="small"
>
{member.status === 'active' ? '活跃' : '沉默'}
</Tag>
@ -132,26 +130,28 @@ const DealerTeam: React.FC = () => {
<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 className="absolute w-32 h-32 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
top: '-16px',
right: '-16px'
}}></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 className="absolute w-20 h-20 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.05)',
bottom: '-10px',
left: '-10px'
}}></View>
<View className="relative z-10">
<Text className="text-lg font-bold mb-4 text-white drop-shadow-sm"></Text>
<Text className="text-lg font-bold mb-4 text-white"></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>
<Text className="text-2xl font-bold mb-1 text-white">{teamStats.total}</Text>
<Text className="text-sm" style={{ color: 'rgba(255, 255, 255, 0.8)' }}></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>
<Text className="text-2xl font-bold mb-1 text-white">¥{teamStats.monthlyCommission}</Text>
<Text className="text-sm" style={{ color: 'rgba(255, 255, 255, 0.8)' }}></Text>
</View>
</View>
</View>
@ -160,18 +160,18 @@ const DealerTeam: React.FC = () => {
{/* 层级分布 */}
<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="gap-2">
<View className="flex items-center justify-between">
<View className="flex items-center">
<Crown color="#f59e0b" size="16" className="mr-2" />
<StarFill 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}
percent={(teamStats.firstLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#f59e0b"
background="#f59e0b"
className="w-20"
/>
</View>
@ -185,9 +185,9 @@ const DealerTeam: React.FC = () => {
<View className="flex items-center">
<Text className="text-sm font-semibold mr-2">{teamStats.secondLevel}</Text>
<Progress
percentage={(teamStats.secondLevel / teamStats.total) * 100}
percent={(teamStats.secondLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#8b5cf6"
background="#8b5cf6"
className="w-20"
/>
</View>
@ -201,9 +201,9 @@ const DealerTeam: React.FC = () => {
<View className="flex items-center">
<Text className="text-sm font-semibold mr-2">{teamStats.thirdLevel}</Text>
<Progress
percentage={(teamStats.thirdLevel / teamStats.total) * 100}
percent={(teamStats.thirdLevel / teamStats.total) * 100}
strokeWidth="6"
strokeColor="#ec4899"
background="#ec4899"
className="w-20"
/>
</View>
@ -233,7 +233,7 @@ const DealerTeam: React.FC = () => {
return (
<View className="bg-gray-50 min-h-screen">
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs value={activeTab} onChange={() => setActiveTab}>
<Tabs.TabPane title="团队总览" value="0">
{renderOverview()}
</Tabs.TabPane>

23
src/dealer/withdraw/index.tsx

@ -11,8 +11,8 @@ import {
Tag,
Empty
} from '@nutui/nutui-react-taro'
import { Money, ArrowRight } from '@nutui/icons-react-taro'
import { businessGradients, cardGradients } from '@/styles/gradients'
import { Wallet } from '@nutui/icons-react-taro'
import { businessGradients } from '@/styles/gradients'
import Taro from '@tarojs/taro'
const DealerWithdraw: React.FC = () => {
@ -84,21 +84,22 @@ const DealerWithdraw: React.FC = () => {
<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 className="absolute top-0 right-0 w-24 h-24 rounded-full" style={{
backgroundColor: 'rgba(255, 255, 255, 0.1)',
right: '-12px',
top: '-12px'
}}></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>
<Text className="text-2xl font-bold text-white">¥{availableAmount}</Text>
</View>
<View className="p-3 rounded-full" style={{
background: 'rgba(255, 255, 255, 0.2)'
}}>
<Money color="white" size="32" />
<Wallet color="white" size="32" />
</View>
</View>
<View className="mt-4 pt-4 relative z-10" style={{
@ -149,7 +150,7 @@ const DealerWithdraw: React.FC = () => {
</View>
<Form.Item name="accountType" label="提现方式" required>
<Radio.Group value={selectedAccount} onChange={setSelectedAccount}>
<Radio.Group value={selectedAccount} onChange={() => setSelectedAccount}>
<Cell.Group>
<Cell>
<Radio value="wechat"></Radio>
@ -200,7 +201,7 @@ const DealerWithdraw: React.FC = () => {
{record.account}
</Text>
</View>
<Tag type={getStatusColor(record.status)} size="small">
<Tag type={getStatusColor(record.status)}>
{getStatusText(record.status)}
</Tag>
</View>
@ -223,7 +224,7 @@ const DealerWithdraw: React.FC = () => {
return (
<View className="bg-gray-50 min-h-screen">
<Tabs value={activeTab} onChange={setActiveTab}>
<Tabs value={activeTab} onChange={() => setActiveTab}>
<Tabs.TabPane title="申请提现" value="0">
{renderWithdrawForm()}
</Tabs.TabPane>

95
src/hooks/useTheme.ts

@ -0,0 +1,95 @@
import { useState, useEffect } from 'react'
import { gradientThemes, GradientTheme, gradientUtils } from '@/styles/gradients'
import Taro from '@tarojs/taro'
export interface UseThemeReturn {
currentTheme: GradientTheme
setTheme: (themeName: string) => void
isAutoTheme: boolean
refreshTheme: () => void
}
/**
* Hook
*
*/
export const useTheme = (): UseThemeReturn => {
const [currentTheme, setCurrentTheme] = useState<GradientTheme>(gradientThemes[0])
const [isAutoTheme, setIsAutoTheme] = useState<boolean>(true)
// 获取当前主题
const getCurrentTheme = (): GradientTheme => {
const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
if (savedTheme === 'auto') {
// 自动主题:根据用户ID生成
const userId = Taro.getStorageSync('userId') || '1'
return gradientUtils.getThemeByUserId(userId)
} else {
// 手动选择的主题
return gradientThemes.find(t => t.name === savedTheme) || gradientThemes[0]
}
}
// 初始化主题
useEffect(() => {
const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
setIsAutoTheme(savedTheme === 'auto')
setCurrentTheme(getCurrentTheme())
}, [])
// 设置主题
const setTheme = (themeName: string) => {
try {
Taro.setStorageSync('user_theme', themeName)
setIsAutoTheme(themeName === 'auto')
setCurrentTheme(getCurrentTheme())
} catch (error) {
console.error('保存主题失败:', error)
}
}
// 刷新主题(用于自动主题模式下用户信息变更时)
const refreshTheme = () => {
setCurrentTheme(getCurrentTheme())
}
return {
currentTheme,
setTheme,
isAutoTheme,
refreshTheme
}
}
/**
*
*
*/
export const useThemeStyles = () => {
const { currentTheme } = useTheme()
return {
// 主要背景样式
primaryBackground: {
background: currentTheme.background,
color: currentTheme.textColor
},
// 按钮样式
primaryButton: {
background: currentTheme.background,
border: 'none',
color: currentTheme.textColor
},
// 强调色
accentColor: currentTheme.primary,
// 文字颜色
textColor: currentTheme.textColor,
// 完整主题对象
theme: currentTheme
}
}

67
src/pages/cart/cart.tsx

@ -10,7 +10,7 @@ import {
Divider,
ConfigProvider
} from '@nutui/nutui-react-taro';
import {ArrowLeft, Del, Shopping} from '@nutui/icons-react-taro';
import {ArrowLeft, Del} from '@nutui/icons-react-taro';
import {View} from '@tarojs/components';
import {CartItem, useCart} from "@/hooks/useCart";
import './cart.scss';
@ -48,11 +48,11 @@ function Cart() {
useShareAppMessage(() => {
return {
title: '购物车 - 网宿小店',
success: function (res) {
console.log('分享成功', res);
success: function () {
console.log('分享成功');
},
fail: function (res) {
console.log('分享失败', res);
fail: function () {
console.log('分享失败');
}
};
});
@ -203,15 +203,25 @@ function Cart() {
>
<span className="text-lg">({cartCount})</span>
</NavBar>
<Empty
description="购物车空空如也"
actions={[{ text: '去逛逛' }]}
{/* 垂直居中的空状态容器 */}
<View
className="flex items-center justify-center"
style={{
marginTop: `${statusBarHeight + 50}px`,
height: `calc(100vh - ${statusBarHeight + 150}px)`,
paddingTop: `${statusBarHeight + 50}px`,
backgroundColor: 'transparent'
}}
onClick={() => Taro.switchTab({ url: '/pages/index/index' })}
/>
>
<Empty
description="购物车空空如也"
actions={[{ text: '去逛逛' }]}
style={{
backgroundColor: 'transparent'
}}
onClick={() => Taro.switchTab({ url: '/pages/index/index' })}
/>
</View>
</>
)
}
@ -251,33 +261,8 @@ function Cart() {
</NavBar>
{/* 购物车内容 */}
<View
className="pt-24"
style={{ backgroundColor: cartItems.length === 0 ? 'transparent' : undefined }}
>
{cartItems.length === 0 ? (
// 空购物车
<View
className="cart-empty-container flex flex-col items-center justify-center h-96"
style={{ backgroundColor: 'transparent' }}
>
<Empty
image={<Shopping size={80}/>}
description="购物车空空如也"
style={{ backgroundColor: 'transparent' }}
>
<Button
type="primary"
size="small"
onClick={() => Taro.switchTab({url: '/pages/index/index'})}
>
</Button>
</Empty>
</View>
) : (
<>
{/* 商品列表 */}
<View className="pt-24">
{/* 商品列表 */}
<View className="bg-white">
{cartItems.map((item: CartItem, index: number) => (
<View key={item.goodsId}>
@ -361,10 +346,8 @@ function Cart() {
</View>
</View>
{/* 底部安全区域占位 */}
<View className="h-20"></View>
</>
)}
{/* 底部安全区域占位 */}
<View className="h-20"></View>
</View>
</>
);

6
src/pages/user/components/IsDealer.tsx

@ -27,12 +27,13 @@ const UserCell = () => {
backgroundImage: 'linear-gradient(to right bottom, #e53e3e, #c53030)',
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/admin/index', true)}>
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Setting className={'text-white '} size={16}/>
<Text style={{fontSize: '16px'}} className={'pl-3 text-white font-medium'}></Text>
</View>
}
extra={<ArrowRight color="#ffffff" size={18}/>}
onClick={() => navTo('/admin/index', true)}
/>
</View>
</>
@ -52,7 +53,7 @@ const UserCell = () => {
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/dealer/index', true)}>
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Reward className={'text-orange-100 '} size={16}/>
<Text style={{fontSize: '16px'}}
className={'pl-3 text-orange-100 font-medium'}></Text>
@ -60,6 +61,7 @@ const UserCell = () => {
</View>
}
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/dealer/index', true)}
/>
</View>
</>

7
src/pages/user/components/UserCell.tsx

@ -122,6 +122,13 @@ const UserCell = () => {
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile', true)}
/>
<Cell
className="nutui-cell-clickable"
title="切换主题"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/theme/index', true)}
/>
<Cell
className="nutui-cell-clickable"
title="退出登录"

4
src/shop/orderConfirm/index.tsx

@ -582,9 +582,7 @@ const OrderConfirm = () => {
<Gap height={50}/>
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10'} style={{
boxShadow: '0 -2px 4px 0 rgba(0,0,0,0.10)'
}}>
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
<View className={'btn-bar flex justify-between items-center'}>
<div className={'flex flex-col justify-center items-start mx-4'}>
<View className={'flex items-center gap-2'}>

4
src/shop/orderConfirmCart/index.tsx

@ -195,9 +195,7 @@ const OrderConfirm = () => {
<Gap height={50} />
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10'} style={{
boxShadow: '0 -2px 4px 0 rgba(0,0,0,0.10)'
}}>
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
<View className={'btn-bar flex justify-between items-center'}>
<div className={'flex justify-center items-center mx-4'}>
<span className={'total-price text-sm text-gray-500'}></span>

60
src/styles/gradients.ts

@ -91,7 +91,7 @@ export const businessGradients = {
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%)',
@ -99,7 +99,7 @@ export const businessGradients = {
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%)',
@ -108,51 +108,47 @@ export const businessGradients = {
}
}
// 卡片渐变样式
// 卡片渐变样式(小程序兼容版本)
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)'
border: '1px solid rgba(255, 255, 255, 0.2)'
// 注意:小程序不支持 backdropFilter
},
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)'
border: '1px solid rgba(255, 255, 255, 0.8)'
// 注意:小程序不支持 boxShadow,使用边框和背景替代
},
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)'
border: '1px solid rgba(255, 255, 255, 0.9)'
// 注意:小程序不支持 boxShadow,使用边框和背景替代
}
}
// 文字渐变样式
// 文字渐变样式(小程序兼容版本 - 使用纯色替代)
export const textGradients = {
primary: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
color: '#667eea'
// 注意:小程序不支持 WebkitBackgroundClip 和 WebkitTextFillColor,使用纯色替代
},
success: {
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
color: '#10b981'
// 注意:小程序不支持文字渐变,使用纯色替代
},
warning: {
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
color: '#f59e0b'
// 注意:小程序不支持文字渐变,使用纯色替代
},
danger: {
background: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent'
color: '#ef4444'
// 注意:小程序不支持文字渐变,使用纯色替代
}
}
@ -164,12 +160,12 @@ export const gradientUtils = {
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)
@ -181,12 +177,12 @@ export const gradientUtils = {
(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}/)
@ -201,7 +197,7 @@ export const animatedGradients = {
backgroundSize: '400% 400%',
animation: 'gradientFlow 15s ease infinite'
},
pulse: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
animation: 'gradientPulse 3s ease-in-out infinite'

8
src/user/order/components/OrderList.tsx

@ -16,8 +16,8 @@ const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
width: '100%',
padding: '0',
overflowY: 'auto',
overflowX: 'hidden',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
overflowX: 'hidden'
// 注意:小程序不支持 boxShadow
})
// 统一的订单状态标签配置,与后端 statusFilter 保持一致
@ -362,8 +362,8 @@ function OrderList(props: OrderListProps) {
borderBottom: '1px solid #e5e5e5'
}}
tabStyle={{
backgroundColor: '#ffffff',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
backgroundColor: '#ffffff'
// 注意:小程序不支持 boxShadow
}}
value={tapIndex}
onChange={(paneKey) => {

36
src/user/store/verification.tsx

@ -31,6 +31,7 @@ const StoreVerification: React.FC = () => {
const json = JSON.parse(res.result)
console.log(json, 'json')
if (json.businessType === 'gift') {
// 调用解密接口
handleDecryptAndVerify(json.token, json.data).then()
}
}
@ -48,20 +49,35 @@ const StoreVerification: React.FC = () => {
// 调用解密接口
const handleDecryptAndVerify = async (token: string, encryptedData: string) => {
const decryptedData = await decryptQrData({token, encryptedData})
console.log('解密成功:', decryptedData)
setScanResult(`${decryptedData}`)
setVerificationCode(`${decryptedData}`)
await handleVerification(`${decryptedData}`)
setLoading(false)
decryptQrData({token, encryptedData}).then(res => {
const decryptedData = res;
console.log('解密结果:', decryptedData)
console.log('解密成功:', decryptedData)
setScanResult(`${decryptedData}`)
setVerificationCode(`${decryptedData}`)
handleVerification(`${decryptedData}`)
}).catch(() => {
console.error('解密失败:')
Taro.showToast({
title: `token失效,请刷新二维码重试`,
icon: 'none'
})
}).finally(() => {
setLoading(false)
})
}
// 验证商品信息
const handleVerification = async (code?: string) => {
setGiftInfo(null)
setVerificationCode(`${code}`)
// 这里应该调用后端API验证核销码
const gift = await getShopGiftByCode(`${code}`)
// 设置礼品信息用于显示
setGiftInfo(gift)
if(gift){
// 设置礼品信息用于显示
setGiftInfo(gift)
}
}
// 手动输入核销码验证
@ -264,10 +280,8 @@ const StoreVerification: React.FC = () => {
{giftInfo.description && (
<>
<View className="text-sm text-gray-600 mb-2" style={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
// 注意:小程序不支持 WebKit 文本截断属性
}}>
{giftInfo.description}
</View>

4
src/user/theme/index.config.ts

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '主题设置',
navigationBarTextStyle: 'black'
})

179
src/user/theme/index.tsx

@ -0,0 +1,179 @@
import React, { useState, useEffect } from 'react'
import { View, Text } from '@tarojs/components'
import { Cell, CellGroup, Radio } from '@nutui/nutui-react-taro'
import { gradientThemes, GradientTheme, gradientUtils } from '@/styles/gradients'
import Taro from '@tarojs/taro'
import FixedButton from "@/components/FixedButton";
const ThemeSelector: React.FC = () => {
const [selectedTheme, setSelectedTheme] = useState<string>('')
const [currentTheme, setCurrentTheme] = useState<GradientTheme | null>(null)
// 获取当前主题
useEffect(() => {
const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
setSelectedTheme(savedTheme)
if (savedTheme === 'auto') {
// 自动主题:根据用户ID生成
const userId = Taro.getStorageSync('userId') || '1'
const theme = gradientUtils.getThemeByUserId(userId)
setCurrentTheme(theme)
} else {
// 手动选择的主题
const theme = gradientThemes.find(t => t.name === savedTheme)
setCurrentTheme(theme || gradientThemes[0])
}
}, [])
// 保存主题设置
const saveTheme = (themeName: string) => {
try {
Taro.setStorageSync('user_theme', themeName)
setSelectedTheme(themeName)
if (themeName === 'auto') {
const userId = Taro.getStorageSync('userId') || '1'
const theme = gradientUtils.getThemeByUserId(userId)
setCurrentTheme(theme)
} else {
const theme = gradientThemes.find(t => t.name === themeName)
setCurrentTheme(theme || gradientThemes[0])
}
Taro.showToast({
title: '主题已保存',
icon: 'success',
})
// 延迟返回,让用户看到效果
setTimeout(() => {
Taro.navigateBack()
}, 1000)
} catch (error) {
Taro.showToast({
title: '保存失败',
icon: 'error',
})
}
}
// 预览主题
const previewTheme = (themeName: string) => {
if (themeName === 'auto') {
const userId = Taro.getStorageSync('userId') || '1'
const theme = gradientUtils.getThemeByUserId(userId)
setCurrentTheme(theme)
} else {
const theme = gradientThemes.find(t => t.name === themeName)
setCurrentTheme(theme || gradientThemes[0])
}
}
return (
<View className="min-h-screen bg-gray-50">
{/* 当前主题预览 */}
{currentTheme && (
<View
className="mx-4 mt-4 rounded-xl p-6 text-center"
style={{
background: currentTheme.background,
color: currentTheme.textColor
}}
>
<Text className="text-lg font-bold mb-2"></Text>
<Text className="text-sm opacity-90 px-2">{currentTheme.description}</Text>
<View className="mt-4 flex justify-center space-x-4">
<View
className="w-8 h-8 rounded-full"
style={{ backgroundColor: currentTheme.primary }}
></View>
{currentTheme.secondary && (
<View
className="w-8 h-8 rounded-full"
style={{ backgroundColor: currentTheme.secondary }}
></View>
)}
</View>
</View>
)}
{/* 主题选择 */}
<View className="mt-4">
<CellGroup>
<Cell
className="px-4 py-2"
title={
<View className="flex items-center justify-between w-full">
<View>
<Text className="font-medium"></Text>
<Text className="text-sm text-gray-500 mt-1">
ID自动选择个性化主题
</Text>
</View>
<Radio
checked={selectedTheme === 'auto'}
onChange={() => {
setSelectedTheme('auto')
previewTheme('auto')
}}
/>
</View>
}
onClick={() => {
setSelectedTheme('auto')
previewTheme('auto')
}}
/>
</CellGroup>
<View className="mt-4">
<Text className="text-sm text-gray-600 px-4 mb-2"></Text>
<CellGroup>
{gradientThemes.map((theme) => (
<Cell
key={theme.name}
className="px-4 py-3"
title={
<View className="flex items-center justify-between w-full">
<View className="flex items-center">
<View
className="w-6 h-6 rounded-full mr-3"
style={{ background: theme.background }}
></View>
<View>
<Text className="font-medium">{theme.description.split(' - ')[0]}</Text>
<Text className="text-sm text-gray-500 mt-1">
{theme.description.split(' - ')[1]}
</Text>
</View>
</View>
<Radio
checked={selectedTheme === theme.name}
onChange={() => {
setSelectedTheme(theme.name)
previewTheme(theme.name)
}}
/>
</View>
}
onClick={() => {
setSelectedTheme(theme.name)
previewTheme(theme.name)
}}
/>
))}
</CellGroup>
</View>
</View>
{/* 保存按钮 */}
<FixedButton text={'保存主题设置'} background={currentTheme?.background || '#1890ff'} onClick={() => saveTheme(selectedTheme)} />
{/* 底部安全区域 */}
<View className="h-20"></View>
</View>
)
}
export default ThemeSelector
Loading…
Cancel
Save