Compare commits

...

10 Commits

Author SHA1 Message Date
610aded9d5 feat(app): 更新应用配置和页面结构
- 修改 app.config.ts 中的页面路由配置,将购物车和发现页面替换为消息和工具箱页面
- 更新导航栏样式配置,包括背景色、标题文本和文字颜色
- 添加完整的底部标签栏配置,包含首页、消息、工具箱和个人中心四个选项
- 更改租户ID从10582到10589,并相应更新package.json和project.config.json中的项目名称
- 重构首页(index.tsx)界面布局,实现新的品牌标识卡片和功能入口网格设计
- 新增消息页面(message)的基本结构和样式文件
- 新增工具箱页面(toolbox)的功能模块和网格布局
- 添加标签栏图标资源和相关脚本文件
2026-03-11 13:08:48 +08:00
6e8d6b1c0d ```
fix(dealer): 解决客户重复报备问题

- 添加报备人身份验证逻辑,避免跨报备人抢单续报
- 当发现相同房号已被其他报备人报备时直接拦截提交
- 显示相应提示信息告知用户房号已报备
- 防止不同报备人对同一客户信息进行重复操作
```
2026-03-10 11:40:02 +08:00
a834f88aaa refactor(admin): 重构用户管理页面实现
- 将原有经销商团队页面重构为用户管理页面
- 集成分页用户查询功能替代原有团队数据获取
- 添加搜索栏和下拉刷新功能
- 实现无限滚动加载用户数据
- 添加用户角色显示和切换功能
- 优化页面布局和用户体验
- 移除原有的经销商相关代码逻辑refactor(admin): 重构用户管理页面实现角色切换功能

- 将原经销商团队页面重构为系统用户管理页面
- 集成分页用户列表查询功能
- 实现下拉刷新和无限滚动加载
- 添加用户搜索和筛选功能
- 实现用户角色切换功能
- 优化用户列表展示界面和交互体验
- 移除原有的经销商相关统计功能
- 统一使用Taro框架进行页面开发
- 修复系统角色API参数传递问题
2026-03-09 16:15:30 +08:00
d1fa0f3ec0 11 2026-03-09 16:02:49 +08:00
ba694813c4 feat(admin): 添加成员管理功能
- 新增成员管理页面,实现团队成员展示和统计功能
- 集成经销商用户数据,显示成员基本信息和业绩数据
- 实现多层级团队浏览,支持查看下级成员和返回上层操作
- 添加成员详情统计,包括订单数、贡献佣金、团队规模等指标
- 实现成员联系方式管理,支持一键拨打电话和发送消息
- 添加成员别名备注功能,方便用户识别和管理
-
2026-03-09 15:24:44 +08:00
c654f62b8c fix(admin): 修复管理员页面导航链接错误
- 在应用配置中添加了 users/index 页面路径
- 将管理员界面中的成员管理导航链接从 /dealer/orders/index 修正为 /admin/users/index
- 修正了 Grid.Item 组件的点击事件目标路径
2026-03-09 15:09:19 +08:00
429c5d282b refactor(admin): 简化经销商首页界面和功能模块
- 移除购物、京东、箭头、钱包、人员等图标导入
- 删除佣金统计卡片组件及其相关样式配置
- 移除团队统计数据展示区域
- 将功能导航标题从"分销工具"改为"管理工具"
- 将网格项目中的"分销订单"改为"成员管理"
- 移除提现申请、我的邀请、我的邀请码等功能入口
- 清理相关的渐变色样式导入和金额格式化函数
2026-03-09 15:08:33 +08:00
ceea662420 feat(customer): 优化报备人管理和权限控制
- 移除页面分页查询,仅保留精确查询接口调用
- 添加当前登录用户ID获取和报备人规范化处理逻辑
- 实现管理员可查看全部客户,普通分销商仅查看自己的权限控制
- 集成用户角色显示功能,在用户卡片组件中展示角色标签
- 修复角色名称获取逻辑,支持多种数据
2026-03-01 12:19:02 +08:00
07d35d48d7 refactor(user): 优化用户权限检查和界面显示逻辑
- 移除邀请人ID表单项并注释相关代码
- 注释掉邀请信息检测成功提示弹窗功能
- 在开发、生产、测试环境中添加本地API调试配置选项
- 添加分销商角色权限检查,非分销商无法查看客户列表
- 使用useRef避免重复显示无权限提示
- 在客户列表页面添加权限验证和加载状态处理
- 移除用户卡片中的余额、积分、优惠券、礼品卡等显示组件
- 简化用户角色名称获取逻辑,优先使用用户角色数组第一个角色名称
- 优化用户信息加载和权限验证流程
2026-02-03 16:54:09 +08:00
217f3556fc refactor(customer): 优化客户报备重复性检查逻辑
- 移除原有的三要素重复校验机制,改用房屋编码精确匹配
- 新增中文数字解析功能,支持大小写中文数字转换为阿拉伯数字
- 实现全角字符转半角处理,统一输入格式标准化
- 添加房屋编码规范化逻辑,去除常见后缀和装饰词
- 重构重复报备检查流程,使用标准化房屋编码进行精确匹配
- 保留关键词搜索兼容历史数据,确保查询准确性
- 移除冗余的组合算法和多条件查询逻辑
2026-02-03 12:20:38 +08:00
35 changed files with 1201 additions and 517 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

21
assets/tabbar/create-icons.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# 创建简单的占位图标(实际项目中应该使用设计好的图标)
# 使用 ImageMagick 创建简单的彩色圆点作为占位图标
# 首页图标
convert -size 48x48 xc:none -fill '#999999' -draw "circle 24,24 24,12" assets/tabbar/home.png
convert -size 48x48 xc:none -fill '#1890FF' -draw "circle 24,24 24,12" assets/tabbar/home-active.png
# 消息图标
convert -size 48x48 xc:none -fill '#999999' -draw "rectangle 12,16 36,32" assets/tabbar/message.png
convert -size 48x48 xc:none -fill '#1890FF' -draw "rectangle 12,16 36,32" assets/tabbar/message-active.png
# 工具箱图标
convert -size 48x48 xc:none -fill '#999999' -draw "rectangle 10,18 38,34" assets/tabbar/toolbox.png
convert -size 48x48 xc:none -fill '#1890FF' -draw "rectangle 10,18 38,34" assets/tabbar/toolbox-active.png
# 我的图标
convert -size 48x48 xc:none -fill '#999999' -draw "circle 24,18 24,8" assets/tabbar/user.png
convert -size 48x48 xc:none -fill '#1890FF' -draw "circle 24,18 24,8" assets/tabbar/user-active.png
echo "图标创建完成"

View File

@@ -0,0 +1,42 @@
const fs = require('fs');
const path = require('path');
// 简单的 1x1 像素 PNG白色
const whitePixel = Buffer.from([
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4,
0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41,
0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00,
0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0x42, 0x60, 0x82
]);
// 创建目录
const dir = path.join(__dirname, '..', 'assets', 'tabbar');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// 创建占位图标文件
const icons = [
'home.png',
'home-active.png',
'message.png',
'message-active.png',
'toolbox.png',
'toolbox-active.png',
'user.png',
'user-active.png'
];
icons.forEach(icon => {
const filePath = path.join(dir, icon);
fs.writeFileSync(filePath, whitePixel);
console.log(`Created: ${icon}`);
});
console.log('\n占位图标创建完成请替换为实际图标文件。');
console.log('图标尺寸建议81x81 像素(微信小程序推荐)');

View File

@@ -1,7 +1,7 @@
import { API_BASE_URL } from './env' import { API_BASE_URL } from './env'
// 租户ID - 请根据实际情况修改 // 租户ID - 请根据实际情况修改
export const TenantId = '10582'; export const TenantId = '10589';
// 接口地址 - 请根据实际情况修改 // 接口地址 - 请根据实际情况修改
export const BaseUrl = API_BASE_URL; export const BaseUrl = API_BASE_URL;
// 当前版本 // 当前版本

View File

