feat: 本次提交更新内容如下
定版本,准备大批量迁移
This commit is contained in:
269
nkebao/ANTD_MOBILE_ICONS.md
Normal file
269
nkebao/ANTD_MOBILE_ICONS.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Ant Design Mobile 图标完整列表
|
||||
|
||||
## 📋 图标分类
|
||||
|
||||
### 🔗 基础图标 (Outline)
|
||||
- `AddOutline` - 添加
|
||||
- `AppOutline` - 应用
|
||||
- `ArrowDownOutline` - 向下箭头
|
||||
- `ArrowLeftOutline` - 向左箭头
|
||||
- `ArrowRightOutline` - 向右箭头
|
||||
- `ArrowUpOutline` - 向上箭头
|
||||
- `AudioOutline` - 音频
|
||||
- `BarChartOutline` - 柱状图
|
||||
- `BellOutline` - 铃铛
|
||||
- `BookOutline` - 书本
|
||||
- `CalendarOutline` - 日历
|
||||
- `CameraOutline` - 相机
|
||||
- `CheckOutline` - 勾选
|
||||
- `ClockCircleOutline` - 时钟
|
||||
- `CloseOutline` - 关闭
|
||||
- `DeleteOutline` - 删除
|
||||
- `DownlandOutline` - 下载
|
||||
- `EditSOutline` - 编辑
|
||||
- `EllipsisOutline` - 省略号
|
||||
- `EyeOutline` - 眼睛
|
||||
- `FileOutline` - 文件
|
||||
- `FilterOutline` - 筛选
|
||||
- `FireOutline` - 火焰
|
||||
- `FlagOutline` - 旗帜
|
||||
- `FolderOutline` - 文件夹
|
||||
- `GiftOutline` - 礼物
|
||||
- `HeartOutline` - 心形
|
||||
- `HomeOutline` - 首页
|
||||
- `ImageOutline` - 图片
|
||||
- `LeftOutline` - 左箭头
|
||||
- `LikeOutline` - 点赞
|
||||
- `LinkOutline` - 链接
|
||||
- `LocationOutline` - 位置
|
||||
- `LockOutline` - 锁定
|
||||
- `MailOutline` - 邮件
|
||||
- `MessageOutline` - 消息
|
||||
- `MoreOutline` - 更多
|
||||
- `PayCircleOutline` - 支付
|
||||
- `PhoneOutline` - 电话
|
||||
- `PictureOutline` - 图片
|
||||
- `PlayOutline` - 播放
|
||||
- `PlusOutline` - 加号
|
||||
- `RightOutline` - 右箭头
|
||||
- `ScanOutline` - 扫描
|
||||
- `SearchOutline` - 搜索
|
||||
- `SendOutline` - 发送
|
||||
- `SettingOutline` - 设置
|
||||
- `ShareOutline` - 分享
|
||||
- `ShopbagOutline` - 购物袋
|
||||
- `StarOutline` - 星星
|
||||
- `TeamOutline` - 团队
|
||||
- `ThumbsUpOutline` - 点赞
|
||||
- `TimeOutline` - 时间
|
||||
- `UnlockOutline` - 解锁
|
||||
- `UserOutline` - 用户
|
||||
- `VideoOutline` - 视频
|
||||
- `VoiceOutline` - 语音
|
||||
|
||||
### 🎨 填充图标 (Fill)
|
||||
- `AddCircleFill` - 添加圆形
|
||||
- `AppFill` - 应用填充
|
||||
- `AudioFill` - 音频填充
|
||||
- `BellFill` - 铃铛填充
|
||||
- `BookFill` - 书本填充
|
||||
- `CalendarFill` - 日历填充
|
||||
- `CameraFill` - 相机填充
|
||||
- `CheckCircleFill` - 勾选圆形
|
||||
- `ClockCircleFill` - 时钟圆形
|
||||
- `CloseCircleFill` - 关闭圆形
|
||||
- `DeleteFill` - 删除填充
|
||||
- `DownlandFill` - 下载填充
|
||||
- `EditSFill` - 编辑填充
|
||||
- `EyeFill` - 眼睛填充
|
||||
- `FileFill` - 文件填充
|
||||
- `FilterFill` - 筛选填充
|
||||
- `FireFill` - 火焰填充
|
||||
- `FlagFill` - 旗帜填充
|
||||
- `FolderFill` - 文件夹填充
|
||||
- `GiftFill` - 礼物填充
|
||||
- `HeartFill` - 心形填充
|
||||
- `HomeFill` - 首页填充
|
||||
- `ImageFill` - 图片填充
|
||||
- `LeftFill` - 左箭头填充
|
||||
- `LikeFill` - 点赞填充
|
||||
- `LinkFill` - 链接填充
|
||||
- `LocationFill` - 位置填充
|
||||
- `LockFill` - 锁定填充
|
||||
- `MailFill` - 邮件填充
|
||||
- `MessageFill` - 消息填充
|
||||
- `MoreFill` - 更多填充
|
||||
- `PayCircleFill` - 支付圆形
|
||||
- `PhoneFill` - 电话填充
|
||||
- `PictureFill` - 图片填充
|
||||
- `PlayFill` - 播放填充
|
||||
- `PlusCircleFill` - 加号圆形
|
||||
- `RightFill` - 右箭头填充
|
||||
- `ScanFill` - 扫描填充
|
||||
- `SearchFill` - 搜索填充
|
||||
- `SendFill` - 发送填充
|
||||
- `SettingFill` - 设置填充
|
||||
- `ShareFill` - 分享填充
|
||||
- `ShopbagFill` - 购物袋填充
|
||||
- `StarFill` - 星星填充
|
||||
- `TeamFill` - 团队填充
|
||||
- `ThumbsUpFill` - 点赞填充
|
||||
- `TimeFill` - 时间填充
|
||||
- `UnlockFill` - 解锁填充
|
||||
- `UserFill` - 用户填充
|
||||
- `VideoFill` - 视频填充
|
||||
- `VoiceFill` - 语音填充
|
||||
|
||||
### 📊 图表图标
|
||||
- `BarChartOutline` - 柱状图
|
||||
- `LineChartOutline` - 折线图
|
||||
- `PieOutline` - 饼图
|
||||
- `AreaOutline` - 面积图
|
||||
- `RadarOutline` - 雷达图
|
||||
- `ScatterOutline` - 散点图
|
||||
|
||||
### 🔧 功能图标
|
||||
- `AddOutline` - 添加
|
||||
- `DeleteOutline` - 删除
|
||||
- `EditSOutline` - 编辑
|
||||
- `SearchOutline` - 搜索
|
||||
- `FilterOutline` - 筛选
|
||||
- `SettingOutline` - 设置
|
||||
- `MoreOutline` - 更多
|
||||
- `CloseOutline` - 关闭
|
||||
- `CheckOutline` - 勾选
|
||||
|
||||
### 📱 通信图标
|
||||
- `MessageOutline` - 消息
|
||||
- `PhoneOutline` - 电话
|
||||
- `MailOutline` - 邮件
|
||||
- `SendOutline` - 发送
|
||||
- `VoiceOutline` - 语音
|
||||
- `VideoOutline` - 视频
|
||||
|
||||
### 👥 用户图标
|
||||
- `UserOutline` - 用户
|
||||
- `TeamOutline` - 团队
|
||||
- `HeartOutline` - 心形
|
||||
- `LikeOutline` - 点赞
|
||||
- `ThumbsUpOutline` - 点赞
|
||||
|
||||
### 🛍️ 商业图标
|
||||
- `ShopbagOutline` - 购物袋
|
||||
- `PayCircleOutline` - 支付
|
||||
- `GiftOutline` - 礼物
|
||||
- `StarOutline` - 星星
|
||||
|
||||
### 📁 文件图标
|
||||
- `FileOutline` - 文件
|
||||
- `FolderOutline` - 文件夹
|
||||
- `ImageOutline` - 图片
|
||||
- `PictureOutline` - 图片
|
||||
- `CameraOutline` - 相机
|
||||
|
||||
### 🎵 媒体图标
|
||||
- `AudioOutline` - 音频
|
||||
- `VideoOutline` - 视频
|
||||
- `PlayOutline` - 播放
|
||||
- `VoiceOutline` - 语音
|
||||
|
||||
### 📅 时间图标
|
||||
- `CalendarOutline` - 日历
|
||||
- `ClockCircleOutline` - 时钟
|
||||
- `TimeOutline` - 时间
|
||||
|
||||
### 🔒 安全图标
|
||||
- `LockOutline` - 锁定
|
||||
- `UnlockOutline` - 解锁
|
||||
- `EyeOutline` - 眼睛
|
||||
|
||||
### 📍 位置图标
|
||||
- `LocationOutline` - 位置
|
||||
- `FlagOutline` - 旗帜
|
||||
|
||||
### 🔗 导航图标
|
||||
- `ArrowUpOutline` - 向上箭头
|
||||
- `ArrowDownOutline` - 向下箭头
|
||||
- `ArrowLeftOutline` - 向左箭头
|
||||
- `ArrowRightOutline` - 向右箭头
|
||||
- `LeftOutline` - 左箭头
|
||||
- `RightOutline` - 右箭头
|
||||
|
||||
## 🎨 使用示例
|
||||
|
||||
### 基础用法
|
||||
```tsx
|
||||
import { AddOutline, UserOutline, SettingOutline } from 'antd-mobile-icons';
|
||||
|
||||
// 在组件中使用
|
||||
<AddOutline />
|
||||
<UserOutline />
|
||||
<SettingOutline />
|
||||
```
|
||||
|
||||
### 自定义样式
|
||||
```tsx
|
||||
import { HeartOutline } from 'antd-mobile-icons';
|
||||
|
||||
// 自定义颜色和大小
|
||||
<HeartOutline style={{ color: '#ff4d4f', fontSize: 24 }} />
|
||||
```
|
||||
|
||||
### 在项目中的使用
|
||||
```tsx
|
||||
// 工作台页面示例
|
||||
const features = [
|
||||
{
|
||||
icon: <ThumbsUpOutline style={{ color: "#ff4d4f" }} />,
|
||||
name: "自动点赞"
|
||||
},
|
||||
{
|
||||
icon: <MessageOutline style={{ color: "#1890ff" }} />,
|
||||
name: "AI对话助手"
|
||||
},
|
||||
{
|
||||
icon: <BarChartOutline style={{ color: "#531dab" }} />,
|
||||
name: "AI数据分析"
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **图标命名规则**: 所有图标都以 `Outline` 或 `Fill` 结尾
|
||||
2. **导入方式**: 从 `antd-mobile-icons` 包中导入
|
||||
3. **样式定制**: 可以通过 `style` 属性自定义颜色、大小等
|
||||
4. **响应式**: 图标支持响应式设计,可以配合 CSS 媒体查询使用
|
||||
5. **可访问性**: 图标支持 `aria-label` 等无障碍属性
|
||||
|
||||
## 🔗 相关资源
|
||||
|
||||
- [Ant Design Mobile 官方文档](https://mobile.ant.design/)
|
||||
- [图标组件文档](https://mobile.ant.design/zh/components/icon)
|
||||
- [图标库 GitHub](https://github.com/ant-design/ant-design-mobile)
|
||||
|
||||
## 📊 项目中已使用的图标
|
||||
|
||||
### 工作台页面
|
||||
- `ThumbsUpOutline` - 自动点赞
|
||||
- `MessageOutline` - AI对话助手
|
||||
- `SendOutline` - 群消息推送
|
||||
- `TeamOutline` - 自动建群
|
||||
- `ShareOutline` - 流量分发
|
||||
- `AppOutline` - AI策略优化
|
||||
- `BarChartOutline` - AI数据分析
|
||||
- `LineChartOutline` - AI销售预测
|
||||
- `ClockCircleOutline` - 朋友圈同步
|
||||
|
||||
### 底部导航
|
||||
- `AppOutline` - 首页
|
||||
- `ShopbagOutline` - 场景获客
|
||||
- `PieOutline` - 工作台
|
||||
- `UserOutline` - 我的
|
||||
|
||||
### 其他页面
|
||||
- `AddOutline` - 添加按钮
|
||||
- `UserAddOutline` - 用户添加
|
||||
- `BellOutline` - 通知
|
||||
- `LoopOutline` - 循环/同步
|
||||
- `TravelOutline` - 旅行/任务
|
||||
140
nkebao/ROUTE_MIGRATION.md
Normal file
140
nkebao/ROUTE_MIGRATION.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 路由迁移总结
|
||||
|
||||
## 📋 迁移概述
|
||||
|
||||
已成功将旧项目 @Cunkebao 的路由结构迁移到新项目 @nkebao,并进行了合理的文件拆分和归类。
|
||||
|
||||
## 🗂️ 路由模块结构
|
||||
|
||||
### 1. 基础路由模块 (`auth.tsx`)
|
||||
- `/login` - 登录页面
|
||||
|
||||
### 2. 设备管理模块 (`devices.tsx`)
|
||||
- `/devices` - 设备列表
|
||||
- `/devices/:id` - 设备详情
|
||||
|
||||
### 3. 微信号管理模块 (`wechat-accounts.tsx`)
|
||||
- `/wechat-accounts` - 微信号列表
|
||||
- `/wechat-accounts/:id` - 微信号详情
|
||||
|
||||
### 4. 工作台模块 (`workspace.tsx`)
|
||||
- `/workspace` - 工作台主页
|
||||
- `/workspace/auto-like` - 自动点赞
|
||||
- `/workspace/auto-like/new` - 新建自动点赞
|
||||
- `/workspace/auto-like/:id` - 自动点赞详情
|
||||
- `/workspace/auto-like/:id/edit` - 编辑自动点赞
|
||||
- `/workspace/auto-group` - 自动分组
|
||||
- `/workspace/auto-group/:id` - 自动分组详情
|
||||
- `/workspace/group-push` - 群发推送
|
||||
- `/workspace/group-push/new` - 新建群发推送
|
||||
- `/workspace/group-push/:id` - 群发推送详情
|
||||
- `/workspace/group-push/:id/edit` - 编辑群发推送
|
||||
- `/workspace/moments-sync` - 朋友圈同步
|
||||
- `/workspace/moments-sync/new` - 新建朋友圈同步
|
||||
- `/workspace/moments-sync/:id` - 朋友圈同步详情
|
||||
- `/workspace/moments-sync/edit/:id` - 编辑朋友圈同步
|
||||
- `/workspace/ai-assistant` - AI助手
|
||||
- `/workspace/traffic-distribution` - 流量分发
|
||||
- `/workspace/traffic-distribution/new` - 新建流量分发
|
||||
- `/workspace/traffic-distribution/edit/:id` - 编辑流量分发
|
||||
- `/workspace/traffic-distribution/:id` - 流量分发详情
|
||||
|
||||
### 5. 场景管理模块 (`scenarios.tsx`)
|
||||
- `/scenarios` - 场景列表
|
||||
- `/scenarios/new` - 新建场景
|
||||
- `/scenarios/new/:scenarioId` - 新建场景(带场景ID)
|
||||
- `/scenarios/edit/:planId` - 编辑场景
|
||||
- `/scenarios/list/:scenarioId/:scenarioName` - 场景列表详情
|
||||
|
||||
### 6. 内容管理模块 (`content.tsx`)
|
||||
- `/content` - 内容管理
|
||||
- `/content/new` - 新建内容
|
||||
- `/content/edit/:id` - 编辑内容
|
||||
- `/content/materials/:id` - 素材管理
|
||||
- `/content/materials/new/:id` - 新建素材
|
||||
- `/content/materials/edit/:id/:materialId` - 编辑素材
|
||||
|
||||
### 7. 流量池模块 (`traffic-pool.tsx`)
|
||||
- `/traffic-pool` - 流量池列表
|
||||
- `/traffic-pool/:id` - 流量池详情
|
||||
|
||||
### 8. 其他功能模块 (`other.tsx`)
|
||||
- `/profile` - 个人中心
|
||||
- `/plans` - 计划管理
|
||||
- `/plans/:planId` - 计划详情
|
||||
- `/orders` - 订单管理
|
||||
- `/contact-import` - 联系人导入
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 路由配置
|
||||
- 使用 React Router DOM v6
|
||||
- 支持动态路由参数
|
||||
- 统一的权限控制
|
||||
- 模块化路由管理
|
||||
|
||||
### 权限控制
|
||||
- 每个路由都配置了 `auth` 属性
|
||||
- 支持角色权限控制
|
||||
- 自动权限检查
|
||||
|
||||
### 组件结构
|
||||
- 使用 Layout 组件统一布局
|
||||
- 使用 MeauMobile 组件作为底部导航
|
||||
- 创建了 PlaceholderPage 通用占位组件
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── router/
|
||||
│ ├── module/
|
||||
│ │ ├── index.tsx # 主路由文件
|
||||
│ │ ├── auth.tsx # 认证路由
|
||||
│ │ ├── devices.tsx # 设备管理路由
|
||||
│ │ ├── wechat-accounts.tsx # 微信号管理路由
|
||||
│ │ ├── workspace.tsx # 工作台路由
|
||||
│ │ ├── scenarios.tsx # 场景管理路由
|
||||
│ │ ├── content.tsx # 内容管理路由
|
||||
│ │ ├── traffic-pool.tsx # 流量池路由
|
||||
│ │ └── other.tsx # 其他功能路由
|
||||
│ ├── index.tsx # 路由入口
|
||||
│ ├── permissionRoute.tsx # 权限路由组件
|
||||
│ └── config.ts # 路由配置
|
||||
├── pages/
|
||||
│ ├── login/ # 登录页面
|
||||
│ ├── devices/ # 设备管理页面
|
||||
│ ├── wechat-accounts/ # 微信号管理页面
|
||||
│ ├── workspace/ # 工作台页面
|
||||
│ ├── scenarios/ # 场景管理页面
|
||||
│ ├── content/ # 内容管理页面
|
||||
│ ├── traffic-pool/ # 流量池页面
|
||||
│ ├── profile/ # 个人中心页面
|
||||
│ ├── plans/ # 计划管理页面
|
||||
│ ├── orders/ # 订单管理页面
|
||||
│ └── contact-import/ # 联系人导入页面
|
||||
└── components/
|
||||
└── PlaceholderPage.tsx # 通用占位页面组件
|
||||
```
|
||||
|
||||
## 🎯 主要特性
|
||||
|
||||
1. **模块化设计**: 按功能模块拆分路由,便于维护
|
||||
2. **权限控制**: 统一的权限管理机制
|
||||
3. **类型安全**: 完整的 TypeScript 类型定义
|
||||
4. **响应式设计**: 适配移动端和桌面端
|
||||
5. **代码复用**: 使用通用组件减少重复代码
|
||||
|
||||
## 🚀 下一步计划
|
||||
|
||||
1. 实现具体的页面功能
|
||||
2. 完善权限控制逻辑
|
||||
3. 添加路由懒加载
|
||||
4. 优化页面性能
|
||||
5. 添加路由守卫
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
- 所有页面目前都是占位页面,需要根据业务需求逐步实现
|
||||
- 路由权限控制需要根据实际业务逻辑调整
|
||||
- 建议按照模块优先级逐步开发页面功能
|
||||
159
nkebao/WORKSPACE_MIGRATION.md
Normal file
159
nkebao/WORKSPACE_MIGRATION.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 工作台页面迁移总结
|
||||
|
||||
## 📋 迁移概述
|
||||
|
||||
已成功将旧项目 @Cunkebao 的工作台页面迁移到新项目 @nkebao,并进行了技术栈转换和样式优化。
|
||||
|
||||
## 🔄 技术栈转换
|
||||
|
||||
### 旧项目技术栈
|
||||
- React + TypeScript
|
||||
- Lucide React 图标库
|
||||
- Tailwind CSS
|
||||
- React Router DOM v6
|
||||
- 自定义UI组件库
|
||||
|
||||
### 新项目技术栈
|
||||
- React + TypeScript
|
||||
- Ant Design Mobile
|
||||
- SCSS Modules
|
||||
- React Router DOM v6
|
||||
- 统一的Layout组件
|
||||
|
||||
## 📱 页面结构
|
||||
|
||||
### 主要功能模块
|
||||
|
||||
1. **任务统计卡片**
|
||||
- 总任务数(带进度条)
|
||||
- 今日任务(带活跃度指标)
|
||||
|
||||
2. **常用功能**
|
||||
- 自动点赞(New标签)
|
||||
- 朋友圈同步
|
||||
- 群消息推送
|
||||
- 自动建群
|
||||
- 流量分发
|
||||
- AI对话助手(New标签)
|
||||
|
||||
3. **AI智能助手**
|
||||
- AI数据分析(New标签)
|
||||
- AI策略优化(New标签)
|
||||
- AI销售预测
|
||||
|
||||
## 🎨 设计优化
|
||||
|
||||
### 视觉改进
|
||||
- 使用Ant Design Mobile组件,保持移动端一致性
|
||||
- 采用SCSS Modules进行样式管理
|
||||
- 统一的颜色主题和间距规范
|
||||
- 响应式设计,适配不同屏幕尺寸
|
||||
|
||||
### 交互优化
|
||||
- 卡片悬停效果
|
||||
- 统一的图标样式和颜色
|
||||
- 清晰的层级结构
|
||||
- 流畅的页面跳转
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
src/pages/workspace/
|
||||
├── Workspace.tsx # 工作台主页
|
||||
├── Workspace.module.scss # 工作台样式
|
||||
├── auto-like/ # 自动点赞模块
|
||||
├── auto-group/ # 自动分组模块
|
||||
├── group-push/ # 群发推送模块
|
||||
├── moments-sync/ # 朋友圈同步模块
|
||||
├── ai-assistant/ # AI助手模块
|
||||
└── traffic-distribution/ # 流量分发模块
|
||||
```
|
||||
|
||||
## 🔗 路由配置
|
||||
|
||||
### 主要路由
|
||||
- `/workspace` - 工作台主页
|
||||
- `/workspace/auto-like` - 自动点赞
|
||||
- `/workspace/group-push` - 群发推送
|
||||
- `/workspace/moments-sync` - 朋友圈同步
|
||||
- `/workspace/auto-group` - 自动分组
|
||||
- `/workspace/traffic-distribution` - 流量分发
|
||||
- `/workspace/ai-assistant` - AI助手
|
||||
|
||||
### AI功能路由
|
||||
- `/workspace/ai-analyzer` - AI数据分析
|
||||
- `/workspace/ai-strategy` - AI策略优化
|
||||
- `/workspace/ai-forecast` - AI销售预测
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 数据展示
|
||||
- 实时任务统计
|
||||
- 进度条可视化
|
||||
- 活跃度指标
|
||||
- 功能分类展示
|
||||
|
||||
### 导航优化
|
||||
- 底部导航集成
|
||||
- 路由自动激活
|
||||
- 页面跳转优化
|
||||
- 返回按钮处理
|
||||
|
||||
### 用户体验
|
||||
- 加载状态处理
|
||||
- 错误边界处理
|
||||
- 响应式布局
|
||||
- 触摸友好设计
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 组件化设计
|
||||
- 使用Layout组件统一布局
|
||||
- 复用PlaceholderPage组件
|
||||
- 模块化的功能卡片
|
||||
- 统一的样式规范
|
||||
|
||||
### 状态管理
|
||||
- 模拟数据展示
|
||||
- 路由状态同步
|
||||
- 组件生命周期管理
|
||||
- 性能优化
|
||||
|
||||
### 样式系统
|
||||
- SCSS Modules避免样式冲突
|
||||
- CSS变量统一主题
|
||||
- 响应式断点设计
|
||||
- 移动端优化
|
||||
|
||||
## 🚀 下一步计划
|
||||
|
||||
1. **数据集成**
|
||||
- 连接真实API接口
|
||||
- 实现数据获取和更新
|
||||
- 添加加载状态
|
||||
- 错误处理机制
|
||||
|
||||
2. **功能完善**
|
||||
- 实现具体功能页面
|
||||
- 添加表单验证
|
||||
- 完善交互逻辑
|
||||
- 优化用户体验
|
||||
|
||||
3. **性能优化**
|
||||
- 路由懒加载
|
||||
- 组件代码分割
|
||||
- 图片资源优化
|
||||
- 缓存策略
|
||||
|
||||
4. **测试完善**
|
||||
- 单元测试
|
||||
- 集成测试
|
||||
- 端到端测试
|
||||
- 性能测试
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
- 所有功能页面目前使用占位组件
|
||||
- 需要根据实际业务需求调整数据展示
|
||||
- 建议按照用户使用频率优先开发核心功能
|
||||
- 注意保持与整体设计风格的一致性
|
||||
@@ -25,7 +25,7 @@ const tabs = [
|
||||
key: "work",
|
||||
title: "工作台",
|
||||
icon: <PieOutline />,
|
||||
path: "/work",
|
||||
path: "/workspace",
|
||||
},
|
||||
{
|
||||
key: "mine",
|
||||
@@ -36,7 +36,7 @@ const tabs = [
|
||||
];
|
||||
|
||||
// 需要展示菜单的路由白名单(可根据实际业务调整)
|
||||
const menuPaths = ["/", "/scene", "/work", "/mine"];
|
||||
const menuPaths = ["/", "/scene", "/workspace", "/mine"];
|
||||
|
||||
const MeauMobile: React.FC = () => {
|
||||
const location = useLocation();
|
||||
|
||||
56
nkebao/src/components/PlaceholderPage.tsx
Normal file
56
nkebao/src/components/PlaceholderPage.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
import { NavBar, Button } from "antd-mobile";
|
||||
import { AddOutline } from "antd-mobile-icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
interface PlaceholderPageProps {
|
||||
title: string;
|
||||
showBack?: boolean;
|
||||
showAddButton?: boolean;
|
||||
addButtonText?: string;
|
||||
showFooter?: boolean;
|
||||
}
|
||||
|
||||
const PlaceholderPage: React.FC<PlaceholderPageProps> = ({
|
||||
title,
|
||||
showBack = true,
|
||||
showAddButton = false,
|
||||
addButtonText = "新建",
|
||||
showFooter = true,
|
||||
}) => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
backArrow={showBack}
|
||||
style={{ background: "#fff" }}
|
||||
onBack={showBack ? () => window.history.back() : undefined}
|
||||
left={
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
{title}
|
||||
</div>
|
||||
}
|
||||
right={
|
||||
showAddButton ? (
|
||||
<Button size="small" color="primary">
|
||||
<AddOutline />
|
||||
<span style={{ marginLeft: 4, fontSize: 12 }}>
|
||||
{addButtonText}
|
||||
</span>
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={showFooter ? <MeauMobile /> : undefined}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>{title}页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaceholderPage;
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const About: React.FC = () => {
|
||||
return <h1>关于 About</h1>;
|
||||
};
|
||||
|
||||
export default About;
|
||||
14
nkebao/src/pages/contact-import/ContactImport.tsx
Normal file
14
nkebao/src/pages/contact-import/ContactImport.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const ContactImport: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage
|
||||
title="联系人导入"
|
||||
showAddButton
|
||||
addButtonText="导入联系人"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactImport;
|
||||
10
nkebao/src/pages/content/Content.tsx
Normal file
10
nkebao/src/pages/content/Content.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Content: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="内容管理" showAddButton addButtonText="新建内容" />
|
||||
);
|
||||
};
|
||||
|
||||
export default Content;
|
||||
8
nkebao/src/pages/content/NewContent.tsx
Normal file
8
nkebao/src/pages/content/NewContent.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const NewContent: React.FC = () => {
|
||||
return <PlaceholderPage title="新建内容" />;
|
||||
};
|
||||
|
||||
export default NewContent;
|
||||
10
nkebao/src/pages/content/materials/List.tsx
Normal file
10
nkebao/src/pages/content/materials/List.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Materials: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="素材管理" showAddButton addButtonText="新建素材" />
|
||||
);
|
||||
};
|
||||
|
||||
export default Materials;
|
||||
8
nkebao/src/pages/content/materials/New.tsx
Normal file
8
nkebao/src/pages/content/materials/New.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const MaterialsNew: React.FC = () => {
|
||||
return <PlaceholderPage title="新建素材" />;
|
||||
};
|
||||
|
||||
export default MaterialsNew;
|
||||
30
nkebao/src/pages/devices/DeviceDetail.tsx
Normal file
30
nkebao/src/pages/devices/DeviceDetail.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { NavBar } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const DeviceDetail: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
backArrow
|
||||
style={{ background: "#fff" }}
|
||||
onBack={() => window.history.back()}
|
||||
>
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
设备详情
|
||||
</div>
|
||||
</NavBar>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>设备详情页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceDetail;
|
||||
37
nkebao/src/pages/devices/Devices.tsx
Normal file
37
nkebao/src/pages/devices/Devices.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { NavBar, Button } from "antd-mobile";
|
||||
import { AddOutline } from "antd-mobile-icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const Devices: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
back={null}
|
||||
style={{ background: "#fff" }}
|
||||
left={
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
设备管理
|
||||
</div>
|
||||
}
|
||||
right={
|
||||
<Button size="small" color="primary">
|
||||
<AddOutline />
|
||||
<span style={{ marginLeft: 4, fontSize: 12 }}>添加设备</span>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>设备管理页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Devices;
|
||||
@@ -1,439 +1,52 @@
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: var(--primary-gradient);
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.login-content {
|
||||
padding: 40px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 背景装饰
|
||||
.bg-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
animation: float 6s ease-in-out infinite;
|
||||
|
||||
&:nth-child(1) {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
bottom: -75px;
|
||||
left: -75px;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
top: 50%;
|
||||
right: 10%;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: #ffffff;
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 24px;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--primary-gradient);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
box-shadow: 0 6px 12px var(--primary-shadow);
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
background: var(--primary-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// 标签页样式
|
||||
.tab-container {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 3px;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&.active {
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
background: white;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
display: none; // 隐藏分割线指示器
|
||||
}
|
||||
|
||||
// 表单样式
|
||||
.login-form {
|
||||
:global(.adm-form) {
|
||||
--adm-font-size-main: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f8f9fa;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--primary-color);
|
||||
background: white;
|
||||
box-shadow: 0 0 0 3px var(--primary-shadow-light);
|
||||
}
|
||||
}
|
||||
|
||||
.input-prefix {
|
||||
padding: 0 12px;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.phone-input,
|
||||
.password-input,
|
||||
.code-input {
|
||||
flex: 1;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding: 12px 14px !important;
|
||||
font-size: 15px !important;
|
||||
color: #333 !important;
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.eye-icon {
|
||||
padding: 0 12px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
padding: 6px 12px;
|
||||
margin-right: 6px;
|
||||
background: var(--primary-gradient);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 8px var(--primary-shadow);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #e5e5e5;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.agreement-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
|
||||
:global(.adm-checkbox) {
|
||||
margin-top: 0;
|
||||
flex-shrink: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
.login-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
height: 46px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
border-radius: 10px;
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
margin-top: 20px;
|
||||
border-radius: 20px;
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
box-shadow: 0 6px 12px var(--primary-shadow);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 16px var(--primary-shadow-dark);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #e5e5e5;
|
||||
color: #999;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 24px 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
span {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 0 12px;
|
||||
color: #999;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.third-party-login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.third-party-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: #f8f9fa;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-icon,
|
||||
.apple-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.wechat-icon {
|
||||
background: #07c160;
|
||||
box-shadow: 0 3px 8px rgba(7, 193, 96, 0.3);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.apple-icon {
|
||||
background: #000;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 480px) {
|
||||
.login-container {
|
||||
padding: 24px 20px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.third-party-login {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.third-party-item {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.wechat-icon,
|
||||
.apple-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
box-shadow: 0 2px 8px var(--primary-shadow);
|
||||
height: 44px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -1,331 +1,64 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Form, Input, Button, Toast, Tabs, Checkbox } from "antd-mobile";
|
||||
import {
|
||||
EyeInvisibleOutline,
|
||||
EyeOutline,
|
||||
UserOutline,
|
||||
} from "antd-mobile-icons";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
|
||||
import React from "react";
|
||||
import { NavBar, Form, Input, Button, Toast } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./login.module.scss";
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [activeTab, setActiveTab] = useState(1); // 1: 密码登录, 2: 验证码登录
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { login } = useUserStore();
|
||||
|
||||
// 倒计时效果
|
||||
useEffect(() => {
|
||||
if (countdown > 0) {
|
||||
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
// 检查URL是否为登录页面
|
||||
const isLoginPage = (url: string) => {
|
||||
try {
|
||||
const urlObj = new URL(url, window.location.origin);
|
||||
return urlObj.pathname === "/login" || urlObj.pathname.endsWith("/login");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 发送验证码
|
||||
const handleSendVerificationCode = async () => {
|
||||
const account = form.getFieldValue("account");
|
||||
|
||||
if (!account) {
|
||||
Toast.show({ content: "请输入手机号", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 手机号格式验证
|
||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||
if (!phoneRegex.test(account)) {
|
||||
Toast.show({ content: "请输入正确的11位手机号", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
await sendVerificationCode({
|
||||
mobile: account,
|
||||
type: "login",
|
||||
});
|
||||
|
||||
Toast.show({ content: "验证码已发送", position: "top" });
|
||||
setCountdown(60);
|
||||
} catch (error) {
|
||||
// 错误已在request中处理,这里不需要额外处理
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async (values: any) => {
|
||||
if (!agreeToTerms) {
|
||||
Toast.show({ content: "请同意用户协议和隐私政策", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// 添加typeId参数
|
||||
const loginParams = {
|
||||
...values,
|
||||
typeId: activeTab as number,
|
||||
};
|
||||
|
||||
let response;
|
||||
if (activeTab === 1) {
|
||||
response = await loginWithPassword(loginParams);
|
||||
} else {
|
||||
response = await loginWithCode(loginParams);
|
||||
}
|
||||
console.log(response, "response");
|
||||
|
||||
// 更新状态管理(token会自动存储到localStorage,用户信息存储在状态管理中)
|
||||
login(response.token, response.member);
|
||||
|
||||
Toast.show({ content: "登录成功", position: "top" });
|
||||
|
||||
// 跳转到首页或重定向URL
|
||||
const returnUrl = searchParams.get("returnUrl");
|
||||
if (returnUrl) {
|
||||
const decodedUrl = decodeURIComponent(returnUrl);
|
||||
if (isLoginPage(decodedUrl)) {
|
||||
navigate("/");
|
||||
} else {
|
||||
window.location.href = decodedUrl;
|
||||
}
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 错误已在request中处理,这里不需要额外处理
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 第三方登录处理
|
||||
const handleWechatLogin = () => {
|
||||
Toast.show({ content: "微信登录功能开发中", position: "top" });
|
||||
};
|
||||
|
||||
const handleAppleLogin = () => {
|
||||
Toast.show({ content: "Apple登录功能开发中", position: "top" });
|
||||
const onFinish = (values: any) => {
|
||||
console.log("登录信息:", values);
|
||||
Toast.show({
|
||||
content: "登录功能待实现",
|
||||
position: "top",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style["login-page"]}>
|
||||
{/* 背景装饰 */}
|
||||
<div className={style["bg-decoration"]}>
|
||||
<div className={style["bg-circle"]}></div>
|
||||
<div className={style["bg-circle"]}></div>
|
||||
<div className={style["bg-circle"]}></div>
|
||||
</div>
|
||||
<Layout>
|
||||
<div className={style["login-page"]}>
|
||||
<NavBar back={null} style={{ background: "#fff" }}>
|
||||
<div className={style["nav-title"]}>登录</div>
|
||||
</NavBar>
|
||||
|
||||
<div className={style["login-container"]}>
|
||||
{/* Logo和标题区域 */}
|
||||
<div className={style["login-header"]}>
|
||||
<div className={style["logo-section"]}>
|
||||
<div className={style["logo-icon"]}>
|
||||
<UserOutline />
|
||||
</div>
|
||||
<h1 className={style["app-name"]}>存客宝</h1>
|
||||
</div>
|
||||
<p className={style["subtitle"]}>登录您的账户继续使用</p>
|
||||
</div>
|
||||
|
||||
{/* 登录表单 */}
|
||||
<div className={style["form-container"]}>
|
||||
{/* 标签页切换 */}
|
||||
<div className={style["tab-container"]}>
|
||||
<div
|
||||
className={`${style["tab-item"]} ${
|
||||
activeTab === 1 ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab(1)}
|
||||
>
|
||||
密码登录
|
||||
</div>
|
||||
<div
|
||||
className={`${style["tab-item"]} ${
|
||||
activeTab === 2 ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab(2)}
|
||||
>
|
||||
验证码登录
|
||||
</div>
|
||||
<div
|
||||
className={`${style["tab-indicator"]} ${
|
||||
activeTab === 2 ? style["slide"] : ""
|
||||
}`}
|
||||
></div>
|
||||
<div className={style["login-content"]}>
|
||||
<div className={style["login-header"]}>
|
||||
<h1 className={style["login-title"]}>存客宝</h1>
|
||||
<p className={style["login-subtitle"]}>欢迎回来,请登录您的账户</p>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
className={style["login-form"]}
|
||||
onFinish={handleLogin}
|
||||
>
|
||||
{/* 手机号输入 */}
|
||||
<Form.Item
|
||||
name="account"
|
||||
label="手机号"
|
||||
rules={[
|
||||
{ required: true, message: "请输入手机号" },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: "请输入正确的11位手机号",
|
||||
},
|
||||
]}
|
||||
label="用户名"
|
||||
name="username"
|
||||
rules={[{ required: true, message: "请输入用户名" }]}
|
||||
>
|
||||
<div className={style["input-wrapper"]}>
|
||||
<span className={style["input-prefix"]}>+86</span>
|
||||
<Input
|
||||
placeholder="请输入手机号"
|
||||
clearable
|
||||
className={style["phone-input"]}
|
||||
/>
|
||||
</div>
|
||||
<Input placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 密码输入 */}
|
||||
{activeTab === 1 && (
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="密码"
|
||||
rules={[{ required: true, message: "请输入密码" }]}
|
||||
>
|
||||
<div className={style["input-wrapper"]}>
|
||||
<Input
|
||||
placeholder="请输入密码"
|
||||
clearable
|
||||
type={showPassword ? "text" : "password"}
|
||||
className={style["password-input"]}
|
||||
/>
|
||||
<div
|
||||
className={style["eye-icon"]}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? <EyeOutline /> : <EyeInvisibleOutline />}
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
rules={[{ required: true, message: "请输入密码" }]}
|
||||
>
|
||||
<Input type="password" placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 验证码输入 */}
|
||||
{activeTab === 2 && (
|
||||
<Form.Item
|
||||
name="verificationCode"
|
||||
label="验证码"
|
||||
rules={[{ required: true, message: "请输入验证码" }]}
|
||||
>
|
||||
<div className={style["input-wrapper"]}>
|
||||
<Input
|
||||
placeholder="请输入验证码"
|
||||
clearable
|
||||
className={style["code-input"]}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`${style["send-code-btn"]} ${
|
||||
countdown > 0 ? style["disabled"] : ""
|
||||
}`}
|
||||
onClick={handleSendVerificationCode}
|
||||
disabled={loading || countdown > 0}
|
||||
>
|
||||
{countdown > 0 ? `${countdown}s` : "获取验证码"}
|
||||
</button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* 用户协议 */}
|
||||
<div className={style["agreement-section"]}>
|
||||
<Checkbox
|
||||
checked={agreeToTerms}
|
||||
onChange={setAgreeToTerms}
|
||||
className={style["agreement-checkbox"]}
|
||||
>
|
||||
<span className={style["agreement-text"]}>
|
||||
我已阅读并同意
|
||||
<span className={style["agreement-link"]}>
|
||||
《存客宝用户协议》
|
||||
</span>
|
||||
和
|
||||
<span className={style["agreement-link"]}>《隐私政策》</span>
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
{/* 登录按钮 */}
|
||||
<Button
|
||||
block
|
||||
type="submit"
|
||||
color="primary"
|
||||
loading={loading}
|
||||
size="large"
|
||||
className={style["login-btn"]}
|
||||
type="submit"
|
||||
className={style["login-button"]}
|
||||
>
|
||||
{loading ? "登录中..." : "登录"}
|
||||
登录
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
{/* 分割线 */}
|
||||
<div className={style["divider"]}>
|
||||
<span>其他登录方式</span>
|
||||
</div>
|
||||
|
||||
{/* 第三方登录 */}
|
||||
<div className={style["third-party-login"]}>
|
||||
<div
|
||||
className={style["third-party-item"]}
|
||||
onClick={handleWechatLogin}
|
||||
>
|
||||
<div className={style["wechat-icon"]}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
width="24"
|
||||
className={style["wechat-icon"]}
|
||||
>
|
||||
<path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.81-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.595-6.348zM5.959 5.48c.609 0 1.104.498 1.104 1.112 0 .612-.495 1.11-1.104 1.11-.612 0-1.108-.498-1.108-1.11 0-.614.496-1.112 1.108-1.112zm5.315 0c.61 0 1.107.498 1.107 1.112 0 .612-.497 1.11-1.107 1.11-.611 0-1.105-.498-1.105-1.11 0-.614.494-1.112 1.105-1.112z"></path>
|
||||
<path d="M23.002 15.816c0-3.309-3.136-6-7-6-3.863 0-7 2.691-7 6 0 3.31 3.137 6 7 6 .814 0 1.601-.099 2.338-.285a.7.7 0 0 1 .579.08l1.5.87a.267.267 0 0 0 .135.044c.13 0 .236-.108.236-.241 0-.06-.023-.118-.038-.17l-.309-1.167a.476.476 0 0 1 .172-.534c1.645-1.17 2.387-2.835 2.387-4.597zm-9.498-1.19c-.497 0-.9-.407-.9-.908a.905.905 0 0 1 .9-.91c.498 0 .9.408.9.91 0 .5-.402.908-.9.908zm4.998 0c-.497 0-.9-.407-.9-.908a.905.905 0 0 1 .9-.91c.498 0 .9.408.9.91 0 .5-.402.908-.9.908z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span>微信</span>
|
||||
</div>
|
||||
<div
|
||||
className={style["third-party-item"]}
|
||||
onClick={handleAppleLogin}
|
||||
>
|
||||
<div className={style["apple-icon"]}>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span>Apple</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
8
nkebao/src/pages/orders/Orders.tsx
Normal file
8
nkebao/src/pages/orders/Orders.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Orders: React.FC = () => {
|
||||
return <PlaceholderPage title="订单管理" />;
|
||||
};
|
||||
|
||||
export default Orders;
|
||||
8
nkebao/src/pages/plans/PlanDetail.tsx
Normal file
8
nkebao/src/pages/plans/PlanDetail.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const PlanDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="计划详情" />;
|
||||
};
|
||||
|
||||
export default PlanDetail;
|
||||
10
nkebao/src/pages/plans/Plans.tsx
Normal file
10
nkebao/src/pages/plans/Plans.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Plans: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="计划管理" showAddButton addButtonText="新建计划" />
|
||||
);
|
||||
};
|
||||
|
||||
export default Plans;
|
||||
8
nkebao/src/pages/profile/Profile.tsx
Normal file
8
nkebao/src/pages/profile/Profile.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
return <PlaceholderPage title="个人中心" showBack={false} />;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
8
nkebao/src/pages/scenarios/ScenarioList.tsx
Normal file
8
nkebao/src/pages/scenarios/ScenarioList.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const ScenarioList: React.FC = () => {
|
||||
return <PlaceholderPage title="场景列表" />;
|
||||
};
|
||||
|
||||
export default ScenarioList;
|
||||
10
nkebao/src/pages/scenarios/Scenarios.tsx
Normal file
10
nkebao/src/pages/scenarios/Scenarios.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const Scenarios: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="场景管理" showAddButton addButtonText="新建场景" />
|
||||
);
|
||||
};
|
||||
|
||||
export default Scenarios;
|
||||
8
nkebao/src/pages/scenarios/new/page.tsx
Normal file
8
nkebao/src/pages/scenarios/new/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const NewPlan: React.FC = () => {
|
||||
return <PlaceholderPage title="新建场景" />;
|
||||
};
|
||||
|
||||
export default NewPlan;
|
||||
@@ -1,14 +1,26 @@
|
||||
import request from '@/api/request';
|
||||
|
||||
// 设备统计
|
||||
export function getDeviceStats() {
|
||||
return request('/v1/dashboard/device-stats', {}, 'GET');
|
||||
// 获取场景列表
|
||||
export function getScenarios(params: any) {
|
||||
return request('/v1/plan/scenes', params, 'GET');
|
||||
}
|
||||
|
||||
// 微信号统计
|
||||
export function getWechatStats() {
|
||||
return request('/v1/dashboard/wechat-stats', {}, 'GET');
|
||||
// 获取场景详情
|
||||
export function getScenarioDetail(id: string) {
|
||||
return request(`/v1/scenarios/${id}`, {}, 'GET');
|
||||
}
|
||||
|
||||
// 你可以根据需要继续添加其他接口
|
||||
// 例如:场景获客统计、今日数据统计等
|
||||
// 创建场景
|
||||
export function createScenario(data: any) {
|
||||
return request('/v1/scenarios', data, 'POST');
|
||||
}
|
||||
|
||||
// 更新场景
|
||||
export function updateScenario(id: string, data: any) {
|
||||
return request(`/v1/scenarios/${id}`, data, 'PUT');
|
||||
}
|
||||
|
||||
// 删除场景
|
||||
export function deleteScenario(id: string) {
|
||||
return request(`/v1/scenarios/${id}`, {}, 'DELETE');
|
||||
}
|
||||
|
||||
@@ -1,71 +1,319 @@
|
||||
.home-page {
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
// 导航栏样式
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.home-cards {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
> :global(.adm-card) {
|
||||
flex: 1;
|
||||
.nav-text {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.new-plan-btn {
|
||||
border-radius: 20px;
|
||||
padding: 4px 12px;
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px var(--primary-shadow);
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 1px 4px var(--primary-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.home-section {
|
||||
// 页面容器
|
||||
.scene-page {
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
min-height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
// 错误提示
|
||||
.error-notice {
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background: #fff2e8;
|
||||
border: 1px solid #ffd591;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 4px rgba(255, 213, 145, 0.2);
|
||||
}
|
||||
|
||||
.error-notice-text {
|
||||
font-size: 12px;
|
||||
color: #d46b08;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 加载状态
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 错误状态
|
||||
.error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 14px;
|
||||
color: #ff4d4f;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
min-width: 100px;
|
||||
border-radius: 20px;
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px var(--primary-shadow);
|
||||
}
|
||||
|
||||
// 页面头部
|
||||
.scene-header {
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 场景列表
|
||||
.scenarios-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// 场景卡片
|
||||
.scenario-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.scenario-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.home-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.home-scene-stats {
|
||||
padding: 16px 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 8px 8px 8px;
|
||||
}
|
||||
.home-scene-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.home-scene-icon {
|
||||
margin: 0 auto 4px auto;
|
||||
}
|
||||
.home-scene-value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #1677ff;
|
||||
}
|
||||
.home-scene-label {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
align-items: center;
|
||||
min-height: 78px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgba(24, 142, 238, 0.12);
|
||||
border-color: var(--primary-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.home-today-item {
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
background: #f7f8fa;
|
||||
.scenario-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scenario-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid rgba(24, 142, 238, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scenario-image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.home-today-value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #1677ff;
|
||||
}
|
||||
.home-today-label {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
|
||||
.scenario-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.home-chart {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
.scenario-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scenario-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.scenario-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scenario-status {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.scenario-growth {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.growth-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 36px;
|
||||
margin-bottom: 12px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
border-radius: 20px;
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px var(--primary-shadow);
|
||||
padding: 6px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 480px) {
|
||||
.scene-page {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.scenario-card {
|
||||
padding: 14px 16px;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.scenario-icon {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
.scenario-image {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.scenario-name {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.stat-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.scenario-growth {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.growth-icon {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,132 +1,184 @@
|
||||
import React, { useState } from "react";
|
||||
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
|
||||
import {
|
||||
AppOutline,
|
||||
UserOutline,
|
||||
PieOutline, // 替换 BarChartOutline
|
||||
ShopbagOutline,
|
||||
BellOutline,
|
||||
} from "antd-mobile-icons";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { NavBar, Button, SpinLoading, Toast, Tag } from "antd-mobile";
|
||||
import { AddOutline, UserAddOutline } from "antd-mobile-icons";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { getScenarios } from "./api";
|
||||
import style from "./index.module.scss";
|
||||
const sceneStats = [
|
||||
{
|
||||
label: "公众号获客",
|
||||
value: 234,
|
||||
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
|
||||
},
|
||||
{
|
||||
label: "海报获客",
|
||||
value: 167,
|
||||
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
|
||||
},
|
||||
{
|
||||
label: "抖音获客",
|
||||
value: 156,
|
||||
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
|
||||
},
|
||||
{
|
||||
label: "小红书获客",
|
||||
value: 89,
|
||||
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
|
||||
},
|
||||
];
|
||||
|
||||
const todayStats = [
|
||||
{ label: "朋友圈同步", value: 12 },
|
||||
{ label: "群发任务", value: 8 },
|
||||
{ label: "获客转化", value: "85%" },
|
||||
{ label: "系统活跃度", value: "98%" },
|
||||
];
|
||||
interface Scenario {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
count: number;
|
||||
growth: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
const Scene: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [scenarios, setScenarios] = useState<Scenario[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchScenarios = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const response = await getScenarios({ page: 1, limit: 20 });
|
||||
|
||||
// 转换API数据为前端需要的格式
|
||||
const transformedScenarios: Scenario[] = response.map((item: any) => ({
|
||||
id: item.id.toString(),
|
||||
name: item.name,
|
||||
image:
|
||||
item.image ||
|
||||
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png",
|
||||
count: item.count, // 模拟今日数据
|
||||
growth: item.growth, // 模拟增长率
|
||||
status: item.status,
|
||||
}));
|
||||
|
||||
setScenarios(transformedScenarios);
|
||||
} catch (error) {
|
||||
console.error("获取场景数据失败:", error);
|
||||
setError("获取场景数据失败,请稍后重试");
|
||||
Toast.show({
|
||||
content: "获取场景数据失败,请稍后重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchScenarios();
|
||||
}, []);
|
||||
|
||||
const handleScenarioClick = (scenarioId: string, scenarioName: string) => {
|
||||
navigate(
|
||||
`/scenarios/list/${scenarioId}/${encodeURIComponent(scenarioName)}`
|
||||
);
|
||||
};
|
||||
|
||||
const handleNewPlan = () => {
|
||||
navigate("/scenarios/new");
|
||||
};
|
||||
|
||||
const getGrowthColor = (growth: string) => {
|
||||
const value = parseFloat(growth.replace(/[+%]/g, ""));
|
||||
if (value > 10) return "#52c41a";
|
||||
if (value > 5) return "#faad14";
|
||||
return "#ff4d4f";
|
||||
};
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar back={null} style={{ background: "#fff" }}>
|
||||
<span style={{ color: "#1677ff", fontWeight: 700 }}>存客宝</span>
|
||||
</NavBar>
|
||||
<NavBar
|
||||
back={null}
|
||||
style={{ background: "#fff" }}
|
||||
left={<div className={style["nav-title"]}>场景获客</div>}
|
||||
right={
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={handleNewPlan}
|
||||
className={style["new-plan-btn"]}
|
||||
>
|
||||
<AddOutline />
|
||||
<span className={style["nav-right"]}>新建计划</span>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div className={style["home-page"]}>
|
||||
{/* 统计卡片 */}
|
||||
<div className={style["home-cards"]}>
|
||||
<Card className={style["home-card"]}>
|
||||
<div className={style["home-card-title"]}>设备数量</div>
|
||||
<div className={style["home-card-value"]}>0</div>
|
||||
</Card>
|
||||
<Card className={style["home-card"]}>
|
||||
<div className={style["home-card-title"]}>微信号数量</div>
|
||||
<div className={style["home-card-value"]}>0</div>
|
||||
</Card>
|
||||
<Card className={style["home-card"]}>
|
||||
<div className={style["home-card-title"]}>在线微信号</div>
|
||||
<div className={style["home-card-value"]}>0</div>
|
||||
</Card>
|
||||
<div className={style["scene-page"]}>
|
||||
<div className={style["scene-header"]}>
|
||||
<div className={style["header-title"]}>
|
||||
<UserAddOutline className={style["header-icon"]} />
|
||||
<span>热门获客场景</span>
|
||||
</div>
|
||||
<div className={style["header-subtitle"]}>
|
||||
选择适合的获客场景,提升转化效果
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 场景获客统计 */}
|
||||
<Card className={style["home-section"]}>
|
||||
<div className={style["home-section-title"]}>场景获客统计</div>
|
||||
<div className={style["home-scene-stats"]}>
|
||||
{sceneStats.map((item) => (
|
||||
<div key={item.label} className={style["home-scene-item"]}>
|
||||
<div className={style["home-scene-icon"]}>{item.icon}</div>
|
||||
<div className={style["home-scene-value"]}>{item.value}</div>
|
||||
<div className={style["home-scene-label"]}>{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 今日数据 */}
|
||||
<Card className={style["home-section"]}>
|
||||
<div className={style["home-section-title"]}>今日数据</div>
|
||||
<Grid columns={2} gap={12}>
|
||||
{todayStats.map((item) => (
|
||||
<Grid.Item key={item.label}>
|
||||
<div className={style["home-today-item"]}>
|
||||
<div className={style["home-today-value"]}>{item.value}</div>
|
||||
<div className={style["home-today-label"]}>{item.label}</div>
|
||||
<div className={style["scenarios-list"]}>
|
||||
{scenarios.map((scenario) => (
|
||||
<div
|
||||
key={scenario.id}
|
||||
className={style["scenario-item"]}
|
||||
onClick={() => handleScenarioClick(scenario.id, scenario.name)}
|
||||
>
|
||||
<div className={style["scenario-card"]}>
|
||||
<div className={style["scenario-left"]}>
|
||||
<div className={style["scenario-icon"]}>
|
||||
<img
|
||||
src={scenario.image}
|
||||
alt={scenario.name}
|
||||
className={style["scenario-image"]}
|
||||
onError={(e) => {
|
||||
e.currentTarget.src =
|
||||
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-api.png";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={style["scenario-info"]}>
|
||||
<div className={style["scenario-name"]}>
|
||||
{scenario.name}
|
||||
</div>
|
||||
<div className={style["scenario-stats"]}>
|
||||
<span className={style["stat-text"]}>
|
||||
今日获客: {scenario.count}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
{/* 每日获客趋势(静态图表占位) */}
|
||||
<Card className={style["home-section"]}>
|
||||
<div className={style["home-section-title"]}>每日获客趋势</div>
|
||||
<div className={style["home-chart"]}>
|
||||
<svg width="100%" height="120">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#1677ff"
|
||||
strokeWidth="3"
|
||||
points="10,100 40,80 70,60 100,50 130,40 160,60 190,80"
|
||||
/>
|
||||
{/* x轴文字 */}
|
||||
{["周一", "周二", "周三", "周四", "周五", "周六", "周日"].map(
|
||||
(d, i) => (
|
||||
<text
|
||||
key={d}
|
||||
x={10 + i * 30}
|
||||
y={115}
|
||||
fontSize="12"
|
||||
textAnchor="middle"
|
||||
<div className={style["scenario-right"]}>
|
||||
<div className={style["scenario-status"]}>
|
||||
<Tag
|
||||
color={scenario.status === 1 ? "success" : "default"}
|
||||
fill="outline"
|
||||
className={style["status-tag"]}
|
||||
>
|
||||
{scenario.status === 1 ? "活跃" : "暂停"}
|
||||
</Tag>
|
||||
</div>
|
||||
<div
|
||||
className={style["scenario-growth"]}
|
||||
style={{ color: getGrowthColor(scenario.growth) }}
|
||||
>
|
||||
{d}
|
||||
</text>
|
||||
)
|
||||
)}
|
||||
</svg>
|
||||
{scenario.growth}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{scenarios.length === 0 && !loading && (
|
||||
<div className={style["empty-state"]}>
|
||||
<div className={style["empty-icon"]}>📊</div>
|
||||
<div className={style["empty-text"]}>暂无场景数据</div>
|
||||
<Button
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={handleNewPlan}
|
||||
className={style["empty-action"]}
|
||||
>
|
||||
创建第一个场景
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
export default Scene;
|
||||
|
||||
10
nkebao/src/pages/traffic-pool/TrafficPool.tsx
Normal file
10
nkebao/src/pages/traffic-pool/TrafficPool.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const TrafficPool: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="流量池" showAddButton addButtonText="新建流量池" />
|
||||
);
|
||||
};
|
||||
|
||||
export default TrafficPool;
|
||||
8
nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx
Normal file
8
nkebao/src/pages/traffic-pool/TrafficPoolDetail.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const TrafficPoolDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="流量池详情" />;
|
||||
};
|
||||
|
||||
export default TrafficPoolDetail;
|
||||
30
nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx
Normal file
30
nkebao/src/pages/wechat-accounts/WechatAccountDetail.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { NavBar } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const WechatAccountDetail: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
backArrow
|
||||
style={{ background: "#fff" }}
|
||||
onBack={() => window.history.back()}
|
||||
>
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
微信号详情
|
||||
</div>
|
||||
</NavBar>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>微信号详情页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default WechatAccountDetail;
|
||||
37
nkebao/src/pages/wechat-accounts/WechatAccounts.tsx
Normal file
37
nkebao/src/pages/wechat-accounts/WechatAccounts.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { NavBar, Button } from "antd-mobile";
|
||||
import { AddOutline } from "antd-mobile-icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const WechatAccounts: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
back={null}
|
||||
style={{ background: "#fff" }}
|
||||
left={
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
微信号管理
|
||||
</div>
|
||||
}
|
||||
right={
|
||||
<Button size="small" color="primary">
|
||||
<AddOutline />
|
||||
<span style={{ marginLeft: 4, fontSize: 12 }}>添加微信号</span>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>微信号管理页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default WechatAccounts;
|
||||
185
nkebao/src/pages/workspace/Workspace.module.scss
Normal file
185
nkebao/src/pages/workspace/Workspace.module.scss
Normal file
@@ -0,0 +1,185 @@
|
||||
.workspace {
|
||||
padding: 16px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.statsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.statsCard {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
:global(.adm-card-body) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.statsTitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.statsValue {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-bottom: 8px;
|
||||
|
||||
:global(.adm-progress-bar) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
:global(.adm-progress-bar-fill) {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.statsSubtitle {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.activityRate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #52c41a;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.activityIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.featuresGrid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.featureLink {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.featureCard {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:global(.adm-card-body) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.featureIcon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.featureHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.featureName {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.newBadge {
|
||||
margin-left: 8px;
|
||||
|
||||
:global(.adm-badge-content) {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.featureDescription {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 375px) {
|
||||
.workspace {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.statsGrid {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.featuresGrid {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.statsCard,
|
||||
.featureCard {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.statsValue {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.featureIcon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
254
nkebao/src/pages/workspace/Workspace.tsx
Normal file
254
nkebao/src/pages/workspace/Workspace.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Card, NavBar, Badge, Progress } from "antd-mobile";
|
||||
import {
|
||||
ThumbsUpOutline,
|
||||
MessageOutline,
|
||||
SendOutline,
|
||||
TeamOutline,
|
||||
ShareOutline,
|
||||
AppOutline,
|
||||
BarChartOutline,
|
||||
LineChartOutline,
|
||||
ClockCircleOutline,
|
||||
} from "antd-mobile-icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
import styles from "./Workspace.module.scss";
|
||||
|
||||
const Workspace: React.FC = () => {
|
||||
// 模拟任务数据
|
||||
const taskStats = {
|
||||
total: 42,
|
||||
inProgress: 12,
|
||||
completed: 30,
|
||||
todayTasks: 12,
|
||||
activityRate: 98,
|
||||
};
|
||||
|
||||
// 常用功能
|
||||
const commonFeatures = [
|
||||
{
|
||||
id: "auto-like",
|
||||
name: "自动点赞",
|
||||
description: "智能自动点赞互动",
|
||||
icon: (
|
||||
<ThumbsUpOutline className={styles.icon} style={{ color: "#ff4d4f" }} />
|
||||
),
|
||||
path: "/workspace/auto-like",
|
||||
bgColor: "#fff2f0",
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
id: "moments-sync",
|
||||
name: "朋友圈同步",
|
||||
description: "自动同步朋友圈内容",
|
||||
icon: (
|
||||
<ClockCircleOutline
|
||||
className={styles.icon}
|
||||
style={{ color: "#722ed1" }}
|
||||
/>
|
||||
),
|
||||
path: "/workspace/moments-sync",
|
||||
bgColor: "#f9f0ff",
|
||||
},
|
||||
{
|
||||
id: "group-push",
|
||||
name: "群消息推送",
|
||||
description: "智能群发助手",
|
||||
icon: (
|
||||
<SendOutline className={styles.icon} style={{ color: "#fa8c16" }} />
|
||||
),
|
||||
path: "/workspace/group-push",
|
||||
bgColor: "#fff7e6",
|
||||
},
|
||||
{
|
||||
id: "auto-group",
|
||||
name: "自动建群",
|
||||
description: "智能拉好友建群",
|
||||
icon: (
|
||||
<TeamOutline className={styles.icon} style={{ color: "#52c41a" }} />
|
||||
),
|
||||
path: "/workspace/auto-group",
|
||||
bgColor: "#f6ffed",
|
||||
},
|
||||
{
|
||||
id: "traffic-distribution",
|
||||
name: "流量分发",
|
||||
description: "管理流量分发和分配",
|
||||
icon: (
|
||||
<ShareOutline className={styles.icon} style={{ color: "#1890ff" }} />
|
||||
),
|
||||
path: "/workspace/traffic-distribution",
|
||||
bgColor: "#e6f7ff",
|
||||
},
|
||||
{
|
||||
id: "ai-assistant",
|
||||
name: "AI对话助手",
|
||||
description: "智能回复,提高互动质量",
|
||||
icon: (
|
||||
<MessageOutline className={styles.icon} style={{ color: "#1890ff" }} />
|
||||
),
|
||||
path: "/workspace/ai-assistant",
|
||||
bgColor: "#e6f7ff",
|
||||
isNew: true,
|
||||
},
|
||||
];
|
||||
|
||||
// AI智能助手
|
||||
const aiFeatures = [
|
||||
{
|
||||
id: "ai-analyzer",
|
||||
name: "AI数据分析",
|
||||
description: "智能分析客户行为特征",
|
||||
icon: (
|
||||
<BarChartOutline className={styles.icon} style={{ color: "#531dab" }} />
|
||||
),
|
||||
path: "/workspace/ai-analyzer",
|
||||
bgColor: "#f0f0ff",
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
id: "ai-strategy",
|
||||
name: "AI策略优化",
|
||||
description: "智能优化获客策略",
|
||||
icon: <AppOutline className={styles.icon} style={{ color: "#13c2c2" }} />,
|
||||
path: "/workspace/ai-strategy",
|
||||
bgColor: "#e6fffb",
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
id: "ai-forecast",
|
||||
name: "AI销售预测",
|
||||
description: "智能预测销售趋势",
|
||||
icon: (
|
||||
<LineChartOutline
|
||||
className={styles.icon}
|
||||
style={{ color: "#d48806" }}
|
||||
/>
|
||||
),
|
||||
path: "/workspace/ai-forecast",
|
||||
bgColor: "#fffbe6",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar back={null} style={{ background: "#fff" }}>
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
工作台
|
||||
</div>
|
||||
</NavBar>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div className={styles.workspace}>
|
||||
{/* 任务统计卡片 */}
|
||||
<div className={styles.statsGrid}>
|
||||
<Card className={styles.statsCard}>
|
||||
<div className={styles.statsTitle}>总任务数</div>
|
||||
<div className={styles.statsValue}>{taskStats.total}</div>
|
||||
<Progress
|
||||
percent={(taskStats.inProgress / taskStats.total) * 100}
|
||||
className={styles.progress}
|
||||
/>
|
||||
<div className={styles.statsSubtitle}>
|
||||
进行中: {taskStats.inProgress} / 已完成: {taskStats.completed}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className={styles.statsCard}>
|
||||
<div className={styles.statsTitle}>今日任务</div>
|
||||
<div className={styles.statsValue} style={{ color: "#52c41a" }}>
|
||||
{taskStats.todayTasks}
|
||||
</div>
|
||||
<div className={styles.activityRate}>
|
||||
<svg
|
||||
className={styles.activityIcon}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 12H7L10 19L14 5L17 12H21"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span>活跃度 {taskStats.activityRate}%</span>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 常用功能 */}
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>常用功能</h2>
|
||||
<div className={styles.featuresGrid}>
|
||||
{commonFeatures.map((feature) => (
|
||||
<Link
|
||||
to={feature.path}
|
||||
key={feature.id}
|
||||
className={styles.featureLink}
|
||||
>
|
||||
<Card className={styles.featureCard}>
|
||||
<div
|
||||
className={styles.featureIcon}
|
||||
style={{ backgroundColor: feature.bgColor }}
|
||||
>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<div className={styles.featureHeader}>
|
||||
<div className={styles.featureName}>{feature.name}</div>
|
||||
{feature.isNew && (
|
||||
<Badge content="New" className={styles.newBadge} />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.featureDescription}>
|
||||
{feature.description}
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI智能助手 */}
|
||||
<div className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>AI 智能助手</h2>
|
||||
<div className={styles.featuresGrid}>
|
||||
{aiFeatures.map((feature) => (
|
||||
<Link
|
||||
to={feature.path}
|
||||
key={feature.id}
|
||||
className={styles.featureLink}
|
||||
>
|
||||
<Card className={styles.featureCard}>
|
||||
<div
|
||||
className={styles.featureIcon}
|
||||
style={{ backgroundColor: feature.bgColor }}
|
||||
>
|
||||
{feature.icon}
|
||||
</div>
|
||||
<div className={styles.featureHeader}>
|
||||
<div className={styles.featureName}>{feature.name}</div>
|
||||
{feature.isNew && (
|
||||
<Badge content="New" className={styles.newBadge} />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.featureDescription}>
|
||||
{feature.description}
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Workspace;
|
||||
8
nkebao/src/pages/workspace/ai-assistant/AIAssistant.tsx
Normal file
8
nkebao/src/pages/workspace/ai-assistant/AIAssistant.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const AIAssistant: React.FC = () => {
|
||||
return <PlaceholderPage title="AI助手" showBack={false} />;
|
||||
};
|
||||
|
||||
export default AIAssistant;
|
||||
10
nkebao/src/pages/workspace/auto-group/AutoGroup.tsx
Normal file
10
nkebao/src/pages/workspace/auto-group/AutoGroup.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const AutoGroup: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="自动分组" showAddButton addButtonText="新建分组" />
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoGroup;
|
||||
8
nkebao/src/pages/workspace/auto-group/Detail.tsx
Normal file
8
nkebao/src/pages/workspace/auto-group/Detail.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const AutoGroupDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="自动分组详情" />;
|
||||
};
|
||||
|
||||
export default AutoGroupDetail;
|
||||
38
nkebao/src/pages/workspace/auto-like/AutoLike.tsx
Normal file
38
nkebao/src/pages/workspace/auto-like/AutoLike.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { NavBar, Button } from "antd-mobile";
|
||||
import { AddOutline } from "antd-mobile-icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const AutoLike: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
backArrow
|
||||
style={{ background: "#fff" }}
|
||||
onBack={() => window.history.back()}
|
||||
left={
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
自动点赞
|
||||
</div>
|
||||
}
|
||||
right={
|
||||
<Button size="small" color="primary">
|
||||
<AddOutline />
|
||||
<span style={{ marginLeft: 4, fontSize: 12 }}>新建任务</span>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>自动点赞页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutoLike;
|
||||
8
nkebao/src/pages/workspace/auto-like/AutoLikeDetail.tsx
Normal file
8
nkebao/src/pages/workspace/auto-like/AutoLikeDetail.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const AutoLikeDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="自动点赞详情" />;
|
||||
};
|
||||
|
||||
export default AutoLikeDetail;
|
||||
30
nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx
Normal file
30
nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { NavBar } from "antd-mobile";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import MeauMobile from "@/components/MeauMobile/MeauMoible";
|
||||
|
||||
const NewAutoLike: React.FC = () => {
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<NavBar
|
||||
backArrow
|
||||
style={{ background: "#fff" }}
|
||||
onBack={() => window.history.back()}
|
||||
>
|
||||
<div style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
||||
新建自动点赞
|
||||
</div>
|
||||
</NavBar>
|
||||
}
|
||||
footer={<MeauMobile />}
|
||||
>
|
||||
<div style={{ padding: 20, textAlign: "center", color: "#666" }}>
|
||||
<h3>新建自动点赞页面</h3>
|
||||
<p>此页面正在开发中...</p>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewAutoLike;
|
||||
10
nkebao/src/pages/workspace/group-push/GroupPush.tsx
Normal file
10
nkebao/src/pages/workspace/group-push/GroupPush.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const GroupPush: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="群发推送" showAddButton addButtonText="新建推送" />
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupPush;
|
||||
8
nkebao/src/pages/workspace/group-push/new.tsx
Normal file
8
nkebao/src/pages/workspace/group-push/new.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const NewGroupPush: React.FC = () => {
|
||||
return <PlaceholderPage title="新建群发推送" />;
|
||||
};
|
||||
|
||||
export default NewGroupPush;
|
||||
8
nkebao/src/pages/workspace/moments-sync/Detail.tsx
Normal file
8
nkebao/src/pages/workspace/moments-sync/Detail.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const MomentsSyncDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="朋友圈同步详情" />;
|
||||
};
|
||||
|
||||
export default MomentsSyncDetail;
|
||||
14
nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx
Normal file
14
nkebao/src/pages/workspace/moments-sync/MomentsSync.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const MomentsSync: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage
|
||||
title="朋友圈同步"
|
||||
showAddButton
|
||||
addButtonText="新建同步"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default MomentsSync;
|
||||
8
nkebao/src/pages/workspace/moments-sync/new.tsx
Normal file
8
nkebao/src/pages/workspace/moments-sync/new.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const NewMomentsSync: React.FC = () => {
|
||||
return <PlaceholderPage title="新建朋友圈同步" />;
|
||||
};
|
||||
|
||||
export default NewMomentsSync;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const TrafficDistributionDetail: React.FC = () => {
|
||||
return <PlaceholderPage title="流量分发详情" />;
|
||||
};
|
||||
|
||||
export default TrafficDistributionDetail;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const NewDistribution: React.FC = () => {
|
||||
return <PlaceholderPage title="新建流量分发" />;
|
||||
};
|
||||
|
||||
export default NewDistribution;
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const TrafficDistribution: React.FC = () => {
|
||||
return (
|
||||
<PlaceholderPage title="流量分发" showAddButton addButtonText="新建分发" />
|
||||
);
|
||||
};
|
||||
|
||||
export default TrafficDistribution;
|
||||
171
nkebao/src/router/config.ts
Normal file
171
nkebao/src/router/config.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
// 路由配置类型定义
|
||||
export interface RouteConfig {
|
||||
path: string;
|
||||
element: React.ReactNode;
|
||||
auth: boolean;
|
||||
requiredRole?: string;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
children?: RouteConfig[];
|
||||
}
|
||||
|
||||
// 路由分组配置
|
||||
export const routeGroups = {
|
||||
// 基础路由
|
||||
basic: {
|
||||
name: "基础功能",
|
||||
routes: ["/", "/login", "/scene", "/work", "/mine"],
|
||||
},
|
||||
|
||||
// 设备管理
|
||||
devices: {
|
||||
name: "设备管理",
|
||||
routes: ["/devices", "/devices/:id"],
|
||||
},
|
||||
|
||||
// 微信号管理
|
||||
wechatAccounts: {
|
||||
name: "微信号管理",
|
||||
routes: ["/wechat-accounts", "/wechat-accounts/:id"],
|
||||
},
|
||||
|
||||
// 工作台
|
||||
workspace: {
|
||||
name: "工作台",
|
||||
routes: [
|
||||
"/workspace",
|
||||
"/workspace/auto-like",
|
||||
"/workspace/auto-like/new",
|
||||
"/workspace/auto-like/:id",
|
||||
"/workspace/auto-like/:id/edit",
|
||||
"/workspace/auto-group",
|
||||
"/workspace/auto-group/:id",
|
||||
"/workspace/group-push",
|
||||
"/workspace/group-push/new",
|
||||
"/workspace/group-push/:id",
|
||||
"/workspace/group-push/:id/edit",
|
||||
"/workspace/moments-sync",
|
||||
"/workspace/moments-sync/new",
|
||||
"/workspace/moments-sync/:id",
|
||||
"/workspace/moments-sync/edit/:id",
|
||||
"/workspace/ai-assistant",
|
||||
"/workspace/traffic-distribution",
|
||||
"/workspace/traffic-distribution/new",
|
||||
"/workspace/traffic-distribution/edit/:id",
|
||||
"/workspace/traffic-distribution/:id",
|
||||
],
|
||||
},
|
||||
|
||||
// 场景管理
|
||||
scenarios: {
|
||||
name: "场景管理",
|
||||
routes: [
|
||||
"/scenarios",
|
||||
"/scenarios/new",
|
||||
"/scenarios/new/:scenarioId",
|
||||
"/scenarios/edit/:planId",
|
||||
"/scenarios/list/:scenarioId/:scenarioName",
|
||||
],
|
||||
},
|
||||
|
||||
// 内容管理
|
||||
content: {
|
||||
name: "内容管理",
|
||||
routes: [
|
||||
"/content",
|
||||
"/content/new",
|
||||
"/content/edit/:id",
|
||||
"/content/materials/:id",
|
||||
"/content/materials/new/:id",
|
||||
"/content/materials/edit/:id/:materialId",
|
||||
],
|
||||
},
|
||||
|
||||
// 流量池
|
||||
trafficPool: {
|
||||
name: "流量池",
|
||||
routes: ["/traffic-pool", "/traffic-pool/:id"],
|
||||
},
|
||||
|
||||
// 其他功能
|
||||
other: {
|
||||
name: "其他功能",
|
||||
routes: [
|
||||
"/profile",
|
||||
"/plans",
|
||||
"/plans/:planId",
|
||||
"/orders",
|
||||
"/contact-import",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// 路由权限配置
|
||||
export const routePermissions = {
|
||||
// 管理员权限
|
||||
admin: Object.values(routeGroups).flatMap(group => group.routes),
|
||||
|
||||
// 普通用户权限
|
||||
user: [
|
||||
"/",
|
||||
"/login",
|
||||
"/scene",
|
||||
"/work",
|
||||
"/mine",
|
||||
"/devices",
|
||||
"/devices/:id",
|
||||
"/wechat-accounts",
|
||||
"/wechat-accounts/:id",
|
||||
"/workspace",
|
||||
"/scenarios",
|
||||
"/content",
|
||||
"/traffic-pool",
|
||||
"/traffic-pool/:id",
|
||||
"/profile",
|
||||
"/plans",
|
||||
"/plans/:planId",
|
||||
"/orders",
|
||||
"/contact-import",
|
||||
],
|
||||
|
||||
// 访客权限
|
||||
guest: ["/", "/login"],
|
||||
};
|
||||
|
||||
// 路由标题映射
|
||||
export const routeTitles: Record<string, string> = {
|
||||
"/": "首页",
|
||||
"/login": "登录",
|
||||
"/scene": "场景获客",
|
||||
"/work": "工作台",
|
||||
"/mine": "我的",
|
||||
"/devices": "设备管理",
|
||||
"/wechat-accounts": "微信号管理",
|
||||
"/workspace": "工作台",
|
||||
"/scenarios": "场景管理",
|
||||
"/content": "内容管理",
|
||||
"/traffic-pool": "流量池",
|
||||
"/profile": "个人中心",
|
||||
"/plans": "计划管理",
|
||||
"/orders": "订单管理",
|
||||
"/contact-import": "联系人导入",
|
||||
};
|
||||
|
||||
// 获取路由标题
|
||||
export const getRouteTitle = (path: string): string => {
|
||||
return routeTitles[path] || "页面";
|
||||
};
|
||||
|
||||
// 检查路由权限
|
||||
export const checkRoutePermission = (
|
||||
path: string,
|
||||
userRole: string = "user"
|
||||
): boolean => {
|
||||
const allowedRoutes = routePermissions[userRole as keyof typeof routePermissions] || [];
|
||||
return allowedRoutes.some(route => {
|
||||
// 简单的路径匹配,支持动态参数
|
||||
const routePattern = route.replace(/:[^/]+/g, "[^/]+");
|
||||
const regex = new RegExp(`^${routePattern}$`);
|
||||
return regex.test(path);
|
||||
});
|
||||
};
|
||||
11
nkebao/src/router/module/auth.tsx
Normal file
11
nkebao/src/router/module/auth.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import Login from "@/pages/login/login";
|
||||
|
||||
const authRoutes = [
|
||||
{
|
||||
path: "/login",
|
||||
element: <Login />,
|
||||
auth: false, // 不需要权限
|
||||
},
|
||||
];
|
||||
|
||||
export default authRoutes;
|
||||
39
nkebao/src/router/module/content.tsx
Normal file
39
nkebao/src/router/module/content.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import Content from "@/pages/content/Content";
|
||||
import NewContent from "@/pages/content/NewContent";
|
||||
import Materials from "@/pages/content/materials/List";
|
||||
import MaterialsNew from "@/pages/content/materials/New";
|
||||
|
||||
const contentRoutes = [
|
||||
{
|
||||
path: "/content",
|
||||
element: <Content />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/content/new",
|
||||
element: <NewContent />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/content/edit/:id",
|
||||
element: <NewContent />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/content/materials/:id",
|
||||
element: <Materials />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/content/materials/new/:id",
|
||||
element: <MaterialsNew />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/content/materials/edit/:id/:materialId",
|
||||
element: <MaterialsNew />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default contentRoutes;
|
||||
17
nkebao/src/router/module/devices.tsx
Normal file
17
nkebao/src/router/module/devices.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import Devices from "@/pages/devices/Devices";
|
||||
import DeviceDetail from "@/pages/devices/DeviceDetail";
|
||||
|
||||
const deviceRoutes = [
|
||||
{
|
||||
path: "/devices",
|
||||
element: <Devices />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/devices/:id",
|
||||
element: <DeviceDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default deviceRoutes;
|
||||
@@ -1,20 +1,15 @@
|
||||
import Home from "@/pages/home/index";
|
||||
import Login from "@/pages/login/login";
|
||||
import Scene from "@/pages/scene/index";
|
||||
import Work from "@/pages/work/index";
|
||||
import Mine from "@/pages/mine/index";
|
||||
import Workspace from "@/pages/workspace/main";
|
||||
|
||||
const routes = [
|
||||
// 基础路由
|
||||
{
|
||||
path: "/",
|
||||
element: <Home />,
|
||||
auth: true, // 需要登录
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: <Login />,
|
||||
auth: false, // 不需要权限
|
||||
},
|
||||
{
|
||||
path: "/scene",
|
||||
element: <Scene />,
|
||||
@@ -22,7 +17,7 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: "/work",
|
||||
element: <Work />,
|
||||
element: <Workspace />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
|
||||
40
nkebao/src/router/module/other.tsx
Normal file
40
nkebao/src/router/module/other.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Profile from "@/pages/profile/Profile";
|
||||
import Plans from "@/pages/plans/Plans";
|
||||
import PlanDetail from "@/pages/plans/PlanDetail";
|
||||
import Orders from "@/pages/orders/Orders";
|
||||
import ContactImport from "@/pages/contact-import/ContactImport";
|
||||
|
||||
const otherRoutes = [
|
||||
{
|
||||
path: "/mine",
|
||||
element: <Profile />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/profile",
|
||||
element: <Profile />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/plans",
|
||||
element: <Plans />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/plans/:planId",
|
||||
element: <PlanDetail />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/orders",
|
||||
element: <Orders />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/contact-import",
|
||||
element: <ContactImport />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default otherRoutes;
|
||||
38
nkebao/src/router/module/scenarios.tsx
Normal file
38
nkebao/src/router/module/scenarios.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import Scenarios from "@/pages/scenarios/Scenarios";
|
||||
import NewPlan from "@/pages/scenarios/new/page";
|
||||
import ScenarioList from "@/pages/scenarios/ScenarioList";
|
||||
|
||||
const scenarioRoutes = [
|
||||
{
|
||||
path: "/scene",
|
||||
element: <Scenarios />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios",
|
||||
element: <Scenarios />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/new",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/new/:scenarioId",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/edit/:planId",
|
||||
element: <NewPlan />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/scenarios/list/:scenarioId/:scenarioName",
|
||||
element: <ScenarioList />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default scenarioRoutes;
|
||||
17
nkebao/src/router/module/traffic-pool.tsx
Normal file
17
nkebao/src/router/module/traffic-pool.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import TrafficPool from "@/pages/traffic-pool/TrafficPool";
|
||||
import TrafficPoolDetail from "@/pages/traffic-pool/TrafficPoolDetail";
|
||||
|
||||
const trafficPoolRoutes = [
|
||||
{
|
||||
path: "/traffic-pool",
|
||||
element: <TrafficPool />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/traffic-pool/:id",
|
||||
element: <TrafficPoolDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default trafficPoolRoutes;
|
||||
17
nkebao/src/router/module/wechat-accounts.tsx
Normal file
17
nkebao/src/router/module/wechat-accounts.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import WechatAccounts from "@/pages/wechat-accounts/WechatAccounts";
|
||||
import WechatAccountDetail from "@/pages/wechat-accounts/WechatAccountDetail";
|
||||
|
||||
const wechatAccountRoutes = [
|
||||
{
|
||||
path: "/wechat-accounts",
|
||||
element: <WechatAccounts />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/wechat-accounts/:id",
|
||||
element: <WechatAccountDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default wechatAccountRoutes;
|
||||
145
nkebao/src/router/module/workspace.tsx
Normal file
145
nkebao/src/router/module/workspace.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import Workspace from "@/pages/workspace/Workspace";
|
||||
import AutoLike from "@/pages/workspace/auto-like/AutoLike";
|
||||
import NewAutoLike from "@/pages/workspace/auto-like/NewAutoLike";
|
||||
import AutoLikeDetail from "@/pages/workspace/auto-like/AutoLikeDetail";
|
||||
import AutoGroup from "@/pages/workspace/auto-group/AutoGroup";
|
||||
import AutoGroupDetail from "@/pages/workspace/auto-group/Detail";
|
||||
import GroupPush from "@/pages/workspace/group-push/GroupPush";
|
||||
import NewGroupPush from "@/pages/workspace/group-push/new";
|
||||
import MomentsSync from "@/pages/workspace/moments-sync/MomentsSync";
|
||||
import MomentsSyncDetail from "@/pages/workspace/moments-sync/Detail";
|
||||
import NewMomentsSync from "@/pages/workspace/moments-sync/new";
|
||||
import AIAssistant from "@/pages/workspace/ai-assistant/AIAssistant";
|
||||
import TrafficDistribution from "@/pages/workspace/traffic-distribution/TrafficDistribution";
|
||||
import TrafficDistributionDetail from "@/pages/workspace/traffic-distribution/Detail";
|
||||
import NewDistribution from "@/pages/workspace/traffic-distribution/NewDistribution";
|
||||
import PlaceholderPage from "@/components/PlaceholderPage";
|
||||
|
||||
const workspaceRoutes = [
|
||||
{
|
||||
path: "/workspace",
|
||||
element: <Workspace />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动点赞
|
||||
{
|
||||
path: "/workspace/auto-like",
|
||||
element: <AutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/new",
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/:id",
|
||||
element: <AutoLikeDetail />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-like/:id/edit",
|
||||
element: <NewAutoLike />,
|
||||
auth: true,
|
||||
},
|
||||
// 自动分组
|
||||
{
|
||||
path: "/workspace/auto-group",
|
||||
element: <AutoGroup />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/auto-group/:id",
|
||||
element: <AutoGroupDetail />,
|
||||
auth: true,
|
||||
},
|
||||
// 群发推送
|
||||
{
|
||||
path: "/workspace/group-push",
|
||||
element: <GroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/new",
|
||||
element: <NewGroupPush />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/:id",
|
||||
element: <PlaceholderPage title="群发推送详情" />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/group-push/:id/edit",
|
||||
element: <PlaceholderPage title="编辑群发推送" />,
|
||||
auth: true,
|
||||
},
|
||||
// 朋友圈同步
|
||||
{
|
||||
path: "/workspace/moments-sync",
|
||||
element: <MomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/moments-sync/new",
|
||||
element: <NewMomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/moments-sync/:id",
|
||||
element: <MomentsSyncDetail />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/moments-sync/edit/:id",
|
||||
element: <NewMomentsSync />,
|
||||
auth: true,
|
||||
},
|
||||
// AI助手
|
||||
{
|
||||
path: "/workspace/ai-assistant",
|
||||
element: <AIAssistant />,
|
||||
auth: true,
|
||||
},
|
||||
// AI数据分析
|
||||
{
|
||||
path: "/workspace/ai-analyzer",
|
||||
element: <PlaceholderPage title="AI数据分析" />,
|
||||
auth: true,
|
||||
},
|
||||
// AI策略优化
|
||||
{
|
||||
path: "/workspace/ai-strategy",
|
||||
element: <PlaceholderPage title="AI策略优化" />,
|
||||
auth: true,
|
||||
},
|
||||
// AI销售预测
|
||||
{
|
||||
path: "/workspace/ai-forecast",
|
||||
element: <PlaceholderPage title="AI销售预测" />,
|
||||
auth: true,
|
||||
},
|
||||
// 流量分发
|
||||
{
|
||||
path: "/workspace/traffic-distribution",
|
||||
element: <TrafficDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/new",
|
||||
element: <NewDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/edit/:id",
|
||||
element: <NewDistribution />,
|
||||
auth: true,
|
||||
},
|
||||
{
|
||||
path: "/workspace/traffic-distribution/:id",
|
||||
element: <TrafficDistributionDetail />,
|
||||
auth: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default workspaceRoutes;
|
||||
@@ -122,4 +122,13 @@ input, textarea {
|
||||
/* 安卓部分 WebView 点击延迟优化 */
|
||||
body, input, textarea, select, button {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
}
|
||||
//导航左右结构的样式
|
||||
.nav-left {
|
||||
color: var(--primary-color);
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
.nav-right {
|
||||
font-size: 12px;
|
||||
}
|
||||
Reference in New Issue
Block a user