From 98151345366693b6baf09f206e7cb2b797987fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Wed, 30 Jul 2025 15:19:15 +0800 Subject: [PATCH] =?UTF-8?q?FEAT=20=3D>=20=E6=9C=AC=E6=AC=A1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E4=B8=BA=EF=BC=9A=20=E5=AD=98?= =?UTF-8?q?=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/README.md | 33 +- nkebao/src/components/Upload/AvatarUpload.tsx | 181 +++++ nkebao/src/components/Upload/README.md | 75 +- .../src/components/Upload/index.module.scss | 102 +++ .../src/pages/mobile/mine/setting/About.tsx | 152 ++++ .../src/pages/mobile/mine/setting/Privacy.tsx | 135 ++++ .../src/pages/mobile/mine/setting/README.md | 188 +++++ .../mobile/mine/setting/SecuritySetting.tsx | 228 ++++++ .../pages/mobile/mine/setting/UserSetting.tsx | 180 +++++ .../mobile/mine/setting/index.module.scss | 729 ++++++++++++++++++ .../src/pages/mobile/mine/setting/index.tsx | 238 ++++++ .../mobile/mine/userSet/index.module.scss | 115 --- .../src/pages/mobile/mine/userSet/index.tsx | 113 --- nkebao/src/router/module/mine.tsx | 27 +- nkebao/src/store/module/settings.ts | 73 ++ 15 files changed, 2335 insertions(+), 234 deletions(-) create mode 100644 nkebao/src/components/Upload/AvatarUpload.tsx create mode 100644 nkebao/src/pages/mobile/mine/setting/About.tsx create mode 100644 nkebao/src/pages/mobile/mine/setting/Privacy.tsx create mode 100644 nkebao/src/pages/mobile/mine/setting/README.md create mode 100644 nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx create mode 100644 nkebao/src/pages/mobile/mine/setting/UserSetting.tsx create mode 100644 nkebao/src/pages/mobile/mine/setting/index.module.scss create mode 100644 nkebao/src/pages/mobile/mine/setting/index.tsx delete mode 100644 nkebao/src/pages/mobile/mine/userSet/index.module.scss delete mode 100644 nkebao/src/pages/mobile/mine/userSet/index.tsx create mode 100644 nkebao/src/store/module/settings.ts diff --git a/Cunkebao/README.md b/Cunkebao/README.md index be60268f..7167bc10 100644 --- a/Cunkebao/README.md +++ b/Cunkebao/README.md @@ -2,23 +2,26 @@ ## 📋 项目简介 -内客宝是一个专业的微信获客和流量管理平台,基于 React 技术栈构建。平台提供智能化的客户获取、管理和运营解决方案,集成了多种自动化工具,帮助企业高效管理微信营销活动。 +内客宝是一个专业的微信获客和流量管理平台,基于 React 技术栈构建。平台提供智能化的客户获取、管理和运营解决方案,集成了多种自动化工具,帮助企业高效管理存客宝活动。 ## 🚀 技术栈详解 ### 核心框架 + - **React 18.2.0** - 现代化的用户界面库 - **TypeScript 4.9.5** - 类型安全的 JavaScript 超集 - **Create React App (CRA) 5.0.1** - React 应用脚手架 - **React Router DOM 6.20.0** - 客户端路由管理 ### 构建工具 + - **CRACO 7.1.0** - Create React App Configuration Override - 支持自定义 webpack 配置 - 路径别名配置 - 构建优化 ### UI 组件库 + - **Radix UI** - 无样式的可访问组件库 - 完整的组件生态系统(30+ 组件) - 优秀的无障碍访问支持 @@ -29,32 +32,38 @@ - 原子化 CSS 类 ### 图标和样式 + - **Lucide React 0.454.0** - 精美的图标库 - **Tailwind CSS Animate** - CSS 动画库 - **Class Variance Authority** - 组件变体管理 - **Tailwind Merge** - Tailwind 类名合并工具 ### 状态管理和表单 + - **React Hook Form 7.54.1** - 高性能表单库 - **Zod 3.24.1** - TypeScript 优先的模式验证 - **@hookform/resolvers 3.9.1** - 表单验证解析器 ### 数据可视化 + - **Recharts** - 基于 React 的图表库 - **Chart.js 4.5.0** - 灵活的图表库 - **@ant-design/plots** - Ant Design 图表组件 ### HTTP 请求和数据处理 + - **Axios 1.6.0** - HTTP 客户端 - **Crypto-js 4.2.0** - 加密库 - **Date-fns** - 日期处理库 - **XLSX 0.18.5** - Excel 文件处理 ### 通知和反馈 + - **React Hot Toast 2.5.2** - 轻量级通知库 - **Sonner 1.7.4** - 现代化 Toast 组件 ### 高级组件 + - **@tanstack/react-table** - 功能强大的表格组件 - **Embla Carousel React 8.5.1** - 轮播组件 - **React Resizable Panels 2.1.7** - 可调整大小的面板 @@ -63,6 +72,7 @@ - **React Day Picker** - 日期选择器 ### 开发工具 + - **PostCSS 8** - CSS 后处理器 - **Autoprefixer 10.4.20** - CSS 前缀自动添加 - **ESLint** - 代码质量检查 @@ -107,6 +117,7 @@ nkebao/ ## 🎯 核心功能模块 ### 工作台 (Workspace) + - **自动点赞** - 智能点赞管理和配置 - **自动建群** - 群组自动化创建和管理 - **群消息推送** - 群组消息批量发送 @@ -115,21 +126,25 @@ nkebao/ - **流量分发** - 流量分配和策略管理 ### 设备管理 (Devices) + - 设备状态监控和配置 - 设备性能分析 - 设备权限管理 ### 场景管理 (Scenarios) + - 营销场景配置 - 自动化流程设计 - 场景效果分析 ### 内容管理 (Content) + - 内容创建与编辑 - 内容模板管理 - 内容发布调度 ### 其他模块 + - 用户管理 (Users) - 订单管理 (Orders) - 流量池管理 (Traffic Pool) @@ -138,10 +153,12 @@ nkebao/ ## 🛠️ 开发指南 ### 环境要求 -- **Node.js** 16+ + +- **Node.js** 16+ - **npm** 或 **yarn** ### 安装依赖 + ```bash # 使用 npm npm install @@ -151,6 +168,7 @@ yarn install ``` ### 开发环境启动 + ```bash # 使用 npm npm start @@ -160,6 +178,7 @@ yarn start ``` ### 构建生产版本 + ```bash # 使用 npm npm run build @@ -169,6 +188,7 @@ yarn build ``` ### 运行测试 + ```bash # 使用 npm npm test @@ -180,7 +200,9 @@ yarn test ## 🔧 配置说明 ### 路径别名配置 + 项目使用 CRACO 配置了路径别名: + ```javascript '@': path.resolve(__dirname, 'src'), '@/components': path.resolve(__dirname, 'src/components'), @@ -193,11 +215,13 @@ yarn test ``` ### Tailwind CSS 配置 + - 自定义字体大小和间距 - 响应式断点配置 - 主题颜色系统 ### TypeScript 配置 + - 严格模式启用 - 路径映射配置 - JSX 支持 @@ -205,6 +229,7 @@ yarn test ## 📱 响应式设计 项目采用移动优先的响应式设计: + - 支持桌面端、平板端、移动端 - 自适应布局组件 - 触摸友好的交互设计 @@ -212,11 +237,13 @@ yarn test ## 🎨 UI 设计系统 ### 设计原则 + - 简洁现代的设计风格 - 一致的用户体验 - 无障碍访问支持 ### 组件库特点 + - 基于 Radix UI 的高质量组件 - 完整的表单组件系统 - 数据展示组件 @@ -263,4 +290,4 @@ yarn test **项目名称**: 内客宝 (nkebao2) **版本**: 0.1.0 **技术栈**: React + TypeScript + CRA + Tailwind CSS -**最后更新**: 2024年12月 \ No newline at end of file +**最后更新**: 2024 年 12 月 diff --git a/nkebao/src/components/Upload/AvatarUpload.tsx b/nkebao/src/components/Upload/AvatarUpload.tsx new file mode 100644 index 00000000..42409314 --- /dev/null +++ b/nkebao/src/components/Upload/AvatarUpload.tsx @@ -0,0 +1,181 @@ +import React, { useState, useEffect } from "react"; +import { Toast, Dialog } from "antd-mobile"; +import { UserOutlined, CameraOutlined } from "@ant-design/icons"; +import style from "./index.module.scss"; + +interface AvatarUploadProps { + value?: string; + onChange?: (url: string) => void; + disabled?: boolean; + className?: string; + size?: number; // 头像尺寸 +} + +const AvatarUpload: React.FC = ({ + value = "", + onChange, + disabled = false, + className, + size = 100, +}) => { + const [uploading, setUploading] = useState(false); + const [avatarUrl, setAvatarUrl] = useState(value); + + useEffect(() => { + setAvatarUrl(value); + }, [value]); + + // 文件验证 + const beforeUpload = (file: File) => { + // 检查文件类型 + const isValidType = file.type.startsWith("image/"); + if (!isValidType) { + Toast.show("只能上传图片文件!"); + return null; + } + + // 检查文件大小 (5MB) + const isLt5M = file.size / 1024 / 1024 < 5; + if (!isLt5M) { + Toast.show("图片大小不能超过5MB!"); + return null; + } + + return file; + }; + + // 上传函数 + const upload = async (file: File): Promise<{ url: string }> => { + const formData = new FormData(); + formData.append("file", file); + + try { + const response = await fetch( + `${import.meta.env.VITE_API_BASE_URL}/v1/attachment/upload`, + { + method: "POST", + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: formData, + }, + ); + + if (!response.ok) { + throw new Error("上传失败"); + } + + const result = await response.json(); + + if (result.code === 200) { + Toast.show("头像上传成功"); + return { url: result.data.url || result.data }; + } else { + throw new Error(result.msg || "上传失败"); + } + } catch (error) { + Toast.show("头像上传失败,请重试"); + throw error; + } + }; + + // 处理头像上传 + const handleAvatarChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file || disabled || uploading) return; + + const validatedFile = beforeUpload(file); + if (!validatedFile) return; + + setUploading(true); + try { + const result = await upload(validatedFile); + setAvatarUrl(result.url); + onChange?.(result.url); + } catch (error) { + console.error("头像上传失败:", error); + } finally { + setUploading(false); + } + }; + + // 删除头像 + const handleDelete = () => { + return Dialog.confirm({ + content: "确定要删除头像吗?", + onConfirm: () => { + setAvatarUrl(""); + onChange?.(""); + Toast.show("头像已删除"); + }, + }); + }; + + return ( +
+
+ {avatarUrl ? ( + 头像 + ) : ( +
+ +
+ )} + + {/* 上传覆盖层 */} +
+ !disabled && !uploading && fileInputRef.current?.click() + } + > + {uploading ? ( +
上传中...
+ ) : ( + + )} +
+ + {/* 删除按钮 */} + {avatarUrl && !disabled && ( +
+ × +
+ )} +
+ + {/* 隐藏的文件输入 */} + + + {/* 提示文字 */} +
+ {uploading + ? "正在上传头像..." + : "点击头像可更换,支持JPG、PNG格式,大小不超过5MB"} +
+
+ ); +}; + +// 创建 ref +const fileInputRef = React.createRef(); + +export default AvatarUpload; diff --git a/nkebao/src/components/Upload/README.md b/nkebao/src/components/Upload/README.md index 05c1a327..3005bf29 100644 --- a/nkebao/src/components/Upload/README.md +++ b/nkebao/src/components/Upload/README.md @@ -12,6 +12,21 @@ - ✅ 数量限制 - ✅ 编辑和新增状态支持 - ✅ 响应式设计 +- ✅ 头像上传专用组件 + +## 组件列表 + +### 1. UploadComponent (图片上传) + +通用的图片上传组件,支持多张图片上传。 + +### 2. AvatarUpload (头像上传) + +专门的头像上传组件,支持圆形头像显示、删除功能。 + +### 3. VideoUpload (视频上传) + +视频上传组件,支持视频文件上传和预览。 ## 使用方法 @@ -35,6 +50,26 @@ const MyComponent = () => { }; ``` +### 头像上传 + +```tsx +import React, { useState } from "react"; +import AvatarUpload from "@/components/Upload/AvatarUpload"; + +const AvatarComponent = () => { + const [avatar, setAvatar] = useState(""); + + return ( + + ); +}; +``` + ### 编辑模式 ```tsx @@ -63,7 +98,7 @@ const EditComponent = () => { ## API -### Props +### UploadComponent Props | 参数 | 说明 | 类型 | 默认值 | | --------- | -------------- | -------------------------- | ----------- | @@ -74,11 +109,30 @@ const EditComponent = () => { | disabled | 是否禁用 | `boolean` | `false` | | className | 自定义类名 | `string` | - | +### AvatarUpload Props + +| 参数 | 说明 | 类型 | 默认值 | +| --------- | ------------ | ----------------------- | ------- | +| value | 头像URL | `string` | `""` | +| onChange | 头像变化回调 | `(url: string) => void` | - | +| disabled | 是否禁用 | `boolean` | `false` | +| className | 自定义类名 | `string` | - | +| size | 头像尺寸 | `number` | `100` | + +### VideoUpload Props + +| 参数 | 说明 | 类型 | 默认值 | +| --------- | ------------ | ----------------------- | ------- | +| value | 视频URL | `string` | `""` | +| onChange | 视频变化回调 | `(url: string) => void` | - | +| disabled | 是否禁用 | `boolean` | `false` | +| className | 自定义类名 | `string` | - | + ### 事件 | 事件名 | 说明 | 回调参数 | | -------- | ------------------ | -------------------------- | -| onChange | 图片列表变化时触发 | `(urls: string[]) => void` | +| onChange | 文件列表变化时触发 | `(urls: string[]) => void` | ## 注意事项 @@ -88,6 +142,7 @@ const EditComponent = () => { 4. **认证**: 自动携带 token 进行认证 5. **预览**: 点击图片可预览 6. **删除**: 删除图片会有确认提示 +7. **头像组件**: 支持圆形显示、删除按钮、上传覆盖层 ## 样式定制 @@ -102,6 +157,13 @@ const EditComponent = () => { } } } + +.avatarUploadContainer { + // 头像上传组件样式 + .avatarWrapper { + // 头像容器样式 + } +} ``` ## 错误处理 @@ -110,3 +172,12 @@ const EditComponent = () => { - 文件大小超限时会显示错误提示 - 上传失败时会显示错误提示 - 网络错误时会显示错误提示 + +## 头像上传特性 + +- **圆形显示**: 头像以圆形方式显示 +- **占位符**: 无头像时显示用户图标 +- **上传覆盖**: 鼠标悬停显示上传图标 +- **删除功能**: 右上角删除按钮 +- **加载状态**: 上传时显示加载提示 +- **尺寸可调**: 支持自定义头像尺寸 diff --git a/nkebao/src/components/Upload/index.module.scss b/nkebao/src/components/Upload/index.module.scss index beab0535..286e3e7e 100644 --- a/nkebao/src/components/Upload/index.module.scss +++ b/nkebao/src/components/Upload/index.module.scss @@ -106,3 +106,105 @@ } } } + +// 头像上传组件样式 +.avatarUploadContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + + .avatarWrapper { + position: relative; + border-radius: 50%; + overflow: hidden; + background: #f0f0f0; + border: 2px solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + border-color: var(--primary-color); + box-shadow: 0 4px 12px rgba(24, 142, 238, 0.3); + } + + .avatarImage { + width: 100%; + height: 100%; + object-fit: cover; + } + + .avatarPlaceholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + font-size: 40px; + } + + .avatarUploadOverlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + opacity: 0; + transition: opacity 0.3s ease; + + &:hover { + opacity: 1; + } + + .uploadLoading { + font-size: 12px; + text-align: center; + line-height: 1.4; + } + } + + .avatarDeleteBtn { + position: absolute; + top: -8px; + right: -8px; + width: 24px; + height: 24px; + background: #ff4d4f; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + z-index: 10; + + &:hover { + background: #ff7875; + transform: scale(1.1); + } + } + + &:hover .avatarUploadOverlay { + opacity: 1; + } + } + + .avatarTip { + font-size: 12px; + color: #999; + text-align: center; + line-height: 1.4; + max-width: 200px; + } +} diff --git a/nkebao/src/pages/mobile/mine/setting/About.tsx b/nkebao/src/pages/mobile/mine/setting/About.tsx new file mode 100644 index 00000000..e1f22402 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/About.tsx @@ -0,0 +1,152 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { NavBar, Card } from "antd-mobile"; +import { + InfoCircleOutlined, + MailOutlined, + PhoneOutlined, + GlobalOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import style from "./index.module.scss"; +import NavCommon from "@/components/NavCommon"; +const About: React.FC = () => { + const navigate = useNavigate(); + + // 应用信息 + const appInfo = { + name: "存客宝管理系统", + version: "1.0.0", + buildNumber: "20241201", + description: "专业的存客宝管理平台,提供设备管理、自动营销、数据分析等功能", + }; + + // 功能特性 + const features = [ + { + title: "设备管理", + description: "统一管理微信设备和账号,实时监控设备状态", + }, + { + title: "自动营销", + description: "智能点赞、群发推送、朋友圈同步等自动化营销功能", + }, + { + title: "流量池管理", + description: "高效管理用户流量池,精准分组和标签管理", + }, + { + title: "内容库", + description: "丰富的营销内容库,支持多种媒体格式", + }, + { + title: "数据分析", + description: "详细的数据统计和分析,助力营销决策", + }, + ]; + + // 联系信息 + const contactInfo = [ + { + id: "email", + title: "邮箱支持", + value: "support@example.com", + icon: , + action: () => { + // 复制邮箱到剪贴板 + navigator.clipboard.writeText("support@example.com"); + }, + }, + { + id: "phone", + title: "客服热线", + value: "400-123-4567", + icon: , + action: () => { + // 拨打电话 + window.location.href = "tel:400-123-4567"; + }, + }, + { + id: "website", + title: "官方网站", + value: "www.example.com", + icon: , + action: () => { + // 打开网站 + window.open("https://www.example.com", "_blank"); + }, + }, + ]; + + return ( + }> +
+ {/* 应用信息卡片 */} + +
+
+
+ logo +
+
+
+
{appInfo.name}
+
版本 {appInfo.version}
+
+ Build {appInfo.buildNumber} +
+
+
+
{appInfo.description}
+
+ + {/* 功能特性 */} + +
功能特性
+
+ {features.map((feature, index) => ( +
+
+
{index + 1}
+
+
+
{feature.title}
+
+ {feature.description} +
+
+
+ ))} +
+
+ + {/* 联系信息 */} + {/* +
联系我们
+ + {contactInfo.map(item => ( + } + onClick={item.action} + arrow + /> + ))} + +
*/} + + {/* 版权信息 */} +
+
© 2024 存客宝管理系统
+
保留所有权利
+
+
+
+ ); +}; + +export default About; diff --git a/nkebao/src/pages/mobile/mine/setting/Privacy.tsx b/nkebao/src/pages/mobile/mine/setting/Privacy.tsx new file mode 100644 index 00000000..ab3f0737 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/Privacy.tsx @@ -0,0 +1,135 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import { NavBar, Card } from "antd-mobile"; +import Layout from "@/components/Layout/Layout"; +import style from "./index.module.scss"; + +const Privacy: React.FC = () => { + const navigate = useNavigate(); + + return ( + navigate(-1)} style={{ background: "#fff" }}> + + 用户隐私协议 + + + } + > +
+ +
+

