refactor(customer): 优化客户数据查询和表单字段校验

- 移除新增客户页面对手机号的必填和格式校验
- 修改手机号字段标签为“手机号/微信号”,取消必填和长度限制
- 新增判断当前用户是否为超级管理员逻辑
- 抽取并统一构建客户查询参数方法,根据权限动态设置筛选条件
- 优化客户列表数据获取逻辑,支持超级管理员查看全部客户
- 调整依赖项,更新使用了新构建的查询参数函数
- 增强状态统计接口参数构建,统一调用参数生成函数
- 优化副作用 Hook 依赖,保证数据加载时机正确
This commit is contained in:
2026-06-04 17:15:48 +08:00
parent 78b67269ba
commit b130d4ac4c
9 changed files with 159 additions and 50 deletions

View File

@@ -30,3 +30,45 @@
### 构建验证
- `taro build --type weapp` 构建成功,无编译错误
## 个人资料完善流程优化 (2026-06-04 17:07)
### 1. 头像检查逻辑简化(仅检查头像)
**文件**: `src/pages/index/Header.tsx`
- `reload()` 中移除昵称检查,仅检查 `hasAvatar`
- 移除监听 `nickname === '微信用户'``useEffect` 自动跳转逻辑
- 新增 `useDidShow` 钩子:从 profile 页返回时重新检查头像状态
### 2. Profile 页面移除昵称字段
**文件**: `src/user/profile/profile.tsx`
- 删除昵称 `Form.Item``getWxNickname` 函数
- 移除 `Input` 导入和 `InputEvent` 类型定义
- 保留头像上传、性别、备注等字段
### 3. 修复头像更新后不立即刷新
**根因**: `useUser` 使用 `useState`,每个组件实例独立持有 state。profile 页更新 `user`UserCard 组件无法感知变化。
**修复**: `src/pages/user/components/UserCard.tsx` 新增 `useDidShow`,页面显示时调用 `fetchUserInfo()` 重新拉取用户数据。
### 4. 修复登出时 Avatar/Nickname 存储未清除
**文件**: `src/hooks/useUser.ts`
- `logoutUser()` 补充清除 `Taro.removeStorageSync('Avatar')``Taro.removeStorageSync('Nickname')`,防止切换账号时数据残留。
## 后台管理按钮新增 PC 端引导页 (2026-06-04 17:10)
### 背景
用户中心页 UserCell.tsx 中"后台管理"按钮(仅管理员可见)原本跳转到首页占位,现改为引导用户到 PC 端后台。
### 变更
1. **新增页面 `src/admin/redirect/index.tsx`** — PC 端引导页
- 显示"请在电脑端打开后台管理"提示
- 展示管理后台地址 `https://nnlzdmc.websoft.top`
- "复制链接并在电脑浏览器打开"按钮(`Taro.setClipboardData`
- 底部提示使用 Chrome/Edge 浏览器
2. **修改 `src/pages/user/components/UserCell.tsx`** — 第 40 行
- `onClick``Taro.reLaunch({url: '/pages/index/index'})` 改为 `navTo('/admin/redirect/index', true)`
3. **路由注册**`app.config.ts` admin 分包已包含 `redirect/index`(已存在配置)
### 构建验证
- `taro build --type weapp` 成功dist 目录下 `admin/redirect/` 正常输出

View File

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

View File

@@ -0,0 +1,84 @@
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Button } from '@nutui/nutui-react-taro'
import { Link, ArrowRight } from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
const PC_ADMIN_URL = 'https://nnlzdmc.websoft.top'
const AdminRedirect: React.FC = () => {
const handleCopyUrl = () => {
Taro.setClipboardData({
data: PC_ADMIN_URL,
success: () => {
Taro.showToast({
title: '链接已复制',
icon: 'success',
duration: 2000
})
}
})
}
return (
<View className="min-h-screen bg-gray-100 flex flex-col items-center justify-center px-8">
{/* 图标区域 */}
<View
className="w-20 h-20 rounded-full flex items-center justify-center mb-8"
style={{
background: 'linear-gradient(135deg, #fef3c7, #fde68a)'
}}
>
<Link color="#f97316" size={40} />
</View>
{/* 标题 */}
<Text className="text-xl font-bold text-gray-800 mb-3">
</Text>
{/* 说明文字 */}
<Text className="text-15 text-gray-500 text-center mb-8 leading-relaxed">
PC
</Text>
{/* URL 显示卡片 */}
<View className="w-full bg-white rounded-xl p-5 mb-6 border border-gray-100">
<View className="flex items-center mb-3">
<Link color="#3b82f6" size={16} />
<Text className="text-sm text-gray-400 ml-2"></Text>
</View>
<Text
className="text-17 font-medium text-blue-600"
style={{ wordBreak: 'break-all' }}
>
{PC_ADMIN_URL}
</Text>
</View>
{/* 复制按钮 */}
<Button
type="primary"
block
size="large"
onClick={handleCopyUrl}
style={{
background: 'linear-gradient(135deg, #f97316, #ea580c)',
border: 'none',
borderRadius: '12px',
height: '48px',
fontSize: '16px'
}}
>
</Button>
{/* 底部提示 */}
<Text className="text-sm text-gray-400 mt-6 text-center">
使 ChromeEdge
</Text>
</View>
)
}
export default AdminRedirect

View File