@@ -3,18 +3,21 @@ export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'https://cms-api.websoft.top/api', API_BASE_URL: 'https://cms-api.websoft.top/api',
// API_BASE_URL: 'http://127.0.0.1:9200/api',
APP_NAME: '开发环境', APP_NAME: '开发环境',
DEBUG: 'true', DEBUG: 'true',
}, },
// 生产环境 // 生产环境
production: { production: {
API_BASE_URL: 'https://cms-api.websoft.top/api', API_BASE_URL: 'https://cms-api.websoft.top/api',
// API_BASE_URL: 'http://127.0.0.1:9200/api',
APP_NAME: '南南佐顿门窗', APP_NAME: '南南佐顿门窗',
DEBUG: 'false', DEBUG: 'false',
}, },
// 测试环境 // 测试环境
test: { test: {
API_BASE_URL: 'https://cms-api.websoft.top/api', API_BASE_URL: 'https://cms-api.websoft.top/api',
// API_BASE_URL: 'http://127.0.0.1:9200/api',
APP_NAME: '测试环境', APP_NAME: '测试环境',
DEBUG: 'true', DEBUG: 'true',
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "template-10582", "name": "template-10589",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"description": "WebSoft Inc.", "description": "WebSoft Inc.",

View File

@@ -1,6 +1,6 @@
{ {
"miniprogramRoot": "dist/", "miniprogramRoot": "dist/",
"projectname": "template-10582", "projectname": "template-10589",
"description": "南南佐顿门窗", "description": "南南佐顿门窗",
"appid": "wx644669e2da8d6519", "appid": "wx644669e2da8d6519",
"setting": { "setting": {

View File

@@ -3,15 +3,11 @@ import {View, Text} from '@tarojs/components'
import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro' import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro'
import { import {
User, User,
Shopping, Shopping
Dongdong,
ArrowRight,
Purse,
People
} from '@nutui/icons-react-taro' } from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import { useThemeStyles } from '@/hooks/useTheme' import { useThemeStyles } from '@/hooks/useTheme'
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients' import {gradientUtils} from '@/styles/gradients'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => { const DealerIndex: React.FC = () => {
@@ -30,10 +26,10 @@ const DealerIndex: React.FC = () => {
} }
// 格式化金额 // 格式化金额
const formatMoney = (money?: string) => { // const formatMoney = (money?: string) => {
if (!money) return '0.00' // if (!money) return '0.00'
return parseFloat(money).toFixed(2) // return parseFloat(money).toFixed(2)
} // }
// 格式化时间 // 格式化时间
const formatTime = (time?: string) => { const formatTime = (time?: string) => {
@@ -108,7 +104,7 @@ const DealerIndex: React.FC = () => {
<View className="text-sm" style={{ <View className="text-sm" style={{
color: 'rgba(255, 255, 255, 0.8)' color: 'rgba(255, 255, 255, 0.8)'
}}> }}>
ID: {dealerUser.userId} | : {dealerUser.refereeId || '无'} {dealerUser.mobile}
</View> </View>
</View> </View>
<View className="text-right hidden"> <View className="text-right hidden">
@@ -125,80 +121,9 @@ const DealerIndex: React.FC = () => {
</View> </View>
)} )}
{/* 佣金统计卡片 */}
{dealerUser && (
<View className="mx-4 -mt-6 rounded-xl p-4 relative z-10" style={cardGradients.elevated}>
<View className="mb-4">
<Text className="font-semibold text-gray-800"></Text>
</View>
<View className="grid grid-cols-3 gap-3">
<View className="text-center p-3 rounded-lg flex flex-col" style={{
background: businessGradients.money.available
}}>
<Text className="text-lg font-bold mb-1 text-white">
{formatMoney(dealerUser.money)}
</Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{
background: businessGradients.money.frozen
}}>
<Text className="text-lg font-bold mb-1 text-white">
{formatMoney(dealerUser.freezeMoney)}
</Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View>
<View className="text-center p-3 rounded-lg flex flex-col" style={{
background: businessGradients.money.total
}}>
<Text className="text-lg font-bold mb-1 text-white">
{formatMoney(dealerUser.totalMoney)}
</Text>
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}></Text>
</View>
</View>
</View>
)}
{/* 团队统计 */}
{dealerUser && (
<View className="bg-white mx-4 mt-4 rounded-xl p-4 hidden">
<View className="flex items-center justify-between mb-4">
<Text className="font-semibold text-gray-800"></Text>
<View
className="text-gray-400 text-sm flex items-center"
onClick={() => navigateToPage('/dealer/team/index')}
>
<Text></Text>
<ArrowRight size="12"/>
</View>
</View>
<View className="grid grid-cols-3 gap-4">
<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 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 grid">
<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"> <View className="bg-white mx-4 mt-4 rounded-xl p-4">
<View className="font-semibold mb-4 text-gray-800"></View> <View className="font-semibold mb-4 text-gray-800"></View>
<ConfigProvider> <ConfigProvider>
<Grid <Grid
columns={4} columns={4}
@@ -209,7 +134,7 @@ const DealerIndex: React.FC = () => {
border: 'none' border: 'none'
} as React.CSSProperties} } as React.CSSProperties}
> >
<Grid.Item text="分销订单" onClick={() => navigateToPage('/dealer/orders/index')}> <Grid.Item text="成员管理" onClick={() => navigateToPage('/admin/users/index')}>
<View className="text-center"> <View className="text-center">
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2"> <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"/> <Shopping color="#3b82f6" size="20"/>
@@ -217,71 +142,8 @@ const DealerIndex: React.FC = () => {
</View> </View>
</Grid.Item> </Grid.Item>
<Grid.Item text={'提现申请'} 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>
</View>
</Grid.Item>
<Grid.Item text={'我的邀请'} 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>
</View>
</Grid.Item>
<Grid.Item text={'我的邀请码'} 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>
</View>
</Grid.Item>
</Grid> </Grid>
{/* 第二行功能 */}
{/*<Grid*/}
{/* columns={4}*/}
{/* className="no-border-grid mt-4"*/}
{/* style={{*/}
{/* '--nutui-grid-border-color': 'transparent',*/}
{/* '--nutui-grid-item-border-width': '0px',*/}
{/* border: 'none'*/}
{/* } as React.CSSProperties}*/}
{/*>*/}
{/* <Grid.Item text={'邀请统计'} onClick={() => navigateToPage('/dealer/invite-stats/index')}>*/}
{/* <View className="text-center">*/}
{/* <View className="w-12 h-12 bg-indigo-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
{/* <Presentation color="#6366f1" size="20"/>*/}
{/* </View>*/}
{/* </View>*/}
{/* </Grid.Item>*/}
{/* /!* 预留其他功能位置 *!/*/}
{/* <Grid.Item text={''}>*/}
{/* <View className="text-center">*/}
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
{/* </View>*/}
{/* </View>*/}
{/* </Grid.Item>*/}
{/* <Grid.Item text={''}>*/}
{/* <View className="text-center">*/}
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
{/* </View>*/}
{/* </View>*/}
{/* </Grid.Item>*/}
{/* <Grid.Item text={''}>*/}
{/* <View className="text-center">*/}
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
{/* </View>*/}
{/* </View>*/}
{/* </Grid.Item>*/}
{/*</Grid>*/}
</ConfigProvider> </ConfigProvider>
</View> </View>
</View> </View>

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '成员管理'
})

256
src/admin/users/index.tsx Normal file
View File

@@ -0,0 +1,256 @@
import {useRef, useState} from 'react'
import Taro, {useDidShow} from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {
Avatar,
Button,
Empty,
InfiniteLoading,
Loading,
PullToRefresh,
SearchBar,
Tag
} from '@nutui/nutui-react-taro'
import type {User} from '@/api/system/user/model'
import {pageUsers} from '@/api/system/user'
import {listUserRole, updateUserRole} from '@/api/system/userRole'
import {listRoles} from '@/api/system/role'
const PAGE_SIZE = 10
const AdminUsers = () => {
const [searchValue, setSearchValue] = useState('')
const [users, setUsers] = useState<User[]>([])
const [loading, setLoading] = useState(false)
const [hasMore, setHasMore] = useState(true)
const [page, setPage] = useState(1)
const [total, setTotal] = useState(0)
const roleIdMapRef = useRef<Record<string, number>>({})
const roleMapLoadedRef = useRef(false)
const getRoleIdByCode = async (roleCode: string) => {
if (!roleMapLoadedRef.current) {
const roles = await listRoles()
const nextMap: Record<string, number> = {}
roles?.forEach(role => {
if (role.roleCode && role.roleId) nextMap[role.roleCode] = role.roleId
})
roleIdMapRef.current = nextMap
roleMapLoadedRef.current = true
}
return roleIdMapRef.current[roleCode]
}
const reload = async (isRefresh = false, overrideKeywords?: string) => {
if (loading) return
if (isRefresh) {
setPage(1)
setUsers([])
setHasMore(true)
}
setLoading(true)
try {
const currentPage = isRefresh ? 1 : page
const res = await pageUsers({
page: currentPage,
limit: PAGE_SIZE,
keywords: overrideKeywords ?? searchValue
})
if (res?.list) {
const nextUsers = isRefresh ? res.list : [...users, ...res.list]
setUsers(nextUsers)
setTotal(res.count || 0)
setHasMore(res.list.length === PAGE_SIZE)
setPage(isRefresh ? 2 : currentPage + 1)
} else {
setUsers([])
setTotal(0)
setHasMore(false)
}
} catch (error) {
console.error('获取用户列表失败:', error)
Taro.showToast({title: '获取用户列表失败', icon: 'error'})
} finally {
setLoading(false)
}
}
const getUserRoleCodes = (target: User): string[] => {
const fromRoles = target.roles?.map(r => r.roleCode).filter(Boolean) as string[] | undefined
const fromSingle = target.roleCode ? [target.roleCode] : []
const merged = [...(fromRoles || []), ...fromSingle].filter(Boolean)
return Array.from(new Set(merged))
}
const getPrimaryRoleCode = (target: User): string | undefined => {
const codes = getUserRoleCodes(target)
if (codes.includes('superAdmin')) return 'superAdmin'
if (codes.includes('admin')) return 'admin'
if (codes.includes('dealer')) return 'dealer'
if (codes.includes('user')) return 'user'
return codes[0]
}
const renderRoleTag = (target: User) => {
const code = getPrimaryRoleCode(target)
if (code === 'superAdmin') return <Tag type="danger"></Tag>
if (code === 'admin') return <Tag type="danger"></Tag>
if (code === 'dealer') return <Tag type="primary"></Tag>
if (code === 'user') return <Tag type="success"></Tag>
return <Tag></Tag>
}
const toggleRole = async (target: User) => {
const current = getPrimaryRoleCode(target)
const nextRoleCode = current === 'dealer' ? 'user' : 'dealer'
const nextRoleName = nextRoleCode === 'user' ? '注册会员' : '业务员'
const confirmRes = await Taro.showModal({
title: '确认切换角色',
content: `确定将该用户切换为「${nextRoleName}」吗?`
})
if (!confirmRes.confirm) return
try {
const userId = target.userId
if (!userId) return
const nextRoleId = await getRoleIdByCode(nextRoleCode)
if (!nextRoleId) {
throw new Error(`未找到角色配置:${nextRoleCode}`)
}
const roles = await listUserRole({userId})
const candidate = roles?.find(r => r.roleCode === 'dealer' || r.roleCode === 'user')
if (candidate) {
await updateUserRole({
...candidate,
roleId: nextRoleId
})
} else {
await updateUserRole({
userId,
roleId: nextRoleId
})
}
Taro.showToast({title: '切换成功', icon: 'success'})
await reload(true)
} catch (error) {
console.error('切换角色失败:', error)
Taro.showToast({title: '切换失败', icon: 'error'})
}
}
const handleSearch = (value: string) => {
setSearchValue(value)
reload(true, value).then()
}
const loadMore = async () => {
if (!loading && hasMore) {
await reload(false)
}
}
useDidShow(() => {
const init = async () => {
try {
await reload(true)
} catch (error) {
console.error('初始化失败:', error)
Taro.showToast({title: '初始化失败', icon: 'error'})
}
}
init().then()
})
return (
<View className="bg-gray-50 min-h-screen">
<View className="py-2 px-3">
<SearchBar
placeholder="搜索昵称/手机号/UID"
value={searchValue}
onChange={setSearchValue}
onSearch={handleSearch}
/>
</View>
{total > 0 && (
<View className="px-4 py-2 text-sm text-gray-500">
{total}
</View>
)}
<PullToRefresh onRefresh={() => reload(true)} headHeight={60}>
<View className="px-4" style={{height: 'calc(100vh - 190px)', overflowY: 'auto'}} id="users-scroll">
{users.length === 0 && !loading ? (
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 260px)'}}>
<Empty description="暂无成员数据" style={{backgroundColor: 'transparent'}}/>
</View>
) : (
<InfiniteLoading
target="users-scroll"
hasMore={hasMore}
onLoadMore={loadMore}
loadingText={
<View className="flex justify-center items-center py-4">
<Loading/>
<View className="ml-2">...</View>
</View>
}
loadMoreText={
<View className="text-center py-4 text-gray-500">
{users.length === 0 ? '暂无数据' : '没有更多了'}
</View>
}
>
{users.map((item, index) => {
const displayName = item.alias || item.nickname || item.realName || item.username || `用户${item.userId || ''}`
const phone = item.phone || item.mobile || '-'
const primaryRole = getPrimaryRoleCode(item)
const toggleText = primaryRole === 'dealer' ? '设为注册会员' : '设为业务员'
return (
<View key={item.userId || index} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center">
<Avatar size="40" src={item.avatar || item.avatarUrl} className="mr-3"/>
<View className="flex-1">
<View className="flex items-center justify-between">
<Text className="font-semibold text-gray-800">{displayName}</Text>
{renderRoleTag(item)}
</View>
<View className="text-xs text-gray-500 mt-1">
UID{item.userId || '-'} · {phone}
</View>
</View>
</View>
<View className="flex justify-end gap-2 pt-3 mt-3 border-t border-gray-100">
<Button
size="small"
fill="outline"
onClick={() => toggleRole(item)}
>
{toggleText}
</Button>
</View>
</View>
)
})}
</InfiniteLoading>
)}
</View>
</PullToRefresh>
</View>
)
}
export default AdminUsers

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@ import {SERVER_API_URL} from "@/utils/server";
export async function pageRoles(params: RoleParam) { export async function pageRoles(params: RoleParam) {
const res = await request.get<ApiResult<PageResult<Role>>>( const res = await request.get<ApiResult<PageResult<Role>>>(
SERVER_API_URL + '/system/role/page', SERVER_API_URL + '/system/role/page',
{ params } params
); );
if (res.code === 0) { if (res.code === 0) {
return res.data; return res.data;
@@ -24,9 +24,7 @@ export async function pageRoles(params: RoleParam) {
export async function listRoles(params?: RoleParam) { export async function listRoles(params?: RoleParam) {
const res = await request.get<ApiResult<Role[]>>( const res = await request.get<ApiResult<Role[]>>(
SERVER_API_URL + '/system/role', SERVER_API_URL + '/system/role',
{
params params
}
); );
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
return res.data; return res.data;

View File

@@ -1,8 +1,8 @@
export default defineAppConfig({ export default defineAppConfig({
pages: [ pages: [
'pages/index/index', 'pages/index/index',
'pages/cart/cart', 'pages/message/message',
'pages/find/find', 'pages/toolbox/toolbox',
'pages/user/user' 'pages/user/user'
], ],
"subpackages": [ "subpackages": [
@@ -87,6 +87,7 @@ export default defineAppConfig({
"root": "admin", "root": "admin",
"pages": [ "pages": [
"index", "index",
"users/index",
"article/index", "article/index",
"userVerify/index" "userVerify/index"
] ]
@@ -94,42 +95,43 @@ export default defineAppConfig({
], ],
window: { window: {
backgroundTextStyle: 'dark', backgroundTextStyle: 'dark',
navigationBarBackgroundColor: '#fff', navigationBarBackgroundColor: '#1890FF',
navigationBarTitleText: 'WeChat', navigationBarTitleText: '工具箱',
navigationBarTextStyle: 'black' navigationBarTextStyle: 'white'
},
tabBar: {
custom: false,
color: "#999999",
selectedColor: "#1890FF",
backgroundColor: "#F8F8F8",
borderStyle: "black",
list: [
{
pagePath: "pages/index/index",
iconPath: "assets/tabbar/home.png",
selectedIconPath: "assets/tabbar/home-active.png",
text: "首页",
},
{
pagePath: "pages/message/message",
iconPath: "assets/tabbar/message.png",
selectedIconPath: "assets/tabbar/message-active.png",
text: "消息",
},
{
pagePath: "pages/toolbox/toolbox",
iconPath: "assets/tabbar/toolbox.png",
selectedIconPath: "assets/tabbar/toolbox-active.png",
text: "工具箱",
},
{
pagePath: "pages/user/user",
iconPath: "assets/tabbar/user.png",
selectedIconPath: "assets/tabbar/user-active.png",
text: "我的",
},
],
}, },
// tabBar: {
// custom: false,
// color: "#8a8a8a",
// selectedColor: "#0e932e",
// backgroundColor: "#ffffff",
// list: [
// {
// pagePath: "pages/index/index",
// iconPath: "assets/tabbar/index.png",
// selectedIconPath: "assets/tabbar/index-active.png",
// text: "首页",
// },
// {
// pagePath: "pages/find/find",
// iconPath: "assets/tabbar/find.png",
// selectedIconPath: "assets/tabbar/find-active.png",
// text: "发现",
// },
// {
// pagePath: "pages/cart/cart",
// iconPath: "assets/tabbar/cart.png",
// selectedIconPath: "assets/tabbar/cart-active.png",
// text: "购物车",
// },
// {
// pagePath: "pages/user/user",
// iconPath: "assets/tabbar/user.png",
// selectedIconPath: "assets/tabbar/user-active.png",
// text: "我的",
// },
// ],
// },
requiredPrivateInfos: [ requiredPrivateInfos: [
"getLocation", "getLocation",
"chooseLocation", "chooseLocation",

View File

@@ -74,13 +74,13 @@ function App(props: { children: any; }) {
trackInviteSource(inviteParams.source || 'unknown', parseInt(inviteParams.inviter || '0')) trackInviteSource(inviteParams.source || 'unknown', parseInt(inviteParams.inviter || '0'))
// 显示邀请提示 // 显示邀请提示
setTimeout(() => { // setTimeout(() => {
Taro.showToast({ // Taro.showToast({
title: `检测到邀请信息 ID:${inviteParams.inviter}`, // title: `检测到邀请信息 ID:${inviteParams.inviter}`,
icon: 'success', // icon: 'success',
duration: 3000 // duration: 3000
}) // })
}, 1000) // }, 1000)
} else { } else {
console.log('❌ 未检测到邀请参数') console.log('❌ 未检测到邀请参数')

View File

@@ -411,9 +411,9 @@ const AddUserAddress = () => {
> >
<View className={'bg-gray-100 h-3'}></View> <View className={'bg-gray-100 h-3'}></View>
<CellGroup style={{padding: '4px 0'}}> <CellGroup style={{padding: '4px 0'}}>
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required> {/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
<Input placeholder="邀请人ID"/> {/* <Input placeholder="邀请人ID"/>*/}
</Form.Item> {/*</Form.Item>*/}
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required> <Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
<View className="flex items-center justify-between"> <View className="flex items-center justify-between">
<Input <Input

View File

@@ -15,7 +15,7 @@ import {
extractDateForCalendar, formatDateForDisplay extractDateForCalendar, formatDateForDisplay
} from "@/utils/dateUtils"; } from "@/utils/dateUtils";
import {ShopDealerUser} from "@/api/shop/shopDealerUser/model"; import {ShopDealerUser} from "@/api/shop/shopDealerUser/model";
import {getShopDealerUser, pageShopDealerUser} from "@/api/shop/shopDealerUser"; import {getShopDealerUser} from "@/api/shop/shopDealerUser";
const AddShopDealerApply = () => { const AddShopDealerApply = () => {
const {params} = useRouter(); const {params} = useRouter();
@@ -161,103 +161,124 @@ const AddShopDealerApply = () => {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}; };
type DupKey = 'address' | 'buildingNo' | 'unitNo' | 'roomNo' | 'realName' | 'mobile';
const DUP_LABELS: Record<DupKey, string> = {
address: '小区名称',
buildingNo: '楼栋号',
unitNo: '单元号',
roomNo: '房号',
realName: '姓名',
mobile: '电话',
};
const normalizeText = (v: any) => (v ?? '').toString().trim(); const normalizeText = (v: any) => (v ?? '').toString().trim();
const getApplyDupFields = (apply: ShopDealerApply): Record<DupKey, string> => { const toHalfWidth = (input: string) =>
(input || '').replace(/[\uFF01-\uFF5E]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)).replace(/\u3000/g, ' ');
const parseChineseNumber = (s: string): number | null => {
const str = (s || '').trim();
if (!str) return null;
// 仅处理纯中文数字(含大小写)+ 单位
if (!/^[零〇一二三四五六七八九十百千万两兩俩壹贰叁肆伍陆柒捌玖拾佰仟萬]+$/.test(str)) return null;
const digitMap: Record<string, number> = {
: 0, : 0,
: 1, : 1,
: 2, : 2, : 2, : 2, : 2,
: 3, : 3,
: 4, : 4,
: 5, : 5,
: 6, : 6,
: 7, : 7,
: 8, : 8,
: 9, : 9,
};
const unitMap: Record<string, number> = {: 10, : 10, : 100, : 100, : 1000, : 1000, : 10000, : 10000};
let total = 0;
let section = 0;
let number = 0;
for (const ch of str) {
if (digitMap[ch] !== undefined) {
number = digitMap[ch];
continue;
}
const unit = unitMap[ch];
if (!unit) continue;
if (unit === 10000) {
section = (section + number) * unit;
total += section;
section = 0;
} else {
// “十/百/千”前省略“一”的情况:十=10、十二=12
const n = number === 0 ? 1 : number;
section += n * unit;
}
number = 0;
}
const result = total + section + number;
return Number.isFinite(result) ? result : null;
};
const normalizeCommunity = (community: string) => {
const s = toHalfWidth(normalizeText(community));
return s.replace(/\s+/g, '').toUpperCase();
};
const normalizeHouseNoPart = (raw: string, kind: 'building' | 'unit' | 'room') => {
let s = toHalfWidth(normalizeText(raw)).toUpperCase();
s = s.replace(/\s+/g, '');
// 去掉常见后缀/装饰词
if (kind === 'building') s = s.replace(/(号楼|栋|幢|楼)$/g, '');
if (kind === 'unit') s = s.replace(/(单元)$/g, '');
if (kind === 'room') s = s.replace(/(室|房|号)$/g, '');
// 只保留数字与字母,统一分隔符差异(如 12-01 / 12#01
s = s.replace(/[^0-9A-Z零一二三四五六七八九十百千万两兩俩壹贰叁肆伍陆柒捌玖拾佰仟萬]/g, '');
// 纯中文数字 => 阿拉伯数字(支持大小写)
const cn = parseChineseNumber(s);
if (cn !== null) return String(cn);
// 数字段去前导 0如 03A => 3A1201 不变)
s = s.replace(/\d+/g, (m) => String(parseInt(m, 10)));
return s;
};
const buildHouseKeyNormalized = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => {
const c = normalizeCommunity(community);
const b = normalizeHouseNoPart(buildingNo, 'building');
const u = normalizeHouseNoPart(unitNo || '', 'unit');
const r = normalizeHouseNoPart(roomNo, 'room');
return [c, b, u, r].join('|');
};
const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => {
const parsed = parseHouseKey(apply.dealerCode); const parsed = parseHouseKey(apply.dealerCode);
return { return buildHouseKeyNormalized(
address: normalizeText(parsed.community || apply.address), parsed.community || apply.address || '',
buildingNo: normalizeText(parsed.buildingNo), parsed.buildingNo || '',
unitNo: normalizeText(parsed.unitNo), parsed.unitNo || '',
roomNo: normalizeText(parsed.roomNo), parsed.roomNo || ''
realName: normalizeText(apply.realName), );
mobile: normalizeText(apply.mobile),
};
}; };
const getNewDupFields = (values: any): Record<DupKey, string> => ({ const findExistingApplyByHouse = async (params: {houseKeyNormalized: string; houseKeyRaw: string; communityKeyword: string}) => {
address: normalizeText(values.address), const tryByDealerCode = async (dealerCode: string) => {
buildingNo: normalizeText(values.buildingNo), const res = await pageShopDealerApply({dealerCode, type: 4});
unitNo: normalizeText(values.unitNo), return res?.list?.[0] as ShopDealerApply | undefined;
roomNo: normalizeText(values.roomNo),
realName: normalizeText(values.realName),
mobile: normalizeText(values.mobile),
});
const combinationsOf3 = <T,>(arr: T[]): [T, T, T][] => {
const res: [T, T, T][] = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
for (let k = j + 1; k < arr.length; k++) {
res.push([arr[i], arr[j], arr[k]]);
}
}
}
return res;
}; };
const findMatchedTriad = (a: Record<DupKey, string>, b: Record<DupKey, string>) => { const keys = Array.from(new Set([params.houseKeyNormalized, params.houseKeyRaw].filter(Boolean)));
const availableKeys = (Object.keys(a) as DupKey[]).filter((k) => a[k] !== ''); for (const k of keys) {
if (availableKeys.length < 3) return null; const hit = await tryByDealerCode(k);
const triads = combinationsOf3(availableKeys); if (hit) return hit;
for (const triad of triads) {
if (triad.every((k) => a[k] === b[k] && b[k] !== '')) return triad;
} }
return null;
};
const checkDuplicateBeforeSubmit = async (values: any, opts?: { skipDealerCode?: string }) => { // 兼容历史数据:用关键词拉取附近数据,再用“规范化后的 houseKey”对比
const inputFields = getNewDupFields(values); const keyword = normalizeText(params.communityKeyword);
const nonEmptyCount = Object.values(inputFields).filter((v) => v !== '').length; if (!keyword) return null;
if (nonEmptyCount < 3) return null;
const seen = new Set<number>();
const scanPages = async (params: any) => {
for (let page = 1; page <= DUP_CHECK_MAX_PAGES; page++) { for (let page = 1; page <= DUP_CHECK_MAX_PAGES; page++) {
const res = await pageShopDealerApply({...params, page, limit: DUP_CHECK_LIMIT}); const res = await pageShopDealerApply({type: 4, keywords: keyword, page, limit: DUP_CHECK_LIMIT});
const list = res?.list || []; const list = res?.list || [];
for (const item of list) { for (const item of list) {
if (opts?.skipDealerCode && item.dealerCode === opts.skipDealerCode) continue; if (getNormalizedHouseKeyFromApply(item) === params.houseKeyNormalized) return item;
if (item.applyId && seen.has(item.applyId)) continue;
if (item.applyId) seen.add(item.applyId);
const triad = findMatchedTriad(inputFields, getApplyDupFields(item));
if (triad) return {item, triad};
} }
if (list.length < DUP_CHECK_LIMIT) break; if (list.length < DUP_CHECK_LIMIT) break;
} }
return null;
};
// 优先按手机号(精确)查询,数据量更小
if (inputFields.mobile) {
const hit = await scanPages({type: 4, mobile: inputFields.mobile});
if (hit) return hit;
}
// 再按小区关键字查询,覆盖房号相关组合
if (inputFields.address) {
const hit = await scanPages({type: 4, keywords: inputFields.address});
if (hit) return hit;
}
// 最后按姓名关键字兜底(用于覆盖不包含“小区/电话”的组合)
if (inputFields.realName) {
const hit = await scanPages({type: 4, keywords: inputFields.realName});
if (hit) return hit;
}
return null; return null;
}; };
@@ -265,6 +286,8 @@ const AddShopDealerApply = () => {
const submitSucceed = async (values: any) => { const submitSucceed = async (values: any) => {
try { try {
const currentUserId = Number(Taro.getStorageSync('UserId')) || 0;
// 房号相关必填校验 // 房号相关必填校验
if (!values.address || values.address.trim() === '') { if (!values.address || values.address.trim() === '') {
Taro.showToast({title: '请选择/填写小区', icon: 'error'}); Taro.showToast({title: '请选择/填写小区', icon: 'error'});
@@ -302,28 +325,59 @@ const AddShopDealerApply = () => {
return; return;
} }
// 验证报备人是否存在 // 规范化报备人:留空=自己报备(当前登录用户)
if (values.userId > 0) { const rawUserId = normalizeText(values.userId);
const isExist = await pageShopDealerUser({userId: Number(values.userId)}); const submitUserId = rawUserId
if(isExist && isExist.count == 0){ ? Number(rawUserId)
Taro.showToast({ : (isEditMode ? (existingApply?.userId || currentUserId) : currentUserId);
title: '报备人不存在', if (!Number.isFinite(submitUserId) || submitUserId <= 0) {
icon: 'error' Taro.showToast({title: '请填写正确的报备人ID', icon: 'error'});
}); return;
}
// 报备人存在性校验 + 获取该报备人的推荐人(用于后端展示链路)
let reporterDealerUser: ShopDealerUser | undefined = undefined;
if (submitUserId === currentUserId) {
reporterDealerUser = referee;
} else {
try {
reporterDealerUser = await getShopDealerUser(submitUserId);
} catch {
Taro.showToast({title: '报备人不存在', icon: 'error'});
return; return;
} }
} }
const houseKey = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo); // 后端常用 0 表示“无推荐人”,避免传空值触发“推荐人不存在”
const submitRefereeId = (reporterDealerUser?.refereeId && reporterDealerUser.refereeId > 0)
? reporterDealerUser.refereeId
: undefined;
const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo);
const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.roomNo);
const houseKey = houseKeyNormalized || houseKeyRaw;
const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.roomNo); const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.roomNo);
// 检查房号是否已报备 // 新增报备:提交前检查房号是否已报备(按 小区+楼栋+单元+房号 判断,且做规范化)
const res = await pageShopDealerApply({dealerCode: houseKey, type: 4});
if (res && res.count > 0) {
const existingCustomer = res.list[0];
if (!isEditMode) { if (!isEditMode) {
const existingCustomer = await findExistingApplyByHouse({
houseKeyNormalized,
houseKeyRaw,
communityKeyword: values.address
});
if (existingCustomer) {
// 报备人不同:直接拦截(避免跨报备人“抢单/续报”)
const existingReporterId = Number(existingCustomer.userId);
if (Number.isFinite(existingReporterId) && existingReporterId > 0 && existingReporterId !== submitUserId) {
Taro.showToast({
title: '请改房号,该房号信息已报备',
icon: 'none',
duration: 2500
});
return false;
}
// 已签约/已取消:直接提示已报备 // 已签约/已取消:直接提示已报备
if (existingCustomer.applyStatus && existingCustomer.applyStatus !== 10) { if (existingCustomer.applyStatus && existingCustomer.applyStatus !== 10) {
Taro.showToast({ Taro.showToast({
@@ -375,21 +429,6 @@ const AddShopDealerApply = () => {
} }
} }
// 新增报备:提交前做“三要素”重复校验(小区/楼栋/单元/房号/姓名/电话 任一三要素重复提示已报备)
if (!isEditMode) {
const dup = await checkDuplicateBeforeSubmit(values, {skipDealerCode: houseKey});
if (dup) {
const triadLabels = dup.triad.map((k: DupKey) => DUP_LABELS[k]).join('、');
const existingDisplay = dup.item.dealerName || dup.item.address || '地址未知';
Taro.showToast({
title: `疑似重复报备:${triadLabels}一致(${existingDisplay}),已报备`,
icon: 'none',
duration: 3000
});
return false;
}
}
// 计算过期时间 // 计算过期时间
const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime(); const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime();
@@ -406,7 +445,10 @@ const AddShopDealerApply = () => {
// 客户姓名/手机号 // 客户姓名/手机号
realName: values.realName, realName: values.realName,
mobile: values.mobile, mobile: values.mobile,
refereeId: referee?.refereeId, // 报备人(留空时用当前登录用户)
// userId: submitUserId,
// 推荐人(报备人的上级;无则传 0
refereeId: submitRefereeId,
applyStatus: isEditMode ? 20 : 10, applyStatus: isEditMode ? 20 : 10,
auditTime: undefined, auditTime: undefined,
// 设置保护期过期时间15天后 // 设置保护期过期时间15天后
@@ -486,8 +528,7 @@ const AddShopDealerApply = () => {
unitNo: parsed.unitNo, unitNo: parsed.unitNo,
roomNo: parsed.roomNo, roomNo: parsed.roomNo,
realName: FormData.realName, realName: FormData.realName,
mobile: FormData.mobile, mobile: FormData.mobile
userId: FormData.userId
}); });
}, [FormData]); }, [FormData]);
@@ -561,13 +602,13 @@ const AddShopDealerApply = () => {
{/*</Form.Item>*/} {/*</Form.Item>*/}
</> </>
)} )}
<Form.Item name="userId" label="报备人(ID)" initialValue={FormData?.userId}> {/*<Form.Item name="userId" label="报备人(ID)" initialValue={FormData?.userId}>*/}
<Input {/* <Input*/}
placeholder="自己报备请留空" {/* placeholder="自己报备请留空"*/}
disabled={isEditMode} {/* disabled={isEditMode}*/}
type="number" {/* type="number"*/}
/> {/* />*/}
</Form.Item> {/*</Form.Item>*/}
</CellGroup> </CellGroup>
</Form> </Form>

View File

@@ -1,4 +1,4 @@
import {useState, useEffect, useCallback} from 'react' import {useState, useEffect, useCallback, useRef} from 'react'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro' import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro' import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro'
@@ -16,6 +16,7 @@ import FixedButton from "@/components/FixedButton";
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply"; import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply";
import {addShopDealerRecord} from "@/api/shop/shopDealerRecord"; import {addShopDealerRecord} from "@/api/shop/shopDealerRecord";
import {useUser} from "@/hooks/useUser";
// 扩展User类型添加客户状态和保护天数 // 扩展User类型添加客户状态和保护天数
interface CustomerUser extends UserType { interface CustomerUser extends UserType {
@@ -32,6 +33,25 @@ const CustomerIndex = () => {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true) const [hasMore, setHasMore] = useState(true)
// 非分销商不允许查看客户列表
const {user, hasRole, loading: userLoading} = useUser()
// 管理员允许查看全部;普通分销商仅查看自己
const isAdminUser = user?.isAdmin === true
const canView = hasRole('dealer') || isAdminUser
const roleCheckFinished = !userLoading
const noPermissionShownRef = useRef(false)
useEffect(() => {
if (!roleCheckFinished || canView) return
if (noPermissionShownRef.current) return
noPermissionShownRef.current = true
Taro.showToast({
title: '没有查看权限',
icon: 'none',
duration: 1500
})
}, [roleCheckFinished, canView])
// Tab配置 // Tab配置
const tabList = getStatusOptions(); const tabList = getStatusOptions();
@@ -178,12 +198,17 @@ const CustomerIndex = () => {
setLoading(true); setLoading(true);
try { try {
const currentPage = resetPage ? 1 : (targetPage || page); const currentPage = resetPage ? 1 : (targetPage || page);
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0;
// 构建API参数根据状态筛选 // 构建API参数根据状态筛选
const params: any = { const params: any = {
type: 4, type: 4,
page: currentPage page: currentPage
}; };
// 非管理员:只看自己添加的客户
if (!isAdminUser && currentUserId > 0) {
params.userId = currentUserId;
}
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
if (applyStatus !== undefined) { if (applyStatus !== undefined) {
params.applyStatus = applyStatus; params.applyStatus = applyStatus;
@@ -226,7 +251,7 @@ const CustomerIndex = () => {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [activeTab, page]); }, [activeTab, page, isAdminUser, user?.userId]);
const reloadMore = async () => { const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载 if (loading || !hasMore) return; // 防止重复加载
@@ -274,12 +299,19 @@ const CustomerIndex = () => {
// 获取所有状态的统计数量 // 获取所有状态的统计数量
const fetchStatusCounts = useCallback(async () => { const fetchStatusCounts = useCallback(async () => {
try { try {
const currentUserId = Number(Taro.getStorageSync('UserId')) || user?.userId || 0;
const baseParams: any = {type: 4};
// 非管理员:只统计自己添加的客户
if (!isAdminUser && currentUserId > 0) {
baseParams.userId = currentUserId;
}
// 并行获取各状态的数量 // 并行获取各状态的数量
const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([ const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([
pageShopDealerApply({type: 4}), // 全部 pageShopDealerApply({...baseParams}), // 全部
pageShopDealerApply({applyStatus: 10, type: 4}), // 跟进中 pageShopDealerApply({...baseParams, applyStatus: 10}), // 跟进中
pageShopDealerApply({applyStatus: 20, type: 4}), // 已签约 pageShopDealerApply({...baseParams, applyStatus: 20}), // 已签约
pageShopDealerApply({applyStatus: 30, type: 4}) // 已取消 pageShopDealerApply({...baseParams, applyStatus: 30}) // 已取消
]); ]);
setStatusCounts({ setStatusCounts({
@@ -291,7 +323,7 @@ const CustomerIndex = () => {
} catch (error) { } catch (error) {
console.error('获取状态统计失败:', error); console.error('获取状态统计失败:', error);
} }
}, []); }, [isAdminUser, user?.userId]);
const getStatusCounts = () => statusCounts; const getStatusCounts = () => statusCounts;
@@ -330,22 +362,24 @@ const CustomerIndex = () => {
}) })
} }
// 初始化数据 // 初始化统计数据
useEffect(() => { useEffect(() => {
fetchCustomerData(activeTab, true).then(); if (!roleCheckFinished || !canView) return;
fetchStatusCounts().then(); fetchStatusCounts().then();
}, []); }, [roleCheckFinished, canView]);
// 当activeTab变化时重新获取数据 // 当activeTab变化时重新获取数据
useEffect(() => { useEffect(() => {
if (!roleCheckFinished || !canView) return;
setList([]); // 清空列表 setList([]); // 清空列表
setPage(1); // 重置页码 setPage(1); // 重置页码
setHasMore(true); // 重置加载状态 setHasMore(true); // 重置加载状态
fetchCustomerData(activeTab, true); fetchCustomerData(activeTab, true);
}, [activeTab]); }, [activeTab, roleCheckFinished, canView]);
// 监听页面显示,当从其他页面返回时刷新数据 // 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => { useDidShow(() => {
if (!roleCheckFinished || !canView) return;
// 刷新当前tab的数据和统计信息 // 刷新当前tab的数据和统计信息
setList([]); setList([]);
setPage(1); setPage(1);
@@ -543,6 +577,30 @@ const CustomerIndex = () => {
); );
}; };
if (!roleCheckFinished) {
return (
<View className="bg-white flex items-center justify-center">
<Loading/>
<Text className="text-gray-500 ml-2">...</Text>
</View>
);
}
if (!canView) {
return (
<View className="bg-white flex flex-col items-center justify-center p-4">
<Empty description="没有查看权限"/>
<Button
size="small"
style={{marginTop: '12px'}}
onClick={() => Taro.navigateBack()}
>
</Button>
</View>
);
}
return ( return (
<View className="min-h-screen bg-gray-50"> <View className="min-h-screen bg-gray-50">
{/* 搜索栏 */} {/* 搜索栏 */}

View File

@@ -256,23 +256,11 @@ export const useUser = () => {
return user?.nickname || user?.realName || user?.username || '未登录'; return user?.nickname || user?.realName || user?.username || '未登录';
}; };
// 获取用户显示的角色(同步版本) // 角色名称:优先取用户 roles 数组的第一个角色名称
const getRoleName = () => { const getRoleName = () => {
if(hasRole('superAdmin')){ // Some APIs return `roles`, some return single `roleName`.
return '超级管理员'; return user?.roles?.[0]?.roleName || user?.roleName || '注册用户'
} }
if(hasRole('admin')){
return '管理员';
}
if(hasRole('staff')){
return '员工';
}
if(hasRole('vip')){
return 'VIP会员';
}
return '注册用户';
}
// 检查用户是否已实名认证 // 检查用户是否已实名认证
const isCertified = () => { const isCertified = () => {
return user?.certification === true; return user?.certification === true;

View File

@@ -1,20 +1,196 @@
page { page {
//background: url('https://oss.wsdns.cn/20250621/33ca4ca532e647bc918a59d01f5d88a9.jpg?x-oss-process=image/resize,m_fixed,w_2000/quality,Q_90') no-repeat top center; background-color: #F8F8F8;
//background-size: 100%; min-height: 100vh;
background: linear-gradient(to bottom, #00eda3, #ffffff);
} }
.buy-btn{ .container {
height: 70px; min-height: 100vh;
background: linear-gradient(to bottom, #1cd98a, #24ca94); padding-bottom: 50px; /* 为底部导航栏留出空间 */
border-radius: 100px; }
color: #ffffff;
/* 顶部导航栏 */
.navbar {
background-color: #1890FF;
height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
position: sticky;
top: 0;
z-index: 100;
}
.navbar-title {
color: #FFFFFF;
font-size: 20px;
font-weight: bold;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
}
.navbar-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.customer-service-icon {
width: 24px;
height: 24px;
}
/* 品牌标识卡片 */
.brand-card {
background-color: #FFFFFF;
border-radius: 12px;
margin: 20px 16px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.brand-logo {
width: 60px;
height: 60px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.logo-image {
width: 100%;
height: 100%;
}
.brand-text {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.brand-name {
color: #0A2E5A;
font-size: 20px;
font-weight: bold;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
line-height: 28px;
}
.brand-en {
color: #666666;
font-size: 12px;
font-family: 'Helvetica Neue', 'Roboto', sans-serif;
letter-spacing: 0.5px;
margin-top: 4px;
}
/* 功能入口网格 */
.menu-grid {
display: flex;
flex-wrap: wrap;
padding: 0 16px;
gap: 16px;
}
.menu-item {
width: calc(25% - 12px);
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
background-color: #FFFFFF;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
}
.menu-item-single {
width: calc(25% - 12px);
}
.menu-icon-wrapper {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.menu-icon {
width: 28px;
height: 28px;
}
.menu-name {
color: #333333;
font-size: 14px;
text-align: center;
line-height: 20px;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
}
/* 底部导航栏 */
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background-color: #F8F8F8;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
.cart-icon{ border-top: 1px solid #E8E8E8;
background: linear-gradient(to bottom, #bbe094, #4ee265); z-index: 1000;
border-radius: 100px 0 0 100px; }
height: 70px;
} .tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.tab-icon {
width: 24px;
height: 24px;
margin-bottom: 2px;
}
.tab-icon-active {
/* 选中状态的图标 */
}
.tab-text {
color: #999999;
font-size: 12px;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
}
.tab-text-active {
color: #1890FF;
font-weight: bold;
}
.tab-item.active {
position: relative;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 2px;
background-color: #1890FF;
} }

View File

@@ -1,134 +1,161 @@
import Header from './Header';
import BestSellers from './BestSellers';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import {useShareAppMessage, useShareTimeline} from "@tarojs/taro" import { View, Image, Text } from '@tarojs/components';
import {useEffect, useState} from "react"; import './index.scss';
import {getShopInfo} from "@/api/layout";
import {Sticky, NoticeBar} from '@nutui/nutui-react-taro' // 功能入口数据
import {View} from '@tarojs/components' const menuItems = [
import Menu from "./Menu"; {
import Banner from "./Banner"; id: 1,
import './index.scss' name: '综合运维工单',
import Grid from "@/pages/index/Grid"; icon: 'https://oss.wsdns.cn/20260311/icon-workorder.svg',
import PopUpAd from "@/pages/index/PopUpAd"; bgColor: '#1890FF',
import {configWebsiteField} from "@/api/cms/cmsWebsiteField"; },
import type {Config} from "@/api/cms/cmsWebsiteField/model"; {
id: 2,
name: '缺陷管理',
icon: 'https://oss.wsdns.cn/20260311/icon-defect.svg',
bgColor: '#FA8C16',
},
{
id: 3,
name: '应急报修',
icon: 'https://oss.wsdns.cn/20260311/icon-emergency.svg',
bgColor: '#F59E0B',
},
{
id: 4,
name: '自动派单配置',
icon: 'https://oss.wsdns.cn/20260311/icon-dispatch.svg',
bgColor: '#1890FF',
},
{
id: 5,
name: '人员轨迹信息',
icon: 'https://oss.wsdns.cn/20260311/icon-tracker.svg',
bgColor: '#1890FF',
},
{
id: 6,
name: '任务工单',
icon: 'https://oss.wsdns.cn/20260311/icon-task.svg',
bgColor: '#1890FF',
},
];
function Home() { function Home() {
// 吸顶状态 // 处理客服点击
const [stickyStatus, setStickyStatus] = useState<boolean>(false) const handleCustomerService = () => {
const [config, setConfig] = useState<Config>()
useShareTimeline(() => {
return {
title: '南南佐顿门窗 - 网宿软件',
path: `/pages/index/index`
};
});
useShareAppMessage(() => {
return {
title: '南南佐顿门窗 - 网宿软件',
path: `/pages/index/index`,
success: function () {
console.log('分享成功');
},
fail: function () {
console.log('分享失败');
}
};
});
const showAuthModal = () => {
Taro.showModal({
title: '授权提示',
content: '需要获取您的用户信息',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确认,打开授权设置页面
openSetting();
}
}
});
};
const openSetting = () => {
// Taro.openSetting调起客户端小程序设置界面返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
Taro.openSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户授权成功,可以获取用户信息
reload();
} else {
// 用户拒绝授权,提示授权失败
Taro.showToast({ Taro.showToast({
title: '授权失败', title: '联系客服',
icon: 'none' icon: 'none',
});
}
}
}); });
}; };
const onSticky = (item: IArguments) => { // 处理菜单点击
if(item){ const handleMenuClick = (item: typeof menuItems[0]) => {
setStickyStatus(!stickyStatus) Taro.showToast({
} title: `点击了${item.name}`,
} icon: 'none',
});
const reload = () => {
}; };
useEffect(() => { // 处理底部导航切换
// 获取站点信息 const handleTabChange = (tab: string) => {
getShopInfo().then(() => { if (tab === 'toolbox') {
// 当前页面
}) return;
// 获取配置信息
configWebsiteField({}).then(data => {
setConfig(data)
})
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
showAuthModal();
}
} }
Taro.switchTab({
url: `/pages/${tab}/index`,
}); });
// 获取用户信息 };
Taro.getUserInfo({
success: (res) => {
const avatar = res.userInfo.avatarUrl;
console.log(avatar, 'avatarUrl')
}
});
}, []);
return ( return (
<> <View className="container">
<Sticky threshold={0} onChange={() => onSticky(arguments)}> {/* 顶部导航栏 */}
<Header stickyStatus={stickyStatus}/> <View className="navbar">
</Sticky> <Text className="navbar-title"></Text>
<View className={'flex flex-col mt-1'}> <View className="navbar-icon" onClick={handleCustomerService}>
<Menu/> <Image
<Banner/> className="customer-service-icon"
<NoticeBar content={config?.NoticeBar || '主营直购电售电业务,以更优惠电价、更全面的服务,致力为工商企业创造更优越经营环境,帮助企业减负排压,深度赋能'} /> src="https://oss.wsdns.cn/20260311/icon-customer-service.svg"
<BestSellers/> mode="aspectFit"
<Grid /> />
</View> </View>
<PopUpAd /> </View>
</>
) {/* 品牌标识卡片 */}
<View className="brand-card">
<View className="brand-logo">
<Image
className="logo-image"
src="https://oss.wsdns.cn/20260311/logo-wangshen.png"
mode="aspectFit"
/>
</View>
<View className="brand-text">
<Text className="brand-name"></Text>
<Text className="brand-en">WANG SHEN HUAN BAO TECHNOLOGY</Text>
</View>
</View>
{/* 功能入口网格 */}
<View className="menu-grid">
{menuItems.map((item, index) => (
<View
key={item.id}
className={`menu-item ${index >= 4 ? 'menu-item-single' : ''}`}
onClick={() => handleMenuClick(item)}
>
<View className="menu-icon-wrapper" style={{ backgroundColor: item.bgColor }}>
<Image
className="menu-icon"
src={item.icon}
mode="aspectFit"
/>
</View>
<Text className="menu-name">{item.name}</Text>
</View>
))}
</View>
{/* 底部导航栏 */}
<View className="tab-bar">
<View className="tab-item" onClick={() => handleTabChange('index')}>
<Image
className="tab-icon"
src="https://oss.wsdns.cn/20260311/icon-home.svg"
mode="aspectFit"
/>
<Text className="tab-text"></Text>
</View>
<View className="tab-item" onClick={() => handleTabChange('message')}>
<Image
className="tab-icon"
src="https://oss.wsdns.cn/20260311/icon-message.svg"
mode="aspectFit"
/>
<Text className="tab-text"></Text>
</View>
<View className="tab-item active" onClick={() => handleTabChange('toolbox')}>
<Image
className="tab-icon tab-icon-active"
src="https://oss.wsdns.cn/20260311/icon-toolbox-active.svg"
mode="aspectFit"
/>
<Text className="tab-text tab-text-active"></Text>
</View>
<View className="tab-item" onClick={() => handleTabChange('user')}>
<Image
className="tab-icon"
src="https://oss.wsdns.cn/20260311/icon-user.svg"
mode="aspectFit"
/>
<Text className="tab-text"></Text>
</View>
</View>
</View>
);
} }
export default Home export default Home;

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '消息',
});

View File

@@ -0,0 +1,17 @@
.message-page {
min-height: 100vh;
background-color: #F8F8F8;
padding-bottom: 50px;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.empty-text {
color: #999999;
font-size: 16px;
}

View File

@@ -0,0 +1,15 @@
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import './message.scss';
function Message() {
return (
<View className="message-page">
<View className="empty-state">
<Text className="empty-text"></Text>
</View>
</View>
);
}
export default Message;

View File

@@ -0,0 +1,5 @@
export default definePageConfig({
navigationBarTitleText: '工具箱',
navigationBarBackgroundColor: '#1890FF',
navigationBarTextStyle: 'white'
});

View File

@@ -0,0 +1,101 @@
page {
background-color: #F8F8F8;
min-height: 100vh;
}
.toolbox-container {
min-height: 100vh;
padding-bottom: 20px;
}
/* 品牌标识卡片 */
.brand-card {
background-color: #FFFFFF;
border-radius: 12px;
margin: 16px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.brand-logo {
width: 60px;
height: 60px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-radius: 12px;
}
.brand-text {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.brand-name {
color: #0A2E5A;
font-size: 20px;
font-weight: bold;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
line-height: 28px;
}
.brand-en {
color: #666666;
font-size: 12px;
font-family: 'Helvetica Neue', 'Roboto', sans-serif;
letter-spacing: 0.5px;
margin-top: 4px;
}
/* 功能入口网格 */
.menu-grid {
display: flex;
flex-wrap: wrap;
padding: 0 16px;
gap: 16px;
}
.menu-item {
width: calc(25% - 12px);
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 8px;
background-color: #FFFFFF;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
transition: transform 0.2s, box-shadow 0.2s;
}
.menu-item:active {
transform: scale(0.95);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}
.menu-icon-wrapper {
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
}
.menu-name {
color: #333333;
font-size: 13px;
text-align: center;
line-height: 18px;
font-family: 'PingFang SC', 'HarmonyOS Sans', 'Source Han Sans', sans-serif;
word-break: break-word;
min-height: 36px;
display: flex;
align-items: center;
}

View File

@@ -0,0 +1,94 @@
import Taro from '@tarojs/taro';
import { View, Text, Image } from '@tarojs/components';
import './toolbox.scss';
// 功能入口数据 - 使用 icons8 的在线图标
const menuItems = [
{
id: 1,
name: '综合运维工单',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/document.png',
bgColor: '#1890FF',
},
{
id: 2,
name: '缺陷管理',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/puzzle.png',
bgColor: '#FA8C16',
},
{
id: 3,
name: '应急报修',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/lightning-bolt.png',
bgColor: '#F59E0B',
},
{
id: 4,
name: '自动派单配置',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/settings.png',
bgColor: '#1890FF',
},
{
id: 5,
name: '人员轨迹信息',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/map-marker.png',
bgColor: '#1890FF',
},
{
id: 6,
name: '任务工单',
icon: 'https://img.icons8.com/ios-filled/100/ffffff/clipboard.png',
bgColor: '#1890FF',
},
];
function Toolbox() {
// 处理菜单点击
const handleMenuClick = (item: typeof menuItems[0]) => {
Taro.showToast({
title: `点击了${item.name}`,
icon: 'none',
});
};
return (
<View className="toolbox-container">
{/* 品牌标识卡片 */}
<View className="brand-card">
<View className="brand-logo">
<Image
className="logo-image"
src="https://img.icons8.com/color/96/environmental-technology.png"
mode="aspectFit"
/>
</View>
<View className="brand-text">
<Text className="brand-name"></Text>
<Text className="brand-en">WANG SHEN HUAN BAO TECHNOLOGY</Text>
</View>
</View>
{/* 功能入口网格 */}
<View className="menu-grid">
{menuItems.map((item) => (
<View
key={item.id}
className="menu-item"
onClick={() => handleMenuClick(item)}
>
<View className="menu-icon-wrapper" style={{ backgroundColor: item.bgColor }}>
<Image
className="menu-icon"
src={item.icon}
mode="aspectFit"
/>
</View>
<Text className="menu-name">{item.name}</Text>
</View>
))}
</View>
</View>
);
}
export default Toolbox;

View File

@@ -1,6 +1,5 @@
import {Button} from '@nutui/nutui-react-taro' import {Avatar, Button, Tag} from '@nutui/nutui-react-taro'
import {Avatar, Tag} from '@nutui/nutui-react-taro' import {View} from '@tarojs/components'
import {View, Text} from '@tarojs/components'
import {Scan} from '@nutui/icons-react-taro'; import {Scan} from '@nutui/icons-react-taro';
import {getWxOpenId} from '@/api/layout'; import {getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
@@ -8,7 +7,6 @@ import {useEffect} from "react";
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {useUser} from "@/hooks/useUser"; import {useUser} from "@/hooks/useUser";
import {useUserData} from "@/hooks/useUserData";
function UserCard() { function UserCard() {
const { const {
@@ -20,7 +18,6 @@ function UserCard() {
getDisplayName, getDisplayName,
getRoleName getRoleName
} = useUser(); } = useUser();
const {data} = useUserData();
useEffect(() => { useEffect(() => {
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。 // Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
@@ -180,12 +177,8 @@ function UserCard() {
<View className={'user-info flex flex-col px-2'}> <View className={'user-info flex flex-col px-2'}>
<View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View> <View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View>
{isLoggedIn ? ( {isLoggedIn ? (
<View className={'grade text-xs py-1'}> <View className={'grade text-xs py-0'}>
<Tag type="success" round> <Tag type="success">{getRoleName()}</Tag>
<Text className={'p-1'}>
{getRoleName()}
</Text>
</Tag>
</View> </View>
) : ''} ) : ''}
</View> </View>
@@ -198,27 +191,6 @@ function UserCard() {
</View> </View>
</View> </View>
</View> </View>
{/*<View className={'flex justify-around mt-1'}>*/}
{/* <View className={'item flex justify-center flex-col items-center'}*/}
{/* onClick={() => navTo('/user/wallet/wallet', true)}>*/}
{/* <Text className={'text-sm text-gray-500'}>余额</Text>*/}
{/* <Text className={'text-xl'}>{data?.balance || '0.00'}</Text>*/}
{/* </View>*/}
{/* <View className={'item flex justify-center flex-col items-center'}>*/}
{/* <Text className={'text-sm text-gray-500'}>积分</Text>*/}
{/* <Text className={'text-xl'}>{data?.points || 0}</Text>*/}
{/* </View>*/}
{/* <View className={'item flex justify-center flex-col items-center'}*/}
{/* onClick={() => navTo('/user/coupon/index', true)}>*/}
{/* <Text className={'text-sm text-gray-500'}>优惠券</Text>*/}
{/* <Text className={'text-xl'}>{data?.coupons || 0}</Text>*/}
{/* </View>*/}
{/* <View className={'item flex justify-center flex-col items-center'}*/}
{/* onClick={() => navTo('/user/gift/index', true)}>*/}
{/* <Text className={'text-sm text-gray-500'}>礼品卡</Text>*/}
{/* <Text className={'text-xl'}>{data?.giftCards || 0}</Text>*/}
{/* </View>*/}
{/*</View>*/}
</View> </View>
</View> </View>
</View> </View>

View File

@@ -2,7 +2,7 @@ import Taro from '@tarojs/taro';
import {User} from "@/api/system/user/model"; import {User} from "@/api/system/user/model";
// 模版套餐ID - 请根据实际情况修改 // 模版套餐ID - 请根据实际情况修改
export const TEMPLATE_ID = '10582'; export const TEMPLATE_ID = '10589';
// 服务接口 - 请根据实际情况修改 // 服务接口 - 请根据实际情况修改
export const SERVER_API_URL = 'https://server.websoft.top/api'; export const SERVER_API_URL = 'https://server.websoft.top/api';
// export const SERVER_API_URL = 'http://127.0.0.1:8000/api'; // export const SERVER_API_URL = 'http://127.0.0.1:8000/api';