用户隐私协议

+

更新时间:2024年12月1日

+ +
+

1. 信息收集

+

我们收集的信息包括:

+
    +
  • 账户信息:用户名、手机号、邮箱等注册信息
  • +
  • 设备信息:设备型号、操作系统版本、设备标识符
  • +
  • 使用数据:应用使用情况、功能访问记录
  • +
  • 微信相关:微信账号信息、好友数据(经您授权)
  • +
+
+ +
+

2. 信息使用

+

我们使用收集的信息用于:

+
    +
  • 提供和改进服务功能
  • +
  • 个性化用户体验
  • +
  • 安全防护和风险控制
  • +
  • 客户支持和问题解决
  • +
  • 合规性要求和法律义务
  • +
+
+ +
+

3. 信息共享

+

我们不会向第三方出售、交易或转让您的个人信息,除非:

+
    +
  • 获得您的明确同意
  • +
  • 法律法规要求
  • +
  • 保护用户和公众的安全
  • +
  • 与授权合作伙伴共享必要信息
  • +
+
+ +
+

4. 数据安全

+

我们采取多种安全措施保护您的信息:

+
    +
  • 数据加密传输和存储
  • +
  • 访问控制和身份验证
  • +
  • 定期安全审计和更新
  • +
  • 员工保密培训
  • +