@@ -98,7 +98,8 @@ export default defineAppConfig({
"index",
"users/index",
"article/index",
"userVerify/index"
"userVerify/index",
"redirect/index"
]
}
],

View File

@@ -146,6 +146,8 @@ export const useUser = () => {
Taro.removeStorageSync('UserId');
Taro.removeStorageSync('TenantId');
Taro.removeStorageSync('Phone');
Taro.removeStorageSync('Avatar');
Taro.removeStorageSync('Nickname');
Taro.removeStorageSync('userInfo');
} catch (error) {
console.error('清除用户数据失败:', error);

View File

@@ -1,5 +1,5 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import Taro, { useDidShow } from '@tarojs/taro';
import {Space} from '@nutui/nutui-react-taro'
import {NavBar} from '@nutui/nutui-react-taro'
import {getWxOpenId} from "@/api/layout";
@@ -38,14 +38,13 @@ const Header = (props: any) => {
},
})
// 检查用户是否已登录并且有头像和昵称
// 检查用户是否已登录并且有头像
if (isLoggedIn) {
const hasAvatar = user?.avatar || Taro.getStorageSync('Avatar');
const hasNickname = user?.nickname || Taro.getStorageSync('Nickname');
if (!hasAvatar || !hasNickname) {
if (!hasAvatar) {
Taro.showToast({
title: '您还没有上传头像和昵称',
title: '请先上传头像',
icon: 'none'
})
setTimeout(() => {
@@ -198,24 +197,23 @@ const Header = (props: any) => {
reload().then()
}, [])
// 监听用户信息变化,当用户信息更新后重新检查
useEffect(() => {
if (isLoggedIn && user) {
console.log('用户信息已更新:', user);
// 检查是否设置头像和昵称
if (user.nickname === '微信用户') {
// 页面显示时重新检查头像(从 profile 页返回时兜底)
useDidShow(() => {
if (isLoggedIn) {
const hasAvatar = user?.avatar || Taro.getStorageSync('Avatar');
if (!hasAvatar) {
Taro.showToast({
title: '请设置头像和昵称',
title: '请先上传头像',
icon: 'none'
})
setTimeout(() => {
Taro.navigateTo({
url: '/user/profile/profile'
});
}, 2000)
})
}, 3000)
}
}
}, [user, isLoggedIn])
})
return (
<>

View File

@@ -1,8 +1,8 @@
import {Avatar, Button, Tag} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components'
import {Scan} from '@nutui/icons-react-taro';
// import {Scan} from '@nutui/icons-react-taro';
import {getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro';
import Taro, { useDidShow } from '@tarojs/taro';
import {useEffect} from "react";
import navTo from "@/utils/common";
import {TenantId} from "@/config/app";
@@ -11,7 +11,6 @@ import {useUser} from "@/hooks/useUser";
function UserCard() {
const {
user,
isAdmin,
isLoggedIn,
loginUser,
fetchUserInfo,
@@ -36,6 +35,15 @@ function UserCard() {
});
}, []);
// 页面显示时重新拉取用户信息(确保从 profile 页更新头像后立即刷新)
useDidShow(() => {
if (isLoggedIn) {
fetchUserInfo().catch(err => {
console.error('refresh user info on show failed:', err);
});
}
});
const reload = async () => {
// 如果已登录,获取最新用户信息
@@ -186,7 +194,7 @@ function UserCard() {
</View>
</View>
<View className={'gap-2 flex items-center'}>
{isAdmin() && <Scan className={'user-card__scan'} size={24} onClick={() => navTo('/user/store/verification', true)} />}
{/*{isAdmin() && <Scan className={'user-card__scan'} size={24} onClick={() => navTo('/user/store/verification', true)} />}*/}
<View className={'user-card__profile mr-4 text-sm px-3 py-1'}
onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'}

View File

@@ -37,7 +37,7 @@ const UserCell = () => {
}
align="center"
extra={<ArrowRight color="#f97316" size={18}/>}
onClick={() => Taro.reLaunch({url: '/pages/index/index'})}
onClick={() => navTo('/admin/redirect/index', true)}
/>
</Cell.Group>
)}

View File

@@ -13,7 +13,6 @@ const {router} = getCurrentInstance()
import {
Form,
Button,
Input,
Radio,
} from '@nutui/nutui-react-taro'
import {DictData} from "@/api/system/dict-data/model";
@@ -28,11 +27,6 @@ interface ChooseAvatarEvent {
};
}
interface InputEvent {
detail: {
value: string;
};
}
function Profile() {
const formId = Number(router?.params.id)
const {user, updateUser, loading} = useUser()
@@ -143,15 +137,6 @@ function Profile() {
})
}
// 获取微信昵称
const getWxNickname = (nickname: string) => {
// 更新表单数据
setFormData({
...FormData,
nickname: nickname
});
}
// 等待 useUser 初始化完成后再加载数据
useEffect(() => {
if (!loading) {
@@ -211,20 +196,6 @@ function Profile() {
</div>
}
>
<Form.Item
label={'昵称'}
name="nickname"
initialValue={FormData.nickname}
rules={[{message: '请获取微信昵称'}]}
>
<Input
type="nickname"
className="info-content__input"
placeholder="请输入昵称"
value={FormData?.nickname}
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
/>
</Form.Item>
<Form.Item
label="性别"
name="sex"