refactor(customer): 优化客户数据查询和表单字段校验
- 移除新增客户页面对手机号的必填和格式校验 - 修改手机号字段标签为“手机号/微信号”,取消必填和长度限制 - 新增判断当前用户是否为超级管理员逻辑 - 抽取并统一构建客户查询参数方法,根据权限动态设置筛选条件 - 优化客户列表数据获取逻辑,支持超级管理员查看全部客户 - 调整依赖项,更新使用了新构建的查询参数函数 - 增强状态统计接口参数构建,统一调用参数生成函数 - 优化副作用 Hook 依赖,保证数据加载时机正确
This commit is contained in:
@@ -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/` 正常输出
|
||||||
|
|||||||
3
src/admin/redirect/index.config.ts
Normal file
3
src/admin/redirect/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '后台管理'
|
||||||
|
})
|
||||||
84
src/admin/redirect/index.tsx
Normal file
84
src/admin/redirect/index.tsx
Normal 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">
|
||||||
|
请使用 Chrome、Edge 等浏览器打开以获得最佳体验
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AdminRedirect
|
||||||
@@ -98,7 +98,8 @@ export default defineAppConfig({
|
|||||||
"index",
|
"index",
|
||||||
"users/index",
|
"users/index",
|
||||||
"article/index",
|
"article/index",
|
||||||
"userVerify/index"
|
"userVerify/index",
|
||||||
|
"redirect/index"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -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)}>
|
||||||
{'个人资料'}
|
{'个人资料'}
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user