+
+ +
+

5. 您的权利

+

您享有以下权利:

+
    +
  • 访问和查看您的个人信息
  • +
  • 更正或更新不准确的信息
  • +
  • 删除您的账户和相关数据
  • +
  • 撤回同意和限制处理
  • +
  • 数据可携带性
  • +
+
+ +
+

6. 数据保留

+

我们仅在必要期间保留您的信息:

+
    +
  • 账户活跃期间持续保留
  • +
  • 法律法规要求的保留期
  • +
  • 业务运营必要的保留期
  • +
  • 您主动删除后及时清除
  • +
+
+ +
+

7. 儿童隐私

+

+ 我们的服务不面向13岁以下儿童。如果发现收集了儿童信息,我们将立即删除。 +

+
+ +
+

8. 国际传输

+

+ 您的信息可能在中国境内或境外处理。我们将确保适当的保护措施。 +

+
+ +
+

9. 协议更新

+

+ 我们可能会更新本隐私协议。重大变更将通过应用内通知或邮件告知您。 +

+
+ +
+

10. 联系我们

+

如果您对本隐私协议有任何疑问,请联系我们:

+
    +
  • 邮箱:privacy@example.com
  • +
  • 电话:400-123-4567
  • +
  • 地址:北京市朝阳区xxx大厦
  • +
