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` 构建成功,无编译错误 - `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", "index",
"users/index", "users/index",
"article/index", "article/index",
"userVerify/index" "userVerify/index",
"redirect/index"
] ]
} }
], ],

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
import {Avatar, Button, Tag} from '@nutui/nutui-react-taro' import {Avatar, Button, Tag} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components' 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 {getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro'; import Taro, { useDidShow } from '@tarojs/taro';
import {useEffect} from "react"; import {useEffect} from "react";
import navTo from "@/utils/common"; import navTo from "@/utils/common";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
@@ -11,7 +11,6 @@ import {useUser} from "@/hooks/useUser";
function UserCard() { function UserCard() {
const { const {
user, user,
isAdmin,
isLoggedIn, isLoggedIn,
loginUser, loginUser,
fetchUserInfo, 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 () => { const reload = async () => {
// 如果已登录,获取最新用户信息 // 如果已登录,获取最新用户信息
@@ -186,7 +194,7 @@ function UserCard() {
</View> </View>
</View> </View>
<View className={'gap-2 flex items-center'}> <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'} <View className={'user-card__profile mr-4 text-sm px-3 py-1'}
onClick={() => navTo('/user/profile/profile', true)}> onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'} {'个人资料'}

View File

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

View File

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