+
+ +
+

感谢您使用存客宝管理系统!

+
+
+
+
+
+ ); +}; + +export default Privacy; diff --git a/nkebao/src/pages/mobile/mine/setting/README.md b/nkebao/src/pages/mobile/mine/setting/README.md new file mode 100644 index 00000000..c8a454b6 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/README.md @@ -0,0 +1,188 @@ +# 设置功能说明 + +## 概述 + +设置功能为存客宝管理系统提供了完整的用户配置管理,包括账户设置、通知设置、应用设置等多个模块。 + +## 功能模块 + +### 1. 主设置页面 (`index.tsx`) + +**功能特性:** + +- 用户信息展示 +- 分组设置项管理 +- 设置状态持久化 + +**主要组件:** + +- 用户信息卡片:显示头像、昵称、账号、角色 +- 设置分组:账户设置、通知设置、应用设置、其他 +- 版本信息:显示应用版本和版权信息 + +### 2. 安全设置页面 (`SecuritySetting.tsx`) + +**功能特性:** + +- 密码修改 +- 手机号绑定 +- 登录设备管理 +- 安全建议 + +**主要功能:** + +- 修改密码:支持旧密码验证和新密码确认 +- 绑定手机号:提高账号安全性 +- 设备管理:查看和管理已登录设备 +- 安全提醒:提供账号安全建议 + +### 3. 关于页面 (`About.tsx`) + +**功能特性:** + +- 应用信息展示 +- 功能特性介绍 +- 联系方式 +- 法律信息 + +**主要内容:** + +- 应用版本信息 +- 功能介绍:设备管理、自动营销、流量池管理等 +- 联系方式:邮箱、电话、官网 +- 法律文档:隐私政策、用户协议、开源许可 + +## 设置管理 + +### 设置Store (`settings.ts`) + +**功能特性:** + +- 全局设置状态管理 +- 设置持久化存储 +- 设置工具函数 + +**支持的设置项:** + +#### 通知设置 + +- `pushNotification`: 推送通知开关 +- `emailNotification`: 邮件通知开关 +- `soundNotification`: 声音提醒开关 + +#### 应用设置 + +- `autoLogin`: 自动登录开关 +- `language`: 语言设置 +- `timezone`: 时区设置 + +#### 隐私设置 + +- `analyticsEnabled`: 数据分析开关 +- `crashReportEnabled`: 崩溃报告开关 + +#### 功能设置 + +- `autoSave`: 自动保存开关 +- `showTutorial`: 教程显示开关 + +### 工具函数 + +```typescript +// 获取设置值 +const value = getSetting("pushNotification"); + +// 设置值 +setSetting("autoLogin", true); +``` + +## 样式设计 + +### 设计原则 + +- 移动端优先设计 +- 统一的视觉风格 +- 良好的用户体验 + +### 样式特性 + +- 响应式布局 +- 卡片式设计 +- 圆角边框 +- 阴影效果 +- 渐变背景 + +## 路由配置 + +```typescript +// 设置相关路由 +{ + path: "/settings", + element: , + auth: true, +}, +{ + path: "/security", + element: , + auth: true, +}, +{ + path: "/about", + element: , + auth: true, +} +``` + +## 使用示例 + +### 基本使用 + +```typescript +import { useSettingsStore } from '@/store/module/settings'; + +const MyComponent = () => { + const { settings, updateSetting } = useSettingsStore(); + + const handleToggleNotification = () => { + updateSetting('pushNotification', !settings.pushNotification); + }; + + return ( + + ); +}; +``` + +## 扩展功能 + +### 添加新设置项 + +1. 在 `AppSettings` 接口中添加新字段 +2. 在 `defaultSettings` 中设置默认值 +3. 在设置页面中添加对应的UI组件 +4. 在样式文件中添加相应的样式 + +### 添加新设置页面 + +1. 创建新的页面组件 +2. 在路由配置中添加路由 +3. 在主设置页面中添加导航链接 +4. 添加相应的样式 + +## 注意事项 + +1. **数据持久化**:所有设置都会自动保存到本地存储 +2. **权限控制**:某些设置可能需要管理员权限 +3. **兼容性**:确保在不同设备和浏览器上的兼容性 +4. **性能优化**:避免频繁的设置更新影响性能 + +## 未来规划 + +- [ ] 多语言支持 +- [ ] 设置导入导出 +- [ ] 云端同步设置 +- [ ] 设置备份恢复 +- [ ] 高级设置选项 diff --git a/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx b/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx new file mode 100644 index 00000000..bdd371b9 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx @@ -0,0 +1,228 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { NavBar, List, Button, Dialog, Toast, Card, Input } from "antd-mobile"; +import { + LockOutlined, + MobileOutlined, + SafetyOutlined, + RightOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import { useUserStore } from "@/store/module/user"; +import style from "./index.module.scss"; + +const SecuritySetting: React.FC = () => { + const navigate = useNavigate(); + const { user } = useUserStore(); + const [showPasswordDialog, setShowPasswordDialog] = useState(false); + const [passwordForm, setPasswordForm] = useState({ + oldPassword: "", + newPassword: "", + confirmPassword: "", + }); + + // 修改密码 + const handleChangePassword = async () => { + const { oldPassword, newPassword, confirmPassword } = passwordForm; + + if (!oldPassword || !newPassword || !confirmPassword) { + Toast.show({ content: "请填写完整信息", position: "top" }); + return; + } + + if (newPassword !== confirmPassword) { + Toast.show({ content: "两次输入的新密码不一致", position: "top" }); + return; + } + + if (newPassword.length < 6) { + Toast.show({ content: "新密码长度不能少于6位", position: "top" }); + return; + } + + try { + // TODO: 调用修改密码API + Toast.show({ content: "密码修改成功", position: "top" }); + setShowPasswordDialog(false); + setPasswordForm({ + oldPassword: "", + newPassword: "", + confirmPassword: "", + }); + } catch (error: any) { + Toast.show({ content: error.message || "密码修改失败", position: "top" }); + } + }; + + // 绑定手机号 + const handleBindPhone = () => { + Toast.show({ content: "功能开发中", position: "top" }); + }; + + // 登录设备管理 + const handleDeviceManagement = () => { + Toast.show({ content: "功能开发中", position: "top" }); + }; + + // 安全设置项 + const securityItems = [ + { + id: "password", + title: "修改密码", + description: "定期更换密码,保护账号安全", + icon: , + onClick: () => setShowPasswordDialog(true), + }, + { + id: "phone", + title: "绑定手机号", + description: user?.phone + ? `已绑定:${user.phone}` + : "绑定手机号,提高账号安全性", + icon: , + onClick: handleBindPhone, + }, + { + id: "devices", + title: "登录设备管理", + description: "查看和管理已登录的设备", + icon: , + onClick: handleDeviceManagement, + }, + ]; + + return ( + navigate(-1)} style={{ background: "#fff" }}> + + 安全设置 + + + } + > +
+ {/* 安全提示卡片 */} + +
+ +
+
账号安全提醒
+
+ 建议定期更换密码,开启双重验证,保护您的账号安全 +
+
+
+
+ + {/* 安全设置列表 */} + +
安全设置
+ + {securityItems.map(item => ( + } + onClick={item.onClick} + arrow + /> + ))} + +
+ + {/* 安全建议 */} + +
安全建议
+
+
+ + 使用强密码,包含字母、数字和特殊字符 +
+
+ + 定期更换密码,建议每3个月更换一次 +
+
+ + 不要在公共场所登录账号 +
+
+ + 及时清理不常用的登录设备 +
+
+
+
+ + {/* 修改密码对话框 */} + + + setPasswordForm(prev => ({ ...prev, oldPassword: value })) + } + /> + + setPasswordForm(prev => ({ ...prev, newPassword: value })) + } + /> + + setPasswordForm(prev => ({ ...prev, confirmPassword: value })) + } + /> + + } + closeOnAction + actions={[ + [ + { + key: "cancel", + text: "取消", + onClick: () => { + setShowPasswordDialog(false); + setPasswordForm({ + oldPassword: "", + newPassword: "", + confirmPassword: "", + }); + }, + }, + { + key: "confirm", + text: "确认修改", + bold: true, + onClick: handleChangePassword, + }, + ], + ]} + onClose={() => { + setShowPasswordDialog(false); + setPasswordForm({ + oldPassword: "", + newPassword: "", + confirmPassword: "", + }); + }} + /> +
+ ); +}; + +export default SecuritySetting; diff --git a/nkebao/src/pages/mobile/mine/setting/UserSetting.tsx b/nkebao/src/pages/mobile/mine/setting/UserSetting.tsx new file mode 100644 index 00000000..bb36d8e0 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/UserSetting.tsx @@ -0,0 +1,180 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { NavBar, Input, Button, Toast, Card } from "antd-mobile"; +import Layout from "@/components/Layout/Layout"; +import AvatarUpload from "@/components/Upload/AvatarUpload"; +import { useUserStore } from "@/store/module/user"; +import style from "./index.module.scss"; +import NavCommon from "@/components/NavCommon"; +// 更新用户信息接口 +const updateUserInfo = async (data: { username: string; avatar?: string }) => { + const response = await fetch("/api/user/update", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("更新用户信息失败"); + } + + return await response.json(); +}; + +const UserSetting: React.FC = () => { + const navigate = useNavigate(); + const { user, setUser } = useUserStore(); + const [nickname, setNickname] = useState(user?.username || ""); + const [avatar, setAvatar] = useState(user?.avatar || ""); + const [saving, setSaving] = useState(false); + + // 保存个人信息 + const handleSave = async () => { + if (!nickname.trim()) { + Toast.show({ content: "昵称不能为空", position: "top" }); + return; + } + + if (nickname.length > 20) { + Toast.show({ content: "昵称长度不能超过20个字符", position: "top" }); + return; + } + + if (!user) { + Toast.show({ content: "用户信息不存在", position: "top" }); + return; + } + + setSaving(true); + try { + // 调用API更新用户信息 + const updateData: { username: string; avatar?: string } = { + username: nickname, + }; + + // 如果头像有变化,也一起更新 + if (avatar !== user.avatar) { + updateData.avatar = avatar; + } + + await updateUserInfo(updateData); + + // 更新本地用户信息 + setUser({ + ...user, + username: nickname, + avatar: avatar, + }); + + Toast.show({ content: "保存成功", position: "top" }); + navigate(-1); + } catch (error: any) { + console.error("保存失败:", error); + Toast.show({ content: error.message || "保存失败", position: "top" }); + } finally { + setSaving(false); + } + }; + + // 重置信息 + const handleReset = () => { + setNickname(user?.username || ""); + setAvatar(user?.avatar || ""); + Toast.show({ content: "已重置", position: "top" }); + }; + + return ( + } + footer={ +
+ +
+ } + > +
+ {/* 头像设置 */} + +
+
头像
+
+ +
+
+
+ + {/* 基本信息 */} + +
+
基本信息
+ +
+ + +
{nickname.length}/20
+
+ +
+ +
+ {user?.account || "未知账号"} +
+
+ +
+ +
+ {user?.phone || "未绑定"} +
+
+ +
+ +
+ {user?.isAdmin === 1 ? "管理员" : "普通用户"} +
+
+
+
+ + {/* 提示信息 */} + +
+
温馨提示
+
+
+ • 昵称修改后将在下次登录时生效 +
+
+ • 头像支持JPG、PNG格式,建议尺寸200x200像素 +
+
+ • 请确保上传的头像符合相关法律法规 +
+
+
+
+
+
+ ); +}; + +export default UserSetting; diff --git a/nkebao/src/pages/mobile/mine/setting/index.module.scss b/nkebao/src/pages/mobile/mine/setting/index.module.scss new file mode 100644 index 00000000..d3cc2ff5 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/index.module.scss @@ -0,0 +1,729 @@ +.save-buttons { + padding: 12px; + background: #fff; +} +.setting-page { + padding: 12px; + + .user-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .user-info { + display: flex; + align-items: center; + padding: 16px; + gap: 16px; + + .avatar { + width: 60px; + height: 60px; + border-radius: 30px; + overflow: hidden; + flex-shrink: 0; + background: #f0f0f0; + border: 2px solid #e0e0e0; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .avatar-placeholder { + width: 100%; + height: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + font-weight: bold; + } + } + + .user-details { + flex: 1; + min-width: 0; + + .username { + font-size: 18px; + font-weight: 600; + color: #333; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .account { + font-size: 14px; + color: #666; + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .role { + font-size: 12px; + color: #999; + background: #f0f0f0; + padding: 2px 8px; + border-radius: 10px; + display: inline-block; + } + } + } + } + + .setting-group { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .group-title { + padding: 12px 16px 8px; + font-size: 14px; + font-weight: 600; + color: #666; + background: #fafafa; + border-bottom: 1px solid #f0f0f0; + } + + :global(.adm-list) { + --border-inner: solid 1px #f0f0f0; + --border-top: none; + --border-bottom: none; + --adm-font-size-main: 16px; + --adm-color-text: #333; + --adm-color-text-secondary: #666; + + .adm-list-item { + padding: 16px; + min-height: 56px; + + .adm-list-item-content { + padding: 0; + } + + .adm-list-item-content-prefix { + margin-right: 12px; + font-size: 20px; + color: var(--primary-color); + } + + .adm-list-item-content-main { + flex: 1; + min-width: 0; + + .adm-list-item-content-main-title { + font-size: 16px; + font-weight: 500; + color: #333; + margin-bottom: 4px; + } + + .adm-list-item-content-main-description { + font-size: 14px; + color: #666; + line-height: 1.4; + } + } + + .adm-list-item-content-extra { + margin-left: 8px; + } + + &:last-child { + border-bottom: none; + } + + &:active { + background-color: #f5f5f5; + } + } + } + } + + .version-info { + text-align: center; + padding: 24px 16px; + color: #999; + font-size: 12px; + line-height: 1.6; + + span { + display: block; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 0; + } + } + } + + // 安全设置页面样式 + .security-tip-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background: linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%); + + .tip-content { + display: flex; + align-items: flex-start; + padding: 16px; + gap: 12px; + + .tip-icon { + font-size: 24px; + color: #fa8c16; + flex-shrink: 0; + margin-top: 2px; + } + + .tip-text { + flex: 1; + + .tip-title { + font-size: 16px; + font-weight: 600; + color: #d46b08; + margin-bottom: 4px; + } + + .tip-description { + font-size: 14px; + color: #873800; + line-height: 1.4; + } + } + } + } + + .security-advice-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .advice-title { + padding: 16px 16px 12px; + font-size: 16px; + font-weight: 600; + color: #333; + border-bottom: 1px solid #f0f0f0; + } + + .advice-list { + padding: 16px; + + .advice-item { + display: flex; + align-items: flex-start; + margin-bottom: 12px; + font-size: 14px; + color: #666; + line-height: 1.5; + + &:last-child { + margin-bottom: 0; + } + + .advice-dot { + color: var(--primary-color); + margin-right: 8px; + font-weight: bold; + flex-shrink: 0; + margin-top: 1px; + } + } + } + } + + .password-form { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 0; + + :global(.adm-input) { + border: 1px solid #d9d9d9; + border-radius: 8px; + padding: 12px; + font-size: 16px; + + &:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(24, 142, 238, 0.2); + } + } + } + + // 关于页面样式 + .app-info-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .app-info { + display: flex; + align-items: center; + padding: 20px 16px; + gap: 16px; + + .app-logo { + width: 80px; + height: 80px; + border-radius: 20px; + overflow: hidden; + flex-shrink: 0; + + .logo-placeholder { + width: 100%; + height: 100%; + background: linear-gradient( + 135deg, + var(--primary-color) 0%, + var(--primary-color-dark) 100% + ); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 32px; + } + } + + .app-details { + flex: 1; + min-width: 0; + + .app-name { + font-size: 20px; + font-weight: 600; + color: #333; + margin-bottom: 4px; + } + + .app-version { + font-size: 14px; + color: #666; + margin-bottom: 2px; + } + + .app-build { + font-size: 12px; + color: #999; + } + } + } + + .app-description { + padding: 0 16px 20px; + font-size: 14px; + color: #666; + line-height: 1.5; + } + } + + .features-list { + padding: 16px; + + .feature-item { + margin-bottom: 16px; + padding: 16px; + background: #f8f9fa; + border-radius: 8px; + border-left: 4px solid var(--primary-color); + + &:last-child { + margin-bottom: 0; + } + + .feature-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 8px; + } + + .feature-description { + font-size: 14px; + color: #666; + line-height: 1.5; + } + } + } + + // 新的功能特性网格布局 + .features-grid { + padding: 12px; + display: grid; + grid-template-columns: 1fr; + gap: 8px; + + .feature-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: #ffffff; + border-radius: 8px; + border: 1px solid #f0f0f0; + transition: all 0.2s ease; + position: relative; + + &:hover { + background: #f8f9fa; + border-color: var(--primary-color); + transform: translateX(4px); + } + + .feature-icon { + flex-shrink: 0; + + .icon-placeholder { + width: 32px; + height: 32px; + border-radius: 6px; + background: var(--primary-color); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 14px; + font-weight: 600; + } + } + + .feature-content { + flex: 1; + min-width: 0; + + .feature-title { + font-size: 15px; + font-weight: 500; + color: #333; + margin-bottom: 2px; + line-height: 1.3; + } + + .feature-description { + font-size: 12px; + color: #999; + line-height: 1.4; + } + } + + // 为不同卡片添加不同的图标颜色 + &:nth-child(1) .icon-placeholder { + background: #1890ff; + } + + &:nth-child(2) .icon-placeholder { + background: #52c41a; + } + + &:nth-child(3) .icon-placeholder { + background: #722ed1; + } + + &:nth-child(4) .icon-placeholder { + background: #fa8c16; + } + + &:nth-child(5) .icon-placeholder { + background: #eb2f96; + } + } + } + + .copyright-info { + text-align: center; + padding: 32px 16px; + color: #999; + font-size: 12px; + line-height: 1.6; + + .copyright-text { + font-weight: 500; + margin-bottom: 4px; + } + + .copyright-subtext { + color: #ccc; + } + } + + // 隐私协议页面样式 + .privacy-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .privacy-content { + padding: 20px 16px; + + h2 { + font-size: 20px; + font-weight: 600; + color: #333; + margin-bottom: 8px; + text-align: center; + } + + .update-time { + font-size: 12px; + color: #999; + text-align: center; + margin-bottom: 24px; + } + + section { + margin-bottom: 24px; + + h3 { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 12px; + } + + p { + font-size: 14px; + color: #666; + line-height: 1.6; + margin-bottom: 8px; + } + + ul { + margin: 8px 0; + padding-left: 20px; + + li { + font-size: 14px; + color: #666; + line-height: 1.6; + margin-bottom: 4px; + } + } + } + + .privacy-footer { + text-align: center; + margin-top: 32px; + padding-top: 20px; + border-top: 1px solid #f0f0f0; + + p { + font-size: 14px; + color: #999; + font-style: italic; + } + } + } + } + + // 个人信息页面样式 + .save-buttons { + padding: 16px; + background: #fff; + border-top: 1px solid #f0f0f0; + } + + .avatar-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .avatar-section { + padding: 20px 16px; + + .avatar-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 16px; + } + + .avatar-container { + display: flex; + justify-content: center; + margin-bottom: 12px; + + .avatar-wrapper { + position: relative; + width: 100px; + height: 100px; + border-radius: 50px; + overflow: hidden; + background: #f0f0f0; + border: 2px solid #e0e0e0; + cursor: pointer; + + .avatar-image { + width: 100%; + height: 100%; + object-fit: cover; + } + + .avatar-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + font-size: 40px; + } + + .avatar-upload-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + opacity: 0; + transition: opacity 0.3s ease; + + &:hover { + opacity: 1; + } + + .upload-loading { + font-size: 12px; + text-align: center; + line-height: 1.4; + } + } + + &:hover .avatar-upload-overlay { + opacity: 1; + } + } + } + + .avatar-tip { + font-size: 12px; + color: #999; + text-align: center; + line-height: 1.4; + } + } + } + + .info-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .info-section { + padding: 20px 16px; + + .info-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 20px; + } + + .input-group { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + + .input-label { + display: block; + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 8px; + } + + .input-field { + width: 100%; + border: 1px solid #d9d9d9; + border-radius: 8px; + padding: 12px; + font-size: 16px; + background: #fff; + + &:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(24, 142, 238, 0.2); + } + } + + .readonly-field { + padding: 12px; + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + font-size: 16px; + color: #666; + min-height: 48px; + display: flex; + align-items: center; + } + + .input-tip { + font-size: 12px; + color: #999; + margin-top: 4px; + text-align: right; + } + } + } + } + + .tip-card { + margin-bottom: 12px; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + .tip-content { + padding: 16px; + + .tip-title { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 12px; + } + + .tip-list { + .tip-item { + font-size: 12px; + color: #666; + line-height: 1.6; + margin-bottom: 6px; + + &:last-child { + margin-bottom: 0; + } + } + } + } + } +} diff --git a/nkebao/src/pages/mobile/mine/setting/index.tsx b/nkebao/src/pages/mobile/mine/setting/index.tsx new file mode 100644 index 00000000..3f728d75 --- /dev/null +++ b/nkebao/src/pages/mobile/mine/setting/index.tsx @@ -0,0 +1,238 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { NavBar, List, Switch, Button, Dialog, Toast, Card } from "antd-mobile"; +import { + UserOutlined, + SafetyOutlined, + InfoCircleOutlined, + LogoutOutlined, + SettingOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import { useUserStore } from "@/store/module/user"; +import { useSettingsStore } from "@/store/module/settings"; +import style from "./index.module.scss"; + +interface SettingItem { + id: string; + title: string; + description?: string; + icon: React.ReactNode; + type: "navigate" | "switch" | "button"; + value?: boolean; + path?: string; + onClick?: () => void; +} + +const Setting: React.FC = () => { + const navigate = useNavigate(); + const { user, logout } = useUserStore(); + const { settings, updateSetting } = useSettingsStore(); + const [showLogoutDialog, setShowLogoutDialog] = useState(false); + + // 处理开关变化 + const handleSwitchChange = (key: keyof typeof settings, value: boolean) => { + updateSetting(key, value); + + Toast.show({ + content: `设置已${value ? "开启" : "关闭"}`, + position: "top", + }); + }; + + // 退出登录 + const handleLogout = () => { + logout(); + setShowLogoutDialog(false); + navigate("/login"); + Toast.show({ + content: "退出成功", + position: "top", + }); + }; + + // 清除缓存 + const handleClearCache = () => { + Dialog.confirm({ + content: "确定要清除缓存吗?这将清除所有本地数据。", + onConfirm: () => { + localStorage.clear(); + sessionStorage.clear(); + Toast.show({ + content: "缓存已清除", + position: "top", + }); + }, + }); + }; + + // 设置项配置 + const settingGroups: { title: string; items: SettingItem[] }[] = [ + { + title: "账户设置", + items: [ + { + id: "profile", + title: "个人信息", + description: "修改头像、昵称等基本信息", + icon: , + type: "navigate", + path: "/userSet", + }, + { + id: "security", + title: "安全设置", + description: "密码修改、登录设备管理", + icon: , + type: "navigate", + path: "/security", + }, + ], + }, + { + title: "其他", + items: [ + { + id: "clearCache", + title: "清除缓存", + description: "清除本地缓存数据", + icon: , + type: "button", + onClick: handleClearCache, + }, + { + id: "privacy", + title: "用户隐私协议", + description: "查看用户隐私协议和数据处理说明", + icon: , + type: "navigate", + path: "/privacy", + }, + { + id: "about", + title: "关于我们", + description: "版本信息、联系方式", + icon: , + type: "navigate", + path: "/about", + }, + { + id: "logout", + title: "退出登录", + description: "安全退出当前账号", + icon: , + type: "button", + onClick: () => setShowLogoutDialog(true), + }, + ], + }, + ]; + + // 渲染设置项 + const renderSettingItem = (item: SettingItem) => { + const handleClick = () => { + if (item.type === "navigate" && item.path) { + navigate(item.path); + } else if (item.type === "switch" && item.onClick) { + item.onClick(); + } else if (item.type === "button" && item.onClick) { + item.onClick(); + } + }; + + return ( + item.onClick?.()} /> + ) : null + } + onClick={handleClick} + arrow={item.type === "navigate"} + /> + ); + }; + + return ( + navigate(-1)} style={{ background: "#fff" }}> + + 设置 + + + } + > +
+ {/* 用户信息卡片 */} + +
+
+ {user?.avatar ? ( + 头像 + ) : ( +
+ {user?.username?.charAt(0) || "用"} +
+ )} +
+
+
+ {user?.username || "未设置昵称"} +
+
+ {user?.account || "未知账号"} +
+
+ {user?.isAdmin === 1 ? "管理员" : "普通用户"} +
+
+
+
+ + {/* 设置列表 */} + {settingGroups.map((group, groupIndex) => ( + +
{group.title}
+ {group.items.map(renderSettingItem)} +
+ ))} + + {/* 版本信息 */} +
+ 版本 1.0.0 + © 2024 存客宝管理系统 +
+
+ + {/* 退出登录确认对话框 */} + setShowLogoutDialog(false)} + /> +
+ ); +}; + +export default Setting; diff --git a/nkebao/src/pages/mobile/mine/userSet/index.module.scss b/nkebao/src/pages/mobile/mine/userSet/index.module.scss deleted file mode 100644 index 448cf14c..00000000 --- a/nkebao/src/pages/mobile/mine/userSet/index.module.scss +++ /dev/null @@ -1,115 +0,0 @@ -.user-set-page { - background: #f7f8fa; -} -.user-card { - margin: 18px 16px 0 16px; - border-radius: 14px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); -} -.user-info { - display: flex; - align-items: flex-start; - padding: 24px 20px 20px 20px; -} -.avatar { - width: 64px; - height: 64px; - border-radius: 50%; - background: #f5f5f5; - display: flex; - align-items: center; - justify-content: center; - font-size: 30px; - font-weight: 700; - color: #1890ff; - margin-right: 22px; - overflow: hidden; -} -.avatar-placeholder { - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - font-size: 30px; - font-weight: 700; - color: #1890ff; - background: #e6f7ff; - border-radius: 50%; -} -.info-list { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 14px; -} -.info-item { - display: flex; - align-items: center; - font-size: 16px; -} -.label { - color: #888; - min-width: 70px; - font-size: 15px; -} -.value { - color: #222; - font-weight: 500; - font-size: 16px; - margin-left: 8px; - word-break: break-all; -} -.avatar-upload { - position: relative; - cursor: pointer; - width: 64px; - height: 64px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - overflow: hidden; - background: #f5f5f5; - transition: box-shadow 0.2s; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} -.avatar-upload img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 50%; -} -.avatar-edit { - position: absolute; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.45); - color: #fff; - font-size: 13px; - text-align: center; - padding: 3px 0 2px 0; - border-radius: 0 0 32px 32px; - opacity: 0; - transition: opacity 0.2s; - pointer-events: none; -} -.avatar-upload:hover .avatar-edit { - opacity: 1; - pointer-events: auto; -} -.edit-input { - flex: 1; - min-width: 0; - font-size: 16px; - border-radius: 8px; - border: 1px solid #e5e6eb; - padding: 4px 10px; - background: #fafbfc; -} -.save-btn { - padding: 12px; - background: #fff; -} diff --git a/nkebao/src/pages/mobile/mine/userSet/index.tsx b/nkebao/src/pages/mobile/mine/userSet/index.tsx deleted file mode 100644 index 5624e6a7..00000000 --- a/nkebao/src/pages/mobile/mine/userSet/index.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { useRef, useState } from "react"; -import { useUserStore } from "@/store/module/user"; -import { Card, Button, Input, Toast } from "antd-mobile"; -import style from "./index.module.scss"; -import { useNavigate } from "react-router-dom"; -import Layout from "@/components/Layout/Layout"; -import NavCommon from "@/components/NavCommon"; - -const UserSetting: React.FC = () => { - const { user, setUser } = useUserStore(); - const navigate = useNavigate(); - const [nickname, setNickname] = useState(user?.username || ""); - const [avatar, setAvatar] = useState(user?.avatar || ""); - const [uploading, setUploading] = useState(false); - const fileInputRef = useRef(null); - - // 头像上传 - const handleAvatarChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; - const reader = new FileReader(); - reader.onload = ev => { - setAvatar(ev.target?.result as string); - }; - reader.readAsDataURL(file); - }; - - // 保存 - const handleSave = async () => { - if (!nickname.trim()) { - Toast.show({ content: "昵称不能为空", position: "top" }); - return; - } - if (!user) return; - setUser({ ...user, id: user.id, username: nickname, avatar }); - Toast.show({ content: "保存成功", position: "top" }); - navigate(-1); - }; - - return ( - } - footer={ -
- -
- } - > -
- -
-
-
fileInputRef.current?.click()} - > - {avatar ? ( - 头像 - ) : ( -
- )} -
更换头像
- -
-
-
-
- 昵称 - -
-
- 手机号 - {user?.phone || "-"} -
-
- 账号 - {user?.account || "-"} -
-
- 角色 - - {user?.isAdmin === 1 ? "管理员" : "普通用户"} - -
-
-
-
-
-
- ); -}; - -export default UserSetting; diff --git a/nkebao/src/router/module/mine.tsx b/nkebao/src/router/module/mine.tsx index 46e852e8..431c3da2 100644 --- a/nkebao/src/router/module/mine.tsx +++ b/nkebao/src/router/module/mine.tsx @@ -6,7 +6,12 @@ import TrafficPoolDetail from "@/pages/mobile/mine/traffic-pool/detail/index"; import WechatAccounts from "@/pages/mobile/mine/wechat-accounts/list/index"; import WechatAccountDetail from "@/pages/mobile/mine/wechat-accounts/detail/index"; import Recharge from "@/pages/mobile/mine/recharge/index"; -import UserSetting from "@/pages/mobile/mine/userSet/index"; +import Setting from "@/pages/mobile/mine/setting/index"; +import SecuritySetting from "@/pages/mobile/mine/setting/SecuritySetting"; +import About from "@/pages/mobile/mine/setting/About"; +import Privacy from "@/pages/mobile/mine/setting/Privacy"; +import UserSetting from "@/pages/mobile/mine/setting/UserSetting"; + const routes = [ { path: "/mine", @@ -51,6 +56,26 @@ const routes = [ }, { path: "/settings", + element: , + auth: true, + }, + { + path: "/security", + element: , + auth: true, + }, + { + path: "/about", + element: , + auth: true, + }, + { + path: "/privacy", + element: , + auth: true, + }, + { + path: "/userSet", element: , auth: true, }, diff --git a/nkebao/src/store/module/settings.ts b/nkebao/src/store/module/settings.ts new file mode 100644 index 00000000..af884e36 --- /dev/null +++ b/nkebao/src/store/module/settings.ts @@ -0,0 +1,73 @@ +import { createPersistStore } from "@/store/createPersistStore"; + +export interface AppSettings { + // 应用设置 + language: string; + timezone: string; + + // 隐私设置 + analyticsEnabled: boolean; + crashReportEnabled: boolean; + + // 功能设置 + autoSave: boolean; + showTutorial: boolean; +} + +interface SettingsState { + settings: AppSettings; + setSettings: (settings: Partial) => void; + resetSettings: () => void; + updateSetting: ( + key: K, + value: AppSettings[K], + ) => void; +} + +// 默认设置 +const defaultSettings: AppSettings = { + language: "zh-CN", + timezone: "Asia/Shanghai", + analyticsEnabled: true, + crashReportEnabled: true, + autoSave: true, + showTutorial: true, +}; + +export const useSettingsStore = createPersistStore( + set => ({ + settings: defaultSettings, + + setSettings: newSettings => + set(state => ({ + settings: { ...state.settings, ...newSettings }, + })), + + resetSettings: () => set({ settings: defaultSettings }), + + updateSetting: (key, value) => + set(state => ({ + settings: { ...state.settings, [key]: value }, + })), + }), + "settings-store", + state => ({ + settings: state.settings, + }), +); + +// 设置工具函数 +export const getSetting = ( + key: K, +): AppSettings[K] => { + const { settings } = useSettingsStore.getState(); + return settings[key]; +}; + +export const setSetting = ( + key: K, + value: AppSettings[K], +) => { + const { updateSetting } = useSettingsStore.getState(); + updateSetting(key, value); +};