feat: 本次提交更新内容如下

梳理了icon ,增加了layout的loading
This commit is contained in:
笔记本里的永平
2025-07-19 16:10:26 +08:00
parent 4af615889b
commit b1938a76c0
31 changed files with 3181 additions and 908 deletions

274
nkebao/AI_ICON_GUIDE.md Normal file
View File

@@ -0,0 +1,274 @@
# AI图标使用指南
## 概述
本文档为AI助手提供标准化的图标选择和使用流程确保在代码生成时使用正确的图标包和图标名称。
## AI使用流程
### 1. 项目类型判断
首先判断项目类型:
- **PC端项目**: 使用 `@ant-design/icons`
- **移动端项目**: 使用 `antd-mobile-icons`
### 2. 图标查找流程
1. 根据功能需求确定图标类型
2. 在对应图标包中查找合适的图标
3. 如果图标不存在,查找替代方案
4. 使用正确的导入语法
### 3. 代码生成模板
#### PC端项目模板
```typescript
import {
// 导航类
HomeOutlined,
UserOutlined,
SettingOutlined,
// 操作类
PlusOutlined,
EditOutlined,
DeleteOutlined,
CopyOutlined,
SearchOutlined,
ReloadOutlined,
// 状态类
CheckOutlined,
CloseOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
InfoCircleOutlined,
LoadingOutlined,
// 方向类
UpOutlined,
DownOutlined,
LeftOutlined,
RightOutlined,
ArrowLeftOutlined,
// 其他
MessageOutlined,
CalendarOutlined,
ClockCircleOutlined,
PictureOutlined,
FileOutlined,
CameraOutlined,
QrcodeOutlined,
} from '@ant-design/icons';
```
#### 移动端项目模板
```typescript
import {
// 导航类
HomeOutline,
UserOutline,
SettingOutline,
// 操作类
AddOutline,
EditSOutline,
DeleteOutline,
CopyOutline,
SearchOutline,
RefreshOutline,
// 状态类
CheckOutline,
CloseOutline,
CheckCircleOutline,
CloseCircleOutline,
ExclamationCircleOutline,
InfoCircleOutline,
LoadingOutline,
// 方向类
UpOutline,
DownOutline,
LeftOutline,
RightOutline,
// 其他
MessageOutline,
CalendarOutline,
ClockCircleOutline,
PictureOutline,
FileOutline,
CameraOutline,
QrCodeOutline,
} from 'antd-mobile-icons';
```
## 功能到图标映射
### 基础功能映射
| 功能需求 | PC端图标 | 移动端图标 | 说明 |
|---------|---------|-----------|------|
| 添加/新建 | PlusOutlined | AddOutline | 通用添加功能 |
| 编辑/修改 | EditOutlined | EditSOutline | 编辑功能 |
| 删除/移除 | DeleteOutlined | DeleteOutline | 删除功能 |
| 复制/克隆 | CopyOutlined | CopyOutline | 复制功能 |
| 搜索/查找 | SearchOutlined | SearchOutline | 搜索功能 |
| 刷新/重新加载 | ReloadOutlined | RefreshOutline | 刷新功能 |
| 设置/配置 | SettingOutlined | SettingOutline | 设置功能 |
| 用户/个人 | UserOutlined | UserOutline | 用户相关 |
| 首页/主页 | HomeOutlined | HomeOutline | 首页导航 |
| 返回/后退 | ArrowLeftOutlined | LeftOutline | 返回功能 |
| 关闭/取消 | CloseOutlined | CloseOutline | 关闭功能 |
| 确认/确定 | CheckOutlined | CheckOutline | 确认功能 |
### 状态指示映射
| 状态需求 | PC端图标 | 移动端图标 | 说明 |
|---------|---------|-----------|------|
| 成功/完成 | CheckCircleOutlined | CheckCircleOutline | 成功状态 |
| 错误/失败 | CloseCircleOutlined | CloseCircleOutline | 错误状态 |
| 警告/注意 | ExclamationCircleOutlined | ExclamationCircleOutline | 警告状态 |
| 信息/提示 | InfoCircleOutlined | InfoCircleOutline | 信息提示 |
| 加载/等待 | LoadingOutlined | LoadingOutline | 加载状态 |
| 时间/等待 | ClockCircleOutlined | ClockCircleOutline | 时间相关 |
### 方向导航映射
| 方向需求 | PC端图标 | 移动端图标 | 说明 |
|---------|---------|-----------|------|
| 向上/上升 | UpOutlined | UpOutline | 向上方向 |
| 向下/下降 | DownOutlined | DownOutline | 向下方向 |
| 向左/后退 | LeftOutlined | LeftOutline | 向左方向 |
| 向右/前进 | RightOutlined | RightOutline | 向右方向 |
### 业务功能映射
| 业务需求 | PC端图标 | 移动端图标 | 说明 |
|---------|---------|-----------|------|
| 消息/通知 | MessageOutlined | MessageOutline | 消息功能 |
| 日历/日期 | CalendarOutlined | CalendarOutline | 日历功能 |
| 图片/照片 | PictureOutlined | PictureOutline | 图片功能 |
| 文件/文档 | FileOutlined | FileOutline | 文件功能 |
| 相机/拍照 | CameraOutlined | CameraOutline | 相机功能 |
| 二维码 | QrcodeOutlined | QrCodeOutline | 二维码功能 |
| 微信/社交 | WechatOutlined | WechatOutline | 微信功能 |
| 设备/手机 | MobileOutlined | MobileOutline | 设备功能 |
| 团队/群组 | TeamOutlined | TeamOutline | 团队功能 |
| 订单/购物 | ShoppingOutlined | ShoppingOutline | 订单功能 |
| 支付/钱包 | PayCircleOutlined | PayCircleOutline | 支付功能 |
## 特殊替换规则
### 移动端不存在的图标替换
| 原需求 | 替换方案 | 说明 |
|--------|----------|------|
| RiseOutlined | UpOutline | 上升趋势 |
| ThumbsUpOutlined | LikeOutline | 点赞功能 |
| ShareAltOutlined | LinkOutline | 分享功能 |
| BarChartOutlined | PieOutline | 图表功能 |
| LineChartOutlined | PieOutline | 图表功能 |
| UserAddOutlined | UserOutline | 用户添加 |
| Progress | 自定义div | 进度条组件 |
### PC端不存在的图标替换
| 原需求 | 替换方案 | 说明 |
|--------|----------|------|
| AntOutline | HomeOutlined | 蚂蚁图标 |
| AppOutline | AppstoreOutlined | 应用图标 |
## AI代码生成示例
### 场景1: 移动端列表页面
```typescript
// AI应该生成的代码
import {
AddOutline, // 添加按钮
EditSOutline, // 编辑按钮
DeleteOutline, // 删除按钮
CopyOutline, // 复制按钮
SearchOutline, // 搜索框
RefreshOutline, // 刷新按钮
UserOutline, // 用户信息
CalendarOutline, // 时间信息
UpOutline, // 上升趋势替换RiseOutlined
} from 'antd-mobile-icons';
// 使用示例
<Button>
<AddOutline /> 新建
</Button>
<Button>
<EditSOutline /> 编辑
</Button>
<Button color="danger">
<DeleteOutline /> 删除
</Button>
```
### 场景2: PC端管理页面
```typescript
// AI应该生成的代码
import {
PlusOutlined, // 添加按钮
EditOutlined, // 编辑按钮
DeleteOutlined, // 删除按钮
CopyOutlined, // 复制按钮
SearchOutlined, // 搜索框
ReloadOutlined, // 刷新按钮
UserOutlined, // 用户信息
CalendarOutlined, // 时间信息
RiseOutlined, // 上升趋势PC端存在
} from '@ant-design/icons';
// 使用示例
<Button icon={<PlusOutlined />}>新建</Button>
<Button icon={<EditOutlined />}>编辑</Button>
<Button icon={<DeleteOutlined />} danger>删除</Button>
```
## 错误检测和修正
### 常见错误模式
1. **混用图标包**: 同时导入PC端和移动端图标
2. **使用不存在的图标**: 在移动端使用PC端特有的图标
3. **命名错误**: 图标名称大小写错误
4. **导入路径错误**: 使用错误的包名
### 修正策略
1. **统一图标包**: 根据项目类型选择单一图标包
2. **查找替代**: 使用对照表查找替代图标
3. **验证存在**: 确保图标在目标包中存在
4. **测试验证**: 在代码中测试图标是否正常显示
## AI使用检查清单
### 代码生成前
- [ ] 确认项目类型PC端/移动端)
- [ ] 选择对应的图标包
- [ ] 根据功能需求选择合适图标
- [ ] 检查图标是否存在
### 代码生成中
- [ ] 使用正确的导入语法
- [ ] 图标名称大小写正确
- [ ] 避免混用不同包的图标
- [ ] 为不存在的图标提供替代方案
### 代码生成后
- [ ] 验证图标导入正确
- [ ] 检查图标使用语法
- [ ] 确保样式设置合理
- [ ] 提供使用示例
## 更新和维护
- 定期更新图标对照表
- 记录新发现的图标差异
- 更新替换规则
- 优化AI使用流程
## 注意事项
1. **优先使用语义化图标**: 选择最能表达功能的图标
2. **保持一致性**: 在同一项目中保持图标风格一致
3. **考虑可访问性**: 为图标添加适当的aria-label
4. **性能优化**: 按需导入图标,避免全量导入
5. **版本兼容**: 注意图标包版本与UI框架版本的兼容性

View File

@@ -1,269 +0,0 @@
# 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` - 旅行/任务

View File

@@ -0,0 +1,205 @@
# 详细图标对照表
## 基础图标对照
### 导航类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 首页 | HomeOutlined | HomeOutline | ✅ |
| 返回 | ArrowLeftOutlined | LeftOutline | ✅ |
| 菜单 | MenuOutlined | MenuOutline | ✅ |
| 设置 | SettingOutlined | SettingOutline | ✅ |
| 用户 | UserOutlined | UserOutline | ✅ |
| 个人中心 | UserOutlined | UserOutline | ✅ |
### 操作类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 添加 | PlusOutlined | AddOutline | ✅ |
| 编辑 | EditOutlined | EditSOutline | ✅ |
| 删除 | DeleteOutlined | DeleteOutline | ✅ |
| 复制 | CopyOutlined | CopyOutline | ✅ |
| 保存 | SaveOutlined | SaveOutline | ✅ |
| 刷新 | ReloadOutlined | RefreshOutline | ✅ |
| 搜索 | SearchOutlined | SearchOutline | ✅ |
| 关闭 | CloseOutlined | CloseOutline | ✅ |
| 确认 | CheckOutlined | CheckOutline | ✅ |
| 取消 | CloseOutlined | CloseOutline | ✅ |
### 状态类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 成功 | CheckCircleOutlined | CheckCircleOutline | ✅ |
| 错误 | CloseCircleOutlined | CloseCircleOutline | ✅ |
| 警告 | ExclamationCircleOutlined | ExclamationCircleOutline | ✅ |
| 信息 | InfoCircleOutlined | InfoCircleOutline | ✅ |
| 加载 | LoadingOutlined | LoadingOutline | ✅ |
| 等待 | ClockCircleOutlined | ClockCircleOutline | ✅ |
### 方向类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 向上 | UpOutlined | UpOutline | ✅ |
| 向下 | DownOutlined | DownOutline | ✅ |
| 向左 | LeftOutlined | LeftOutline | ✅ |
| 向右 | RightOutlined | RightOutline | ✅ |
| 向上圆形 | UpCircleOutlined | UpCircleOutline | ✅ |
| 向下圆形 | DownCircleOutlined | DownCircleOutline | ✅ |
### 通信类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 消息 | MessageOutlined | MessageOutline | ✅ |
| 邮件 | MailOutlined | MailOutline | ✅ |
| 电话 | PhoneOutlined | PhoneOutline | ✅ |
| 视频 | VideoCameraOutlined | VideoCameraOutline | ✅ |
| 语音 | AudioOutlined | AudioOutline | ✅ |
### 媒体类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 图片 | PictureOutlined | PictureOutline | ✅ |
| 文件 | FileOutlined | FileOutline | ✅ |
| 文件夹 | FolderOutlined | FolderOutline | ✅ |
| 相机 | CameraOutlined | CameraOutline | ✅ |
| 二维码 | QrcodeOutlined | QrCodeOutline | ✅ |
### 时间类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 日历 | CalendarOutlined | CalendarOutline | ✅ |
| 时钟 | ClockCircleOutlined | ClockCircleOutline | ✅ |
| 历史 | HistoryOutlined | HistoryOutline | ✅ |
### 数据类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 统计 | BarChartOutlined | BarChartOutline | ✅ |
| 饼图 | PieChartOutlined | PieChartOutline | ✅ |
| 折线图 | LineChartOutlined | LineChartOutline | ✅ |
| 表格 | TableOutlined | TableOutline | ✅ |
| 列表 | UnorderedListOutlined | UnorderedListOutline | ✅ |
## 特殊图标对照
### 业务相关
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 设备 | MobileOutlined | MobileOutline | ✅ |
| 微信 | WechatOutlined | WechatOutline | ✅ |
| 群组 | TeamOutlined | TeamOutline | ✅ |
| 客户 | UserAddOutlined | UserAddOutline | ❌ |
| 订单 | ShoppingOutlined | ShoppingOutline | ✅ |
| 支付 | PayCircleOutlined | PayCircleOutline | ✅ |
### 工具类
| 功能 | PC端 | 移动端 | 状态 |
|------|------|--------|------|
| 工具 | ToolOutlined | ToolOutline | ✅ |
| 配置 | SettingOutlined | SettingOutline | ✅ |
| 帮助 | QuestionCircleOutlined | QuestionCircleOutline | ✅ |
| 反馈 | MessageOutlined | MessageOutline | ✅ |
| 分享 | ShareAltOutlined | ShareOutline | ❌ |
## 不存在的图标替换方案
### PC端存在但移动端不存在的图标
| PC端图标 | 推荐替换 | 说明 |
|----------|----------|------|
| UserAddOutlined | UserOutline | 用户添加功能 |
| ShareAltOutlined | LinkOutline | 分享功能 |
| RiseOutlined | UpOutline | 上升趋势 |
| ThumbsUpOutlined | LikeOutline | 点赞功能 |
| BarChartOutlined | PieOutline | 图表功能 |
| LineChartOutlined | PieOutline | 图表功能 |
### 移动端存在但PC端不存在的图标
| 移动端图标 | 推荐替换 | 说明 |
|------------|----------|------|
| AntOutline | HomeOutlined | 蚂蚁图标 |
| AppOutline | AppstoreOutlined | 应用图标 |
## 使用规范
### 1. 导入规范
```typescript
// PC端项目
import {
HomeOutlined,
UserOutlined,
SettingOutlined,
} from '@ant-design/icons';
// 移动端项目
import {
HomeOutline,
UserOutline,
SettingOutline,
} from 'antd-mobile-icons';
```
### 2. 命名规范
- PC端使用 `Outlined` 后缀
- 移动端:使用 `Outline` 后缀
- 保持语义化命名
### 3. 使用建议
- 优先使用语义明确的图标
- 保持图标风格一致性
- 考虑图标在不同尺寸下的清晰度
- 为图标添加适当的aria-label
### 4. 错误处理
当图标不存在时:
1. 查找语义相近的图标
2. 使用通用图标如QuestionOutlined
3. 考虑使用文字替代
4. 创建自定义图标组件
## 项目中的实际应用
### 场景获客模块使用的图标
```typescript
// 移动端项目中的图标使用
import {
AddOutline, // 添加
UpOutline, // 上升趋势替换RiseOutline
UserOutline, // 用户
CalendarOutline, // 日历
CopyOutline, // 复制
DeleteOutline, // 删除
EditSOutline, // 编辑
SettingOutline, // 设置
SearchOutline, // 搜索
RefreshOutline, // 刷新
QrCodeOutline, // 二维码
} from 'antd-mobile-icons';
```
### 工作台模块使用的图标
```typescript
// 移动端项目中的图标使用
import {
LikeOutline, // 点赞替换ThumbsUpOutline
LinkOutline, // 链接替换ShareOutline
PieOutline, // 饼图替换BarChartOutline/LineChartOutline
UserOutline, // 用户
TeamOutline, // 团队
MessageOutline, // 消息
} from 'antd-mobile-icons';
```
## 更新和维护
1. **定期检查**: 定期检查新版本中新增的图标
2. **文档更新**: 及时更新图标对照表
3. **团队协作**: 团队成员共享图标使用规范
4. **代码审查**: 在代码审查中检查图标使用是否正确
## 注意事项
1. **包版本**: 确保图标包版本与UI框架版本兼容
2. **按需导入**: 避免全量导入图标,影响打包体积
3. **样式覆盖**: 可以通过CSS自定义图标样式
4. **无障碍**: 为图标添加适当的无障碍属性
5. **性能**: 大量使用图标时注意性能优化

View File

@@ -0,0 +1,230 @@
# PC端与移动端图标对照文档
## 概述
本文档记录了PC端@ant-design/icons和移动端antd-mobile-icons的图标名称对照以及正确的导入方式。
## 导入方式
### PC端图标 (@ant-design/icons)
```typescript
import {
HomeOutlined,
UserOutlined,
SettingOutlined,
// ... 其他图标
} from '@ant-design/icons';
```
### 移动端图标 (antd-mobile-icons)
```typescript
import {
AntOutline,
ArrowDownCircleOutline,
UserOutline,
// ... 其他图标
} from 'antd-mobile-icons';
```
## 图标对照表
### 常用图标对照
| 功能描述 | PC端图标 | 移动端图标 | 备注 |
|---------|---------|-----------|------|
| 首页 | HomeOutlined | HomeOutline | 完全对应 |
| 用户 | UserOutlined | UserOutline | 完全对应 |
| 设置 | SettingOutlined | SettingOutline | 完全对应 |
| 搜索 | SearchOutlined | SearchOutline | 完全对应 |
| 添加 | PlusOutlined | AddOutline | 完全对应 |
| 编辑 | EditOutlined | EditSOutline | 移动端略有不同 |
| 删除 | DeleteOutlined | DeleteOutline | 完全对应 |
| 复制 | CopyOutlined | CopyOutline | 完全对应 |
| 刷新 | ReloadOutlined | RefreshOutline | 完全对应 |
| 二维码 | QrcodeOutlined | QrCodeOutline | 完全对应 |
| 日历 | CalendarOutlined | CalendarOutline | 完全对应 |
| 时钟 | ClockCircleOutlined | ClockCircleOutline | 完全对应 |
| 箭头向上 | UpOutlined | UpOutline | 完全对应 |
| 箭头向下 | DownOutlined | DownOutline | 完全对应 |
| 箭头向左 | LeftOutlined | LeftOutline | 完全对应 |
| 箭头向右 | RightOutlined | RightOutline | 完全对应 |
| 返回 | ArrowLeftOutlined | LeftOutline | 移动端使用LeftOutline |
| 关闭 | CloseOutlined | CloseOutline | 完全对应 |
| 检查 | CheckOutlined | CheckOutline | 完全对应 |
| 警告 | ExclamationCircleOutlined | ExclamationCircleOutline | 完全对应 |
| 信息 | InfoCircleOutlined | InfoCircleOutline | 完全对应 |
| 成功 | CheckCircleOutlined | CheckCircleOutline | 完全对应 |
| 错误 | CloseCircleOutlined | CloseCircleOutline | 完全对应 |
### 方向性图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 向上 | UpOutlined | UpOutline |
| 向下 | DownOutlined | DownOutline |
| 向左 | LeftOutlined | LeftOutline |
| 向右 | RightOutlined | RightOutline |
| 向上圆形 | UpCircleOutlined | UpCircleOutline |
| 向下圆形 | DownCircleOutlined | DownCircleOutline |
| 向左圆形 | LeftCircleOutlined | LeftCircleOutline |
| 向右圆形 | RightCircleOutlined | RightCircleOutline |
### 编辑类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 编辑 | EditOutlined | EditSOutline |
| 删除 | DeleteOutlined | DeleteOutline |
| 复制 | CopyOutlined | CopyOutline |
| 剪切 | ScissorOutlined | ScissorOutline |
| 撤销 | UndoOutlined | UndoOutline |
| 重做 | RedoOutlined | RedoOutline |
| 保存 | SaveOutlined | SaveOutline |
### 通信类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 消息 | MessageOutlined | MessageOutline |
| 邮件 | MailOutlined | MailOutline |
| 电话 | PhoneOutlined | PhoneOutline |
| 视频通话 | VideoCameraOutlined | VideoCameraOutline |
| 语音 | AudioOutlined | AudioOutline |
### 媒体类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 图片 | PictureOutlined | PictureOutline |
| 视频 | VideoCameraOutlined | VideoCameraOutline |
| 音频 | AudioOutlined | AudioOutline |
| 文件 | FileOutlined | FileOutline |
| 文件夹 | FolderOutlined | FolderOutline |
| 相机 | CameraOutlined | CameraOutline |
### 导航类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 菜单 | MenuOutlined | MenuOutline |
| 汉堡菜单 | MenuFoldOutlined | MenuOutline |
| 展开菜单 | MenuUnfoldOutlined | MenuOutline |
| 面包屑 | BreadcrumbOutlined | BreadcrumbOutline |
| 分页 | PaginationOutlined | PaginationOutline |
### 数据展示类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 表格 | TableOutlined | TableOutline |
| 列表 | UnorderedListOutlined | UnorderedListOutline |
| 卡片 | CreditCardOutlined | CreditCardOutline |
| 统计 | BarChartOutlined | BarChartOutline |
| 饼图 | PieChartOutlined | PieChartOutline |
| 折线图 | LineChartOutlined | LineChartOutline |
| 仪表盘 | DashboardOutlined | DashboardOutline |
### 反馈类图标
| 功能描述 | PC端图标 | 移动端图标 |
|---------|---------|-----------|
| 成功 | CheckCircleOutlined | CheckCircleOutline |
| 错误 | CloseCircleOutlined | CloseCircleOutline |
| 警告 | ExclamationCircleOutlined | ExclamationCircleOutline |
| 信息 | InfoCircleOutlined | InfoCircleOutline |
| 加载 | LoadingOutlined | LoadingOutline |
| 等待 | ClockCircleOutlined | ClockCircleOutline |
## 使用建议
### 1. 项目类型判断
- **PC端项目**: 使用 `@ant-design/icons`
- **移动端项目**: 使用 `antd-mobile-icons`
### 2. 图标选择原则
- 优先选择语义化图标
- 保持图标风格一致性
- 考虑图标在不同尺寸下的清晰度
### 3. 常见错误避免
- 不要混用PC端和移动端图标
- 注意图标名称的大小写
- 确保图标在对应包中存在
### 4. 图标替换策略
当某个图标在目标包中不存在时:
1. 查找语义相近的图标
2. 使用通用图标如QuestionOutlined
3. 考虑使用文字替代
4. 创建自定义图标组件
## 实际项目中的使用示例
### PC端项目示例
```typescript
import {
HomeOutlined,
UserOutlined,
SettingOutlined,
SearchOutlined,
PlusOutlined,
EditOutlined,
DeleteOutlined,
CopyOutlined,
ReloadOutlined,
QrcodeOutlined,
CalendarOutlined,
ClockCircleOutlined,
UpOutlined,
DownOutlined,
LeftOutlined,
RightOutlined,
CloseOutlined,
CheckOutlined,
ExclamationCircleOutlined,
InfoCircleOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
} from '@ant-design/icons';
```
### 移动端项目示例
```typescript
import {
HomeOutline,
UserOutline,
SettingOutline,
SearchOutline,
AddOutline,
EditSOutline,
DeleteOutline,
CopyOutline,
RefreshOutline,
QrCodeOutline,
CalendarOutline,
ClockCircleOutline,
UpOutline,
DownOutline,
LeftOutline,
RightOutline,
CloseOutline,
CheckOutline,
ExclamationCircleOutline,
InfoCircleOutline,
CheckCircleOutline,
CloseCircleOutline,
} from 'antd-mobile-icons';
```
## 注意事项
1. **包依赖**: 确保项目中已安装对应的图标包
2. **版本兼容**: 注意图标包版本与UI框架版本的兼容性
3. **性能考虑**: 按需导入图标,避免全量导入
4. **样式覆盖**: 可以通过CSS自定义图标颜色和大小
5. **无障碍**: 为图标添加适当的aria-label属性
## 更新记录
- 2024-01-XX: 初始版本,包含常用图标对照
- 后续根据实际使用情况持续更新

View File

@@ -0,0 +1,271 @@
# 图标快速参考表
## 快速查找
### 🔍 按功能查找
| 功能 | PC端 | 移动端 | 导入方式 |
|------|------|--------|----------|
| **添加** | PlusOutlined | AddOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **编辑** | EditOutlined | EditSOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **删除** | DeleteOutlined | DeleteOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **复制** | CopyOutlined | CopyOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **搜索** | SearchOutlined | SearchOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **刷新** | ReloadOutlined | RefreshOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **设置** | SettingOutlined | SettingOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **用户** | UserOutlined | UserOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **首页** | HomeOutlined | HomeOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **返回** | ArrowLeftOutlined | LeftOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **关闭** | CloseOutlined | CloseOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **确认** | CheckOutlined | CheckOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **成功** | CheckCircleOutlined | CheckCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **错误** | CloseCircleOutlined | CloseCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **警告** | ExclamationCircleOutlined | ExclamationCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **信息** | InfoCircleOutlined | InfoCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **加载** | LoadingOutlined | LoadingOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **向上** | UpOutlined | UpOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **向下** | DownOutlined | DownOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **向左** | LeftOutlined | LeftOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **向右** | RightOutlined | RightOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **消息** | MessageOutlined | MessageOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **电话** | PhoneOutlined | PhoneOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **日历** | CalendarOutlined | CalendarOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **时钟** | ClockCircleOutlined | ClockCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **图片** | PictureOutlined | PictureOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **文件** | FileOutlined | FileOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **相机** | CameraOutlined | CameraOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **二维码** | QrcodeOutlined | QrCodeOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **微信** | WechatOutlined | WechatOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **设备** | MobileOutlined | MobileOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **团队** | TeamOutlined | TeamOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **订单** | ShoppingOutlined | ShoppingOutline | `@ant-design/icons` / `antd-mobile-icons` |
| **支付** | PayCircleOutlined | PayCircleOutline | `@ant-design/icons` / `antd-mobile-icons` |
### 🔄 常见替换
| 原图标 | 替换为 | 说明 |
|--------|--------|------|
| RiseOutlined | UpOutline | 上升趋势 |
| ThumbsUpOutlined | LikeOutline | 点赞功能 |
| ShareAltOutlined | LinkOutline | 分享功能 |
| BarChartOutlined | PieOutline | 图表功能 |
| LineChartOutlined | PieOutline | 图表功能 |
| UserAddOutlined | UserOutline | 用户添加 |
| SettingOutline | SettingOutline | 设置(移动端) |
## 导入模板
### PC端项目模板
```typescript
import {
HomeOutlined,
UserOutlined,
SettingOutlined,
SearchOutlined,
PlusOutlined,
EditOutlined,
DeleteOutlined,
CopyOutlined,
ReloadOutlined,
CloseOutlined,
CheckOutlined,
UpOutlined,
DownOutlined,
LeftOutlined,
RightOutlined,
MessageOutlined,
CalendarOutlined,
ClockCircleOutlined,
PictureOutlined,
FileOutlined,
CameraOutlined,
QrcodeOutlined,
WechatOutlined,
MobileOutlined,
TeamOutlined,
ShoppingOutlined,
PayCircleOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
InfoCircleOutlined,
LoadingOutlined,
} from '@ant-design/icons';
```
### 移动端项目模板
```typescript
import {
HomeOutline,
UserOutline,
SettingOutline,
SearchOutline,
AddOutline,
EditSOutline,
DeleteOutline,
CopyOutline,
RefreshOutline,
CloseOutline,
CheckOutline,
UpOutline,
DownOutline,
LeftOutline,
RightOutline,
MessageOutline,
CalendarOutline,
ClockCircleOutline,
PictureOutline,
FileOutline,
CameraOutline,
QrCodeOutline,
WechatOutline,
MobileOutline,
TeamOutline,
ShoppingOutline,
PayCircleOutline,
CheckCircleOutline,
CloseCircleOutline,
ExclamationCircleOutline,
InfoCircleOutline,
LoadingOutline,
} from 'antd-mobile-icons';
```
## 使用示例
### 基础使用
```typescript
// PC端
import { HomeOutlined, UserOutlined } from '@ant-design/icons';
<HomeOutlined style={{ fontSize: 16, color: '#1890ff' }} />
<UserOutlined style={{ fontSize: 16, color: '#52c41a' }} />
// 移动端
import { HomeOutline, UserOutline } from 'antd-mobile-icons';
<HomeOutline style={{ fontSize: 16, color: '#1890ff' }} />
<UserOutline style={{ fontSize: 16, color: '#52c41a' }} />
```
### 按钮中使用
```typescript
// PC端
import { PlusOutlined, EditOutlined } from '@ant-design/icons';
<Button icon={<PlusOutlined />}>添加</Button>
<Button icon={<EditOutlined />}>编辑</Button>
// 移动端
import { AddOutline, EditSOutline } from 'antd-mobile-icons';
<Button>
<AddOutline /> 添加
</Button>
<Button>
<EditSOutline /> 编辑
</Button>
```
### 列表中使用
```typescript
// PC端
import { DeleteOutlined, CopyOutlined } from '@ant-design/icons';
<Button icon={<DeleteOutlined />} danger>删除</Button>
<Button icon={<CopyOutlined />}>复制</Button>
// 移动端
import { DeleteOutline, CopyOutline } from 'antd-mobile-icons';
<Button color="danger">
<DeleteOutline /> 删除
</Button>
<Button>
<CopyOutline /> 复制
</Button>
```
## 常见错误
### ❌ 错误示例
```typescript
// 错误混用PC端和移动端图标
import { HomeOutlined } from '@ant-design/icons'; // PC端
import { UserOutline } from 'antd-mobile-icons'; // 移动端
// 错误:使用不存在的图标
import { RiseOutlined } from 'antd-mobile-icons'; // 不存在
import { UserAddOutline } from 'antd-mobile-icons'; // 不存在
```
### ✅ 正确示例
```typescript
// 正确:统一使用移动端图标
import {
HomeOutline,
UserOutline,
UpOutline, // 替换RiseOutlined
UserOutline // 替换UserAddOutline
} from 'antd-mobile-icons';
// 正确统一使用PC端图标
import {
HomeOutlined,
UserOutlined,
RiseOutlined, // PC端存在
UserAddOutlined // PC端存在
} from '@ant-design/icons';
```
## 快速检查清单
### 开发前检查
- [ ] 确认项目类型PC端/移动端)
- [ ] 选择对应的图标包
- [ ] 检查图标是否存在
- [ ] 准备替换方案
### 开发中检查
- [ ] 使用正确的导入方式
- [ ] 图标名称大小写正确
- [ ] 避免混用不同包的图标
- [ ] 为图标添加适当的样式
### 开发后检查
- [ ] 图标显示正常
- [ ] 样式符合设计要求
- [ ] 无障碍属性完整
- [ ] 性能影响最小
## 紧急替换方案
当遇到图标不存在时,使用以下通用图标:
```typescript
// 移动端通用图标
import {
QuestionCircleOutline, // 通用问号
AppOutline, // 通用应用
ToolOutline, // 通用工具
SettingOutline, // 通用设置
UserOutline, // 通用用户
} from 'antd-mobile-icons';
// PC端通用图标
import {
QuestionCircleOutlined, // 通用问号
AppstoreOutlined, // 通用应用
ToolOutlined, // 通用工具
SettingOutlined, // 通用设置
UserOutlined, // 通用用户
} from '@ant-design/icons';
```
## 更新日志
- 2024-01-XX: 初始版本
- 添加常用图标对照
- 添加错误示例和正确示例
- 添加快速检查清单
- 添加紧急替换方案

View File

@@ -1,44 +0,0 @@
# Nkebao Base
一个现代化的 React + TypeScript 前端项目模板,适用于中后台管理系统、移动端 H5 等场景。
集成了 Vite 极速构建、Zustand 轻量状态管理、模块化路由与权限拦截、axios 请求封装、移动端 rem 适配等主流工程能力。
---
## 启动方式
```bash
yarn install
yarn dev
```
---
## 技术栈
- **React 18**:主流前端框架
- **TypeScript**:类型安全开发
- **Vite**:极速开发与构建工具
- **axios**:请求库,统一封装
- **sass**CSS 预处理
- **React Router v6**:路由管理,支持模块化与权限拦截
- **antd / antd-mobile**:桌面端与移动端 UI 组件库
- **postcss-pxtorem**rem 适配,移动端友好
- **Zustand**:轻量状态管理,支持持久化
- **ESLint + Prettier**:代码规范与自动格式化
- **路径别名 @**:简化 import
---
## 目录结构
```
src/
api/ # axios 封装与接口模块
pages/ # 页面组件
styles/ # 全局样式
App.tsx # 入口组件
main.tsx # 入口文件
public/
index.html # HTML 模板
```

View File

@@ -1,140 +0,0 @@
# 路由迁移总结
## 📋 迁移概述
已成功将旧项目 @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. 添加路由守卫
## 📝 注意事项
- 所有页面目前都是占位页面,需要根据业务需求逐步实现
- 路由权限控制需要根据实际业务逻辑调整
- 建议按照模块优先级逐步开发页面功能

View File

@@ -1,159 +0,0 @@
# 工作台页面迁移总结
## 📋 迁移概述
已成功将旧项目 @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. **测试完善**
- 单元测试
- 集成测试
- 端到端测试
- 性能测试
## 📝 注意事项
- 所有功能页面目前使用占位组件
- 需要根据实际业务需求调整数据展示
- 建议按照用户使用频率优先开发核心功能
- 注意保持与整体设计风格的一致性

View File

@@ -1,5 +1,7 @@
import React from "react";
import { SpinLoading } from "antd-mobile";
import styles from "./layout.module.scss";
interface LayoutProps {
loading?: boolean;
children?: React.ReactNode;
@@ -7,11 +9,25 @@ interface LayoutProps {
footer?: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children, header, footer }) => {
const Layout: React.FC<LayoutProps> = ({
children,
header,
footer,
loading = false,
}) => {
return (
<div className={styles.container}>
{header && <header>{header}</header>}
<main>{children}</main>
<main>
{loading ? (
<div className={styles.loadingContainer}>
<SpinLoading color="primary" style={{ fontSize: 32 }} />
<div className={styles.loadingText}>...</div>
</div>
) : (
children
)}
</main>
{footer && <footer>{footer}</footer>}
</div>
);

View File

@@ -8,4 +8,21 @@
.container main {
flex: 1;
overflow: auto;
}
.loadingContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 300px;
background: rgba(255, 255, 255, 0.8);
}
.loadingText {
margin-top: 16px;
color: #666;
font-size: 14px;
text-align: center;
}

View File

@@ -1,81 +1,81 @@
import React, { useState, useEffect } from "react";
import { TabBar } from "antd-mobile";
import {
AppOutline,
ShopbagOutline,
PieOutline,
UserOutline,
} from "antd-mobile-icons";
import { useLocation, useNavigate } from "react-router-dom";
const tabs = [
{
key: "home",
title: "首页",
icon: <AppOutline />,
path: "/",
},
{
key: "scene",
title: "场景获客",
icon: <ShopbagOutline />,
path: "/scene",
},
{
key: "work",
title: "工作台",
icon: <PieOutline />,
path: "/workspace",
},
{
key: "mine",
title: "我的",
icon: <UserOutline />,
path: "/mine",
},
];
// 需要展示菜单的路由白名单(可根据实际业务调整)
const menuPaths = ["/", "/scene", "/workspace", "/mine"];
const MeauMobile: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const [activeKey, setActiveKey] = useState("home");
// 根据当前路由自动设置 activeKey支持嵌套路由
useEffect(() => {
const found = tabs.find((tab) =>
tab.path === "/"
? location.pathname === "/"
: location.pathname.startsWith(tab.path)
);
if (found) setActiveKey(found.key);
}, [location.pathname]);
// 判断当前路由是否需要展示菜单
const showMenu = menuPaths.some((path) =>
path === "/"
? location.pathname === "/"
: location.pathname.startsWith(path)
);
if (!showMenu) return null;
return (
<TabBar
style={{ background: "#fff" }}
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
const tab = tabs.find((t) => t.key === key);
if (tab && tab.path) navigate(tab.path);
}}
>
{tabs.map((item) => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
);
};
export default MeauMobile;
import React, { useState, useEffect } from "react";
import { TabBar } from "antd-mobile";
import {
AppOutline,
ShopbagOutline,
PieOutline,
UserOutline,
} from "antd-mobile-icons";
import { useLocation, useNavigate } from "react-router-dom";
const tabs = [
{
key: "home",
title: "首页",
icon: <AppOutline />,
path: "/",
},
{
key: "scene",
title: "场景获客",
icon: <ShopbagOutline />,
path: "/scene",
},
{
key: "work",
title: "工作台",
icon: <PieOutline />,
path: "/workspace",
},
{
key: "mine",
title: "我的",
icon: <UserOutline />,
path: "/mine",
},
];
// 需要展示菜单的路由白名单(可根据实际业务调整)
const menuPaths = ["/", "/scene", "/workspace", "/mine"];
const MeauMobile: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const [activeKey, setActiveKey] = useState("home");
// 根据当前路由自动设置 activeKey支持嵌套路由
useEffect(() => {
const found = tabs.find((tab) =>
tab.path === "/"
? location.pathname === "/"
: location.pathname.startsWith(tab.path)
);
if (found) setActiveKey(found.key);
}, [location.pathname]);
// 判断当前路由是否需要展示菜单
const showMenu = menuPaths.some((path) =>
path === "/"
? location.pathname === "/"
: location.pathname.startsWith(path)
);
if (!showMenu) return null;
return (
<TabBar
style={{ background: "#fff" }}
activeKey={activeKey}
onChange={(key) => {
setActiveKey(key);
const tab = tabs.find((t) => t.key === key);
if (tab && tab.path) navigate(tab.path);
}}
>
{tabs.map((item) => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
);
};
export default MeauMobile;

View File

@@ -1,56 +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;
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { PlusOutlined } from "@ant-design/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">
<PlusOutlined />
<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;

View File

@@ -0,0 +1,250 @@
.scenario-list-page {
padding: 16px;
background: #f5f5f5;
min-height: 100vh;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.new-plan-btn {
font-size: 14px;
height: 32px;
padding: 0 12px;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
gap: 16px;
}
.loading-text {
color: #666;
font-size: 14px;
}
.scenario-card {
margin-bottom: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.scenario-info {
padding: 16px;
}
.scenario-name {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.scenario-desc {
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 16px;
}
.scenario-stats {
display: flex;
gap: 24px;
}
.stat-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
color: #666;
svg {
font-size: 16px;
color: #1890ff;
}
}
.search-bar {
display: flex;
gap: 12px;
margin-bottom: 16px;
align-items: center;
.adm-input {
flex: 1;
border-radius: 8px;
}
}
.refresh-btn {
height: 40px;
width: 40px;
padding: 0;
border-radius: 8px;
}
.plan-list {
background: transparent;
border-radius: 12px;
overflow: hidden;
}
.plan-item {
background: white;
margin-bottom: 12px;
border-radius: 12px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&:last-child {
margin-bottom: 0;
}
}
.plan-content {
flex: 1;
}
.plan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.plan-name {
font-size: 16px;
font-weight: 600;
color: #333;
flex: 1;
margin-right: 12px;
}
.plan-meta {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
color: #999;
}
.plan-actions {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.empty-text {
color: #999;
font-size: 14px;
margin-bottom: 20px;
}
.create-first-btn {
height: 40px;
padding: 0 24px;
border-radius: 20px;
}
.api-dialog {
background: white;
border-radius: 16px 16px 0 0;
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
}
.dialog-content {
flex: 1;
overflow-y: auto;
}
.api-item {
margin-bottom: 20px;
label {
display: block;
font-size: 14px;
color: #333;
margin-bottom: 8px;
font-weight: 500;
}
.adm-input {
border-radius: 8px;
}
}
.qr-dialog {
background: white;
border-radius: 16px;
padding: 20px;
width: 300px;
max-width: 90vw;
}
.qr-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
gap: 16px;
color: #666;
font-size: 14px;
}
.qr-image {
width: 100%;
max-width: 200px;
height: auto;
border-radius: 8px;
}
.qr-error {
text-align: center;
color: #ff4d4f;
font-size: 14px;
padding: 40px 20px;
}

View File

@@ -1,8 +1,618 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const ScenarioList: React.FC = () => {
return <PlaceholderPage title="场景列表" />;
};
export default ScenarioList;
import React, { useEffect, useState, useCallback } from "react";
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
import {
NavBar,
List,
Button,
Toast,
SpinLoading,
Dialog,
Input,
Popup,
Card,
Tag,
Space,
Divider,
} from "antd-mobile";
import {
PlusOutlined,
UserOutlined,
CalendarOutlined,
CopyOutlined,
DeleteOutlined,
EditOutlined,
SettingOutlined,
SearchOutlined,
ReloadOutlined,
QrcodeOutlined,
UpOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import {
getPlanList,
getPlanDetail,
copyPlan,
deletePlan,
getWxMinAppCode,
} from "./api";
import style from "./ScenarioList.module.scss";
interface Task {
id: string;
name: string;
status: number;
created_at: string;
updated_at: string;
enabled: boolean;
total_customers?: number;
today_customers?: number;
}
interface ScenarioData {
id: string;
name: string;
image: string;
description: string;
totalPlans: number;
totalCustomers: number;
todayCustomers: number;
growth: string;
}
interface ApiSettings {
apiKey: string;
webhookUrl: string;
taskId: string;
}
const ScenarioList: React.FC = () => {
const { scenarioId, scenarioName } = useParams<{
scenarioId: string;
scenarioName: string;
}>();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const [scenario, setScenario] = useState<ScenarioData | null>(null);
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [showApiDialog, setShowApiDialog] = useState(false);
const [currentApiSettings, setCurrentApiSettings] = useState<ApiSettings>({
apiKey: "",
webhookUrl: "",
taskId: "",
});
const [searchTerm, setSearchTerm] = useState("");
const [loadingTasks, setLoadingTasks] = useState(false);
const [showQrDialog, setShowQrDialog] = useState(false);
const [qrLoading, setQrLoading] = useState(false);
const [qrImg, setQrImg] = useState("");
// 获取渠道中文名称
const getChannelName = (channel: string) => {
const channelMap: Record<string, string> = {
douyin: "抖音直播获客",
kuaishou: "快手直播获客",
xiaohongshu: "小红书种草获客",
weibo: "微博话题获客",
haibao: "海报扫码获客",
phone: "电话号码获客",
gongzhonghao: "公众号引流获客",
weixinqun: "微信群裂变获客",
payment: "付款码获客",
api: "API接口获客",
};
return channelMap[channel] || `${channel}获客`;
};
// 获取场景描述
const getScenarioDescription = (channel: string) => {
const descriptions: Record<string, string> = {
douyin: "通过抖音平台进行精准获客,利用短视频内容吸引目标用户",
xiaohongshu: "利用小红书平台进行内容营销获客,通过优质内容建立品牌形象",
gongzhonghao: "通过微信公众号进行获客,建立私域流量池",
haibao: "通过海报分享进行获客,快速传播品牌信息",
phone: "通过电话营销进行获客,直接与客户沟通",
weixinqun: "通过微信群进行获客,利用社交裂变效应",
payment: "通过付款码进行获客,便捷的支付方式",
api: "通过API接口进行获客支持第三方系统集成",
};
return descriptions[channel] || "通过该平台进行获客";
};
useEffect(() => {
const fetchScenarioData = async () => {
if (!scenarioId) return;
setLoading(true);
setError("");
try {
// 获取计划列表
const response = await getPlanList(scenarioId, 1, 20);
// 设置计划列表
if (response && response.data && response.data.list) {
setTasks(response.data.list);
} else {
setTasks([]);
}
// 构建场景数据
const scenarioData: ScenarioData = {
id: scenarioId,
name: scenarioName || "",
image: "",
description: getScenarioDescription(scenarioId),
totalPlans: response?.data?.list?.length || 0,
totalCustomers: 0,
todayCustomers: 0,
growth: "",
};
setScenario(scenarioData);
} catch (error) {
console.error("获取场景数据失败:", error);
// 即使API失败也要创建基本的场景数据
const scenarioData: ScenarioData = {
id: scenarioId,
name: getScenarioName(),
image: "",
description: getScenarioDescription(scenarioId),
totalPlans: 0,
totalCustomers: 0,
todayCustomers: 0,
growth: "",
};
setScenario(scenarioData);
setTasks([]);
} finally {
setLoading(false);
}
};
fetchScenarioData();
}, [scenarioId]);
// 获取场景名称
const getScenarioName = useCallback(() => {
const urlName = searchParams.get("name");
if (urlName) {
return urlName;
}
return getChannelName(scenarioId || "");
}, [searchParams, scenarioId]);
// 更新场景数据中的名称
useEffect(() => {
setScenario((prev) =>
prev
? {
...prev,
name: (() => {
const urlName = searchParams.get("name");
if (urlName) return urlName;
return getChannelName(scenarioId || "");
})(),
}
: null
);
}, [searchParams, scenarioId]);
const handleCopyPlan = async (taskId: string) => {
const taskToCopy = tasks.find((task) => task.id === taskId);
if (!taskToCopy) return;
try {
const response = await copyPlan(taskId);
if (response && response.code === 200) {
Toast.show({
content: `已成功复制"${taskToCopy.name}"`,
position: "top",
});
// 刷新列表
handleRefresh();
} else {
Toast.show({
content: response?.msg || "复制失败",
position: "top",
});
}
} catch (error) {
Toast.show({
content: "复制失败,请重试",
position: "top",
});
}
};
const handleDeletePlan = async (taskId: string) => {
const taskToDelete = tasks.find((task) => task.id === taskId);
if (!taskToDelete) return;
const result = await Dialog.confirm({
content: `确定要删除"${taskToDelete.name}"吗?`,
confirmText: "删除",
cancelText: "取消",
});
if (result) {
try {
const response = await deletePlan(taskId);
if (response && response.code === 200) {
Toast.show({
content: "计划已删除",
position: "top",
});
// 刷新列表
handleRefresh();
} else {
Toast.show({
content: response?.msg || "删除失败",
position: "top",
});
}
} catch (error) {
Toast.show({
content: "删除失败,请重试",
position: "top",
});
}
}
};
const handleOpenApiSettings = async (taskId: string) => {
try {
const response = await getPlanDetail(taskId);
if (response && response.code === 200 && response.data) {
const detail = response.data;
setCurrentApiSettings({
apiKey: detail.apiKey || "",
webhookUrl: detail.webhookUrl || "",
taskId: taskId,
});
setShowApiDialog(true);
}
} catch (error) {
Toast.show({
content: "获取API设置失败",
position: "top",
});
}
};
const handleCopyApiUrl = (url: string) => {
navigator.clipboard.writeText(url);
Toast.show({
content: "API地址已复制到剪贴板",
position: "top",
});
};
const handleCreateNewPlan = () => {
navigate(`/scenarios/new/${scenarioId}`);
};
const handleShowQrCode = async (taskId: string) => {
setQrLoading(true);
setShowQrDialog(true);
try {
const response = await getWxMinAppCode(taskId);
if (response && response.code === 200 && response.data) {
setQrImg(response.data.qrCode || "");
} else {
Toast.show({
content: "获取二维码失败",
position: "top",
});
}
} catch (error) {
Toast.show({
content: "获取二维码失败",
position: "top",
});
} finally {
setQrLoading(false);
}
};
const getStatusColor = (status: number) => {
switch (status) {
case 1:
return "success";
case 0:
return "default";
case -1:
return "danger";
default:
return "default";
}
};
const getStatusText = (status: number) => {
switch (status) {
case 1:
return "运行中";
case 0:
return "已暂停";
case -1:
return "已停止";
default:
return "未知";
}
};
const handleRefresh = async () => {
setLoadingTasks(true);
try {
const response = await getPlanList(scenarioId!, 1, 20);
if (response && response.data && response.data.list) {
setTasks(response.data.list);
}
} catch (error) {
Toast.show({
content: "刷新失败",
position: "top",
});
} finally {
setLoadingTasks(false);
}
};
const filteredTasks = tasks.filter((task) =>
task.name.toLowerCase().includes(searchTerm.toLowerCase())
);
if (loading) {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<div className={style["nav-title"]}></div>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["loading"]}>
<SpinLoading color="primary" style={{ fontSize: 32 }} />
<div className={style["loading-text"]}>...</div>
</div>
</Layout>
);
}
return (
<Layout
header={
<NavBar
back={null}
style={{ background: "#fff" }}
left={<div className={style["nav-title"]}>{scenario?.name}</div>}
right={
<Button
size="small"
color="primary"
onClick={handleCreateNewPlan}
className={style["new-plan-btn"]}
>
<PlusOutlined />
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div className={style["scenario-list-page"]}>
{/* 场景信息卡片 */}
{scenario && (
<Card className={style["scenario-card"]}>
<div className={style["scenario-info"]}>
<div className={style["scenario-name"]}>{scenario.name}</div>
<div className={style["scenario-desc"]}>
{scenario.description}
</div>
<div className={style["scenario-stats"]}>
<div className={style["stat-item"]}>
<UserOutlined />
<span>: {scenario.totalPlans}</span>
</div>
<div className={style["stat-item"]}>
<CalendarOutlined />
<span>: {scenario.todayCustomers}</span>
</div>
</div>
</div>
</Card>
)}
{/* 搜索栏 */}
<div className={style["search-bar"]}>
<div className={style["search-input-wrapper"]}>
<SearchOutlined className={style["search-icon"]} />
<Input
placeholder="搜索计划名称"
value={searchTerm}
onChange={setSearchTerm}
clearable
/>
</div>
<Button
size="small"
onClick={handleRefresh}
loading={loadingTasks}
className={style["refresh-btn"]}
>
<ReloadOutlined />
</Button>
</div>
{/* 计划列表 */}
<List className={style["plan-list"]}>
{filteredTasks.length === 0 ? (
<div className={style["empty-state"]}>
<div className={style["empty-text"]}>
{searchTerm ? "没有找到匹配的计划" : "暂无计划"}
</div>
<Button
color="primary"
onClick={handleCreateNewPlan}
className={style["create-first-btn"]}
>
<PlusOutlined />
</Button>
</div>
) : (
filteredTasks.map((task) => (
<List.Item
key={task.id}
className={style["plan-item"]}
onClick={() => navigate(`/scenarios/edit/${task.id}`)}
>
<div className={style["plan-content"]}>
<div className={style["plan-header"]}>
<div className={style["plan-name"]}>{task.name}</div>
<Tag color={getStatusColor(task.status)}>
{getStatusText(task.status)}
</Tag>
</div>
<div className={style["plan-meta"]}>
<span>: {task.created_at}</span>
{task.total_customers !== undefined && (
<span>: {task.total_customers}</span>
)}
{task.today_customers !== undefined && (
<span>: {task.today_customers}</span>
)}
</div>
</div>
<div className={style["plan-actions"]}>
<Space>
<Button
size="mini"
onClick={(e) => {
e.stopPropagation();
handleCopyPlan(task.id);
}}
>
<CopyOutlined />
</Button>
<Button
size="mini"
onClick={(e) => {
e.stopPropagation();
handleShowQrCode(task.id);
}}
>
<QrcodeOutlined />
</Button>
<Button
size="mini"
onClick={(e) => {
e.stopPropagation();
handleOpenApiSettings(task.id);
}}
>
<SettingOutlined />
</Button>
<Button
size="mini"
color="danger"
onClick={(e) => {
e.stopPropagation();
handleDeletePlan(task.id);
}}
>
<DeleteOutlined />
</Button>
</Space>
</div>
</List.Item>
))
)}
</List>
{/* API设置弹窗 */}
<Popup
visible={showApiDialog}
onMaskClick={() => setShowApiDialog(false)}
position="bottom"
bodyStyle={{ height: "60vh" }}
>
<div className={style["api-dialog"]}>
<div className={style["dialog-header"]}>
<h3>API设置</h3>
<Button size="small" onClick={() => setShowApiDialog(false)}>
</Button>
</div>
<div className={style["dialog-content"]}>
<div className={style["api-item"]}>
<label>API Key:</label>
<div className={style["input-with-button"]}>
<Input value={currentApiSettings.apiKey} readOnly />
<Button
size="mini"
onClick={() => handleCopyApiUrl(currentApiSettings.apiKey)}
>
</Button>
</div>
</div>
<div className={style["api-item"]}>
<label>Webhook URL:</label>
<div className={style["input-with-button"]}>
<Input value={currentApiSettings.webhookUrl} readOnly />
<Button
size="mini"
onClick={() =>
handleCopyApiUrl(currentApiSettings.webhookUrl)
}
>
</Button>
</div>
</div>
</div>
</div>
</Popup>
{/* 二维码弹窗 */}
<Popup
visible={showQrDialog}
onMaskClick={() => setShowQrDialog(false)}
position="bottom"
>
<div className={style["qr-dialog"]}>
<div className={style["dialog-header"]}>
<h3></h3>
<Button size="small" onClick={() => setShowQrDialog(false)}>
</Button>
</div>
<div className={style["dialog-content"]}>
{qrLoading ? (
<div className={style["qr-loading"]}>
<SpinLoading color="primary" />
<div>...</div>
</div>
) : qrImg ? (
<img
src={qrImg}
alt="小程序二维码"
className={style["qr-image"]}
/>
) : (
<div className={style["qr-error"]}></div>
)}
</div>
</div>
</Popup>
</div>
</Layout>
);
};
export default ScenarioList;

View File

@@ -0,0 +1,51 @@
import request from "@/api/request";
// 获取计划列表
export const getPlanList = async (scenarioId: string, page: number = 1, limit: number = 20) => {
return request(`/api/scenarios/${scenarioId}/plans`, { page, limit }, 'GET');
};
// 获取计划详情
export const getPlanDetail = async (planId: string) => {
return request(`/api/scenarios/plans/${planId}`, undefined, 'GET');
};
// 复制计划
export const copyPlan = async (planId: string) => {
return request(`/api/scenarios/plans/${planId}/copy`, undefined, 'POST');
};
// 删除计划
export const deletePlan = async (planId: string) => {
return request(`/api/scenarios/plans/${planId}`, undefined, 'DELETE');
};
// 创建计划
export const createPlan = async (data: any) => {
return request('/api/scenarios/plans', data, 'POST');
};
// 更新计划
export const updatePlan = async (planId: string, data: any) => {
return request(`/api/scenarios/plans/${planId}`, data, 'PUT');
};
// 获取小程序二维码
export const getWxMinAppCode = async (planId: string) => {
return request(`/api/scenarios/plans/${planId}/qrcode`, undefined, 'GET');
};
// 获取场景类型列表
export const getScenarioTypes = async () => {
return request('/api/scenarios/types', undefined, 'GET');
};
// 获取设备列表
export const getDevices = async () => {
return request('/api/devices', undefined, 'GET');
};
// 获取海报列表
export const getPosters = async () => {
return request('/api/posters', undefined, 'GET');
};

View File

@@ -0,0 +1,43 @@
.new-plan-page {
background: #f5f5f5;
min-height: 100vh;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.back-btn {
height: 32px;
width: 32px;
padding: 0;
border-radius: 50%;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
gap: 16px;
}
.loading-text {
color: #666;
font-size: 14px;
}
.steps-container {
background: white;
padding: 20px 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.step-content {
flex: 1;
padding: 0 16px;
}

View File

@@ -1,8 +1,293 @@
import React from "react";
import PlaceholderPage from "@/components/PlaceholderPage";
const NewPlan: React.FC = () => {
return <PlaceholderPage title="新建场景" />;
};
export default NewPlan;
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { NavBar, Button, Toast, SpinLoading, Steps, Popup } from "antd-mobile";
import { LeftOutline } from "antd-mobile-icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import BasicSettings from "./steps/BasicSettings";
import FriendRequestSettings from "./steps/FriendRequestSettings";
import MessageSettings from "./steps/MessageSettings";
import {
getScenarioTypes,
createPlan,
updatePlan,
getPlanDetail,
} from "../api";
import style from "./page.module.scss";
// 步骤定义
const steps = [
{ id: 1, title: "步骤一", subtitle: "基础设置" },
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
{ id: 3, title: "步骤三", subtitle: "消息设置" },
];
// 类型定义
interface FormData {
name: string;
scenario: number;
posters: any[];
device: string[];
remarkType: string;
greeting: string;
addInterval: number;
startTime: string;
endTime: string;
enabled: boolean;
sceneId: string | number;
remarkFormat: string;
addFriendInterval: number;
}
const NewPlan: React.FC = () => {
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState<FormData>({
name: "",
scenario: 1,
posters: [],
device: [],
remarkType: "phone",
greeting: "你好,请通过",
addInterval: 1,
startTime: "09:00",
endTime: "18:00",
enabled: true,
sceneId: "",
remarkFormat: "",
addFriendInterval: 1,
});
const [sceneList, setSceneList] = useState<any[]>([]);
const [sceneLoading, setSceneLoading] = useState(true);
const { scenarioId, planId } = useParams<{
scenarioId: string;
planId: string;
}>();
const [isEdit, setIsEdit] = useState(false);
const [saving, setSaving] = useState(false);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setSceneLoading(true);
try {
// 获取场景类型
const res = await getScenarioTypes();
if (res?.data) {
setSceneList(res.data);
}
if (planId) {
setIsEdit(true);
// 获取计划详情
const detailRes = await getPlanDetail(planId);
if (detailRes.code === 200 && detailRes.data) {
const detail = detailRes.data;
setFormData((prev) => ({
...prev,
name: detail.name ?? "",
scenario: Number(detail.scenario) || 1,
posters: detail.posters ?? [],
device: detail.device ?? [],
remarkType: detail.remarkType ?? "phone",
greeting: detail.greeting ?? "",
addInterval: detail.addInterval ?? 1,
startTime: detail.startTime ?? "09:00",
endTime: detail.endTime ?? "18:00",
enabled: detail.enabled ?? true,
sceneId: Number(detail.scenario) || 1,
remarkFormat: detail.remarkFormat ?? "",
addFriendInterval: detail.addFriendInterval ?? 1,
}));
}
} else if (scenarioId) {
setFormData((prev) => ({
...prev,
scenario: Number(scenarioId) || 1,
}));
}
} catch (error) {
Toast.show({
content: "加载数据失败",
position: "top",
});
} finally {
setSceneLoading(false);
}
};
// 更新表单数据
const onChange = (data: any) => {
setFormData((prev) => ({ ...prev, ...data }));
};
// 处理保存
const handleSave = async () => {
if (!formData.name.trim()) {
Toast.show({
content: "请输入计划名称",
position: "top",
});
return;
}
setSaving(true);
try {
let result;
if (isEdit && planId) {
// 编辑
const editData = {
...formData,
id: Number(planId),
planId: Number(planId),
};
result = await updatePlan(planId, editData);
} else {
// 新建
result = await createPlan(formData);
}
if (result.code === 200) {
Toast.show({
content: isEdit ? "计划已更新" : "获客计划已创建",
position: "top",
});
const sceneItem = sceneList.find((v) => formData.scenario === v.id);
navigate(
`/scenarios/list/${formData.sceneId}/${sceneItem?.name || ""}`
);
} else {
Toast.show({
content: result.msg || "操作失败",
position: "top",
});
}
} catch (error) {
Toast.show({
content: isEdit ? "更新计划失败,请重试" : "创建计划失败,请重试",
position: "top",
});
} finally {
setSaving(false);
}
};
// 下一步
const handleNext = () => {
if (currentStep === steps.length) {
handleSave();
} else {
setCurrentStep((prev) => prev + 1);
}
};
// 上一步
const handlePrev = () => {
setCurrentStep((prev) => Math.max(prev - 1, 1));
};
// 渲染当前步骤内容
const renderStepContent = () => {
switch (currentStep) {
case 1:
return (
<BasicSettings
formData={formData}
onChange={onChange}
onNext={handleNext}
sceneList={sceneList}
sceneLoading={sceneLoading}
/>
);
case 2:
return (
<FriendRequestSettings
formData={formData}
onChange={onChange}
onNext={handleNext}
onPrev={handlePrev}
/>
);
case 3:
return (
<MessageSettings
formData={formData}
onChange={onChange}
onNext={handleNext}
onPrev={handlePrev}
saving={saving}
/>
);
default:
return null;
}
};
if (sceneLoading) {
return (
<Layout
header={
<NavBar back={null} style={{ background: "#fff" }}>
<div className={style["nav-title"]}>
{isEdit ? "编辑计划" : "新建计划"}
</div>
</NavBar>
}
footer={<MeauMobile />}
>
<div className={style["loading"]}>
<SpinLoading color="primary" style={{ fontSize: 32 }} />
<div className={style["loading-text"]}>...</div>
</div>
</Layout>
);
}
return (
<Layout
header={
<NavBar
back={null}
style={{ background: "#fff" }}
left={
<div className={style["nav-title"]}>
{isEdit ? "编辑计划" : "新建计划"}
</div>
}
right={
<Button
size="small"
onClick={() => navigate(-1)}
className={style["back-btn"]}
>
<LeftOutline />
</Button>
}
/>
}
footer={<MeauMobile />}
>
<div className={style["new-plan-page"]}>
{/* 步骤指示器 */}
<div className={style["steps-container"]}>
<Steps current={currentStep - 1}>
{steps.map((step) => (
<Steps.Step
key={step.id}
title={step.title}
description={step.subtitle}
/>
))}
</Steps>
</div>
{/* 步骤内容 */}
<div className={style["step-content"]}>{renderStepContent()}</div>
</div>
</Layout>
);
};
export default NewPlan;

View File

@@ -0,0 +1,63 @@
.basic-settings {
padding: 16px 0;
}
.form-card {
margin-bottom: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.adm-form-item-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.adm-input {
border-radius: 8px;
}
.adm-selector {
border-radius: 8px;
}
}
.time-input {
width: 120px;
border-radius: 8px;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 40vh;
gap: 16px;
}
.loading-text {
color: #666;
font-size: 14px;
}
.actions {
padding: 20px 0;
}
.next-btn {
width: 100%;
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}

View File

@@ -0,0 +1,210 @@
import React, { useState, useEffect } from "react";
import {
Form,
Input,
Selector,
Button,
SpinLoading,
Toast,
Card,
Space,
} from "antd-mobile";
import { getDevices, getPosters } from "../../api";
import style from "./BasicSettings.module.scss";
interface BasicSettingsProps {
formData: any;
onChange: (data: any) => void;
onNext: () => void;
sceneList: any[];
sceneLoading: boolean;
}
const BasicSettings: React.FC<BasicSettingsProps> = ({
formData,
onChange,
onNext,
sceneList,
sceneLoading,
}) => {
const [devices, setDevices] = useState<any[]>([]);
const [posters, setPosters] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoading(true);
try {
// 获取设备列表
const devicesRes = await getDevices();
if (devicesRes?.data) {
setDevices(devicesRes.data);
}
// 获取海报列表
const postersRes = await getPosters();
if (postersRes?.data) {
setPosters(postersRes.data);
}
} catch (error) {
Toast.show({
content: "加载数据失败",
position: "top",
});
} finally {
setLoading(false);
}
};
const handleNext = () => {
if (!formData.name.trim()) {
Toast.show({
content: "请输入计划名称",
position: "top",
});
return;
}
if (!formData.scenario) {
Toast.show({
content: "请选择场景类型",
position: "top",
});
return;
}
if (formData.device.length === 0) {
Toast.show({
content: "请选择设备",
position: "top",
});
return;
}
onNext();
};
if (loading || sceneLoading) {
return (
<div className={style["loading"]}>
<SpinLoading color="primary" style={{ fontSize: 32 }} />
<div className={style["loading-text"]}>...</div>
</div>
);
}
return (
<div className={style["basic-settings"]}>
<Card className={style["form-card"]}>
<Form layout="vertical">
{/* 计划名称 */}
<Form.Item label="计划名称" required className={style["form-item"]}>
<Input
placeholder="请输入计划名称"
value={formData.name}
onChange={(value) => onChange({ name: value })}
clearable
/>
</Form.Item>
{/* 场景类型 */}
<Form.Item label="场景类型" required className={style["form-item"]}>
<Selector
options={sceneList.map((scene) => ({
label: scene.name,
value: scene.id,
}))}
value={[formData.scenario]}
onChange={(value) => {
const selectedScene = sceneList.find(
(scene) => scene.id === value[0]
);
onChange({
scenario: value[0],
sceneId: value[0],
name: selectedScene?.name || "",
});
}}
/>
</Form.Item>
{/* 选择设备 */}
<Form.Item label="选择设备" required className={style["form-item"]}>
<Selector
options={devices.map((device) => ({
label: device.name,
value: device.id,
}))}
value={formData.device}
onChange={(value) => onChange({ device: value })}
multiple
/>
</Form.Item>
{/* 选择海报 */}
<Form.Item label="选择海报" className={style["form-item"]}>
<Selector
options={posters.map((poster) => ({
label: poster.name,
value: poster.id,
}))}
value={formData.posters}
onChange={(value) => onChange({ posters: value })}
multiple
/>
</Form.Item>
{/* 工作时间 */}
<Form.Item label="工作时间" className={style["form-item"]}>
<Space>
<Input
type="time"
value={formData.startTime}
onChange={(value) => onChange({ startTime: value })}
className={style["time-input"]}
/>
<span></span>
<Input
type="time"
value={formData.endTime}
onChange={(value) => onChange({ endTime: value })}
className={style["time-input"]}
/>
</Space>
</Form.Item>
{/* 添加间隔 */}
<Form.Item label="添加间隔(分钟)" className={style["form-item"]}>
<Input
type="number"
placeholder="请输入添加间隔"
value={formData.addInterval.toString()}
onChange={(value) =>
onChange({ addInterval: Number(value) || 1 })
}
min={1}
max={60}
/>
</Form.Item>
</Form>
</Card>
{/* 操作按钮 */}
<div className={style["actions"]}>
<Button
color="primary"
size="large"
onClick={handleNext}
className={style["next-btn"]}
>
</Button>
</div>
</div>
);
};
export default BasicSettings;

View File

@@ -0,0 +1,56 @@
.friend-request-settings {
padding: 16px 0;
}
.form-card {
margin-bottom: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.adm-form-item-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.adm-input {
border-radius: 8px;
}
.adm-selector {
border-radius: 8px;
}
.adm-text-area {
border-radius: 8px;
}
}
.actions {
padding: 20px 0;
}
.prev-btn {
flex: 1;
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}
.next-btn {
flex: 1;
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}

View File

@@ -0,0 +1,113 @@
import React from "react";
import {
Form,
Input,
Selector,
Button,
Card,
Space,
TextArea,
} from "antd-mobile";
import style from "./FriendRequestSettings.module.scss";
interface FriendRequestSettingsProps {
formData: any;
onChange: (data: any) => void;
onNext: () => void;
onPrev: () => void;
}
const FriendRequestSettings: React.FC<FriendRequestSettingsProps> = ({
formData,
onChange,
onNext,
onPrev,
}) => {
const remarkTypeOptions = [
{ label: "手机号", value: "phone" },
{ label: "微信号", value: "wechat" },
{ label: "QQ号", value: "qq" },
{ label: "自定义", value: "custom" },
];
const handleNext = () => {
if (!formData.greeting.trim()) {
// 可以添加验证逻辑
}
onNext();
};
return (
<div className={style["friend-request-settings"]}>
<Card className={style["form-card"]}>
<Form layout="vertical">
{/* 备注类型 */}
<Form.Item label="备注类型" required className={style["form-item"]}>
<Selector
options={remarkTypeOptions}
value={[formData.remarkType]}
onChange={(value) => onChange({ remarkType: value[0] })}
/>
</Form.Item>
{/* 备注格式 */}
{formData.remarkType === "custom" && (
<Form.Item label="备注格式" required className={style["form-item"]}>
<Input
placeholder="请输入备注格式,如:{name}-{phone}"
value={formData.remarkFormat}
onChange={(value) => onChange({ remarkFormat: value })}
clearable
/>
</Form.Item>
)}
{/* 打招呼消息 */}
<Form.Item label="打招呼消息" required className={style["form-item"]}>
<TextArea
placeholder="请输入打招呼消息"
value={formData.greeting}
onChange={(value) => onChange({ greeting: value })}
rows={4}
maxLength={200}
showCount
/>
</Form.Item>
{/* 好友申请间隔 */}
<Form.Item label="好友申请间隔(分钟)" className={style["form-item"]}>
<Input
type="number"
placeholder="请输入好友申请间隔"
value={formData.addFriendInterval.toString()}
onChange={(value) =>
onChange({ addFriendInterval: Number(value) || 1 })
}
min={1}
max={60}
/>
</Form.Item>
</Form>
</Card>
{/* 操作按钮 */}
<div className={style["actions"]}>
<Space style={{ width: "100%" }}>
<Button size="large" onClick={onPrev} className={style["prev-btn"]}>
</Button>
<Button
color="primary"
size="large"
onClick={handleNext}
className={style["next-btn"]}
>
</Button>
</Space>
</div>
</div>
);
};
export default FriendRequestSettings;

View File

@@ -0,0 +1,64 @@
.message-settings {
padding: 16px 0;
}
.form-card {
margin-bottom: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.adm-form-item-label {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.adm-input {
border-radius: 8px;
}
.adm-text-area {
border-radius: 8px;
}
}
.switch-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
span {
font-size: 14px;
color: #333;
}
}
.actions {
padding: 20px 0;
}
.prev-btn {
flex: 1;
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}
.save-btn {
flex: 1;
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
}

View File

@@ -0,0 +1,140 @@
import React from "react";
import {
Form,
Input,
Selector,
Button,
Card,
Space,
TextArea,
Switch,
} from "antd-mobile";
import style from "./MessageSettings.module.scss";
interface MessageSettingsProps {
formData: any;
onChange: (data: any) => void;
onNext: () => void;
onPrev: () => void;
saving: boolean;
}
const MessageSettings: React.FC<MessageSettingsProps> = ({
formData,
onChange,
onNext,
onPrev,
saving,
}) => {
const handleSave = () => {
onNext();
};
return (
<div className={style["message-settings"]}>
<Card className={style["form-card"]}>
<Form layout="vertical">
{/* 启用状态 */}
<Form.Item label="启用状态" className={style["form-item"]}>
<div className={style["switch-item"]}>
<span></span>
<Switch
checked={formData.enabled}
onChange={(checked) => onChange({ enabled: checked })}
/>
</div>
</Form.Item>
{/* 自动回复消息 */}
<Form.Item label="自动回复消息" className={style["form-item"]}>
<TextArea
placeholder="请输入自动回复消息"
value={formData.autoReply || ""}
onChange={(value) => onChange({ autoReply: value })}
rows={4}
maxLength={500}
showCount
/>
</Form.Item>
{/* 关键词回复 */}
<Form.Item label="关键词回复" className={style["form-item"]}>
<TextArea
placeholder="请输入关键词回复规则,格式:关键词=回复内容"
value={formData.keywordReply || ""}
onChange={(value) => onChange({ keywordReply: value })}
rows={4}
maxLength={1000}
showCount
/>
</Form.Item>
{/* 群发消息 */}
<Form.Item label="群发消息" className={style["form-item"]}>
<TextArea
placeholder="请输入群发消息内容"
value={formData.groupMessage || ""}
onChange={(value) => onChange({ groupMessage: value })}
rows={4}
maxLength={500}
showCount
/>
</Form.Item>
{/* 消息发送间隔 */}
<Form.Item label="消息发送间隔(秒)" className={style["form-item"]}>
<Input
type="number"
placeholder="请输入消息发送间隔"
value={formData.messageInterval?.toString() || "5"}
onChange={(value) =>
onChange({ messageInterval: Number(value) || 5 })
}
min={1}
max={300}
/>
</Form.Item>
{/* 每日发送限制 */}
<Form.Item label="每日发送限制" className={style["form-item"]}>
<Input
type="number"
placeholder="请输入每日发送限制数量"
value={formData.dailyLimit?.toString() || "100"}
onChange={(value) =>
onChange({ dailyLimit: Number(value) || 100 })
}
min={1}
max={1000}
/>
</Form.Item>
</Form>
</Card>
{/* 操作按钮 */}
<div className={style["actions"]}>
<Space style={{ width: "100%" }}>
<Button
size="large"
onClick={onPrev}
className={style["prev-btn"]}
disabled={saving}
>
</Button>
<Button
color="primary"
size="large"
onClick={handleSave}
className={style["save-btn"]}
loading={saving}
>
{saving ? "保存中..." : "保存计划"}
</Button>
</Space>
</div>
</div>
);
};
export default MessageSettings;

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { NavBar, Button, Toast, SpinLoading } from "antd-mobile";
import { AddOutline } from "antd-mobile-icons";
import { RiseOutlined } from "@ant-design/icons";
import { PlusOutlined, UpOutlined } from "@ant-design/icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import { getScenarios } from "./api";
@@ -91,7 +90,7 @@ const Scene: React.FC = () => {
className={style["new-plan-btn"]}
style={{ marginLeft: "auto" }}
>
<AddOutline />
<PlusOutlined />
</Button>
</NavBar>
}
@@ -118,7 +117,7 @@ const Scene: React.FC = () => {
className={style["new-plan-btn"]}
style={{ marginLeft: "auto" }}
>
<AddOutline />
<PlusOutlined />
</Button>
</NavBar>
}
@@ -149,7 +148,7 @@ const Scene: React.FC = () => {
className={style["new-plan-btn"]}
style={{ marginLeft: "auto" }}
>
<AddOutline />
<PlusOutlined />
</Button>
}
></NavBar>
@@ -189,7 +188,7 @@ const Scene: React.FC = () => {
: {scenario.count}
</span>
<span className={style["card-growth"]}>
<RiseOutlined
<UpOutlined
style={{ fontSize: 14, color: "#52c41a", marginRight: 2 }}
/>
{scenario.growth}

View File

@@ -1,37 +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;
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { PlusOutlined } from "@ant-design/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">
<PlusOutlined />
<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;

View File

@@ -2,15 +2,15 @@ import React from "react";
import { Link } from "react-router-dom";
import { Card, NavBar, Badge } from "antd-mobile";
import {
LikeOutline,
MessageOutline,
SendOutline,
TeamOutline,
LinkOutline,
AppOutline,
PieOutline,
ClockCircleOutline,
} from "antd-mobile-icons";
LikeOutlined,
MessageOutlined,
SendOutlined,
TeamOutlined,
LinkOutlined,
AppstoreOutlined,
PieChartOutlined,
ClockCircleOutlined,
} from "@ant-design/icons";
import Layout from "@/components/Layout/Layout";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import styles from "./Workspace.module.scss";
@@ -32,7 +32,7 @@ const Workspace: React.FC = () => {
name: "自动点赞",
description: "智能自动点赞互动",
icon: (
<LikeOutline className={styles.icon} style={{ color: "#ff4d4f" }} />
<LikeOutlined className={styles.icon} style={{ color: "#ff4d4f" }} />
),
path: "/workspace/auto-like",
bgColor: "#fff2f0",
@@ -43,7 +43,7 @@ const Workspace: React.FC = () => {
name: "朋友圈同步",
description: "自动同步朋友圈内容",
icon: (
<ClockCircleOutline
<ClockCircleOutlined
className={styles.icon}
style={{ color: "#722ed1" }}
/>
@@ -56,7 +56,7 @@ const Workspace: React.FC = () => {
name: "群消息推送",
description: "智能群发助手",
icon: (
<SendOutline className={styles.icon} style={{ color: "#fa8c16" }} />
<SendOutlined className={styles.icon} style={{ color: "#fa8c16" }} />
),
path: "/workspace/group-push",
bgColor: "#fff7e6",
@@ -66,7 +66,7 @@ const Workspace: React.FC = () => {
name: "自动建群",
description: "智能拉好友建群",
icon: (
<TeamOutline className={styles.icon} style={{ color: "#52c41a" }} />
<TeamOutlined className={styles.icon} style={{ color: "#52c41a" }} />
),
path: "/workspace/auto-group",
bgColor: "#f6ffed",
@@ -76,7 +76,7 @@ const Workspace: React.FC = () => {
name: "流量分发",
description: "管理流量分发和分配",
icon: (
<LinkOutline className={styles.icon} style={{ color: "#1890ff" }} />
<LinkOutlined className={styles.icon} style={{ color: "#1890ff" }} />
),
path: "/workspace/traffic-distribution",
bgColor: "#e6f7ff",
@@ -86,7 +86,7 @@ const Workspace: React.FC = () => {
name: "AI对话助手",
description: "智能回复,提高互动质量",
icon: (
<MessageOutline className={styles.icon} style={{ color: "#1890ff" }} />
<MessageOutlined className={styles.icon} style={{ color: "#1890ff" }} />
),
path: "/workspace/ai-assistant",
bgColor: "#e6f7ff",
@@ -100,7 +100,12 @@ const Workspace: React.FC = () => {
id: "ai-analyzer",
name: "AI数据分析",
description: "智能分析客户行为特征",
icon: <PieOutline className={styles.icon} style={{ color: "#531dab" }} />,
icon: (
<PieChartOutlined
className={styles.icon}
style={{ color: "#531dab" }}
/>
),
path: "/workspace/ai-analyzer",
bgColor: "#f0f0ff",
isNew: true,
@@ -109,7 +114,12 @@ const Workspace: React.FC = () => {
id: "ai-strategy",
name: "AI策略优化",
description: "智能优化获客策略",
icon: <AppOutline className={styles.icon} style={{ color: "#13c2c2" }} />,
icon: (
<AppstoreOutlined
className={styles.icon}
style={{ color: "#13c2c2" }}
/>
),
path: "/workspace/ai-strategy",
bgColor: "#e6fffb",
isNew: true,
@@ -118,7 +128,12 @@ const Workspace: React.FC = () => {
id: "ai-forecast",
name: "AI销售预测",
description: "智能预测销售趋势",
icon: <PieOutline className={styles.icon} style={{ color: "#d48806" }} />,
icon: (
<PieChartOutlined
className={styles.icon}
style={{ color: "#d48806" }}
/>
),
path: "/workspace/ai-forecast",
bgColor: "#fffbe6",
},

View File

@@ -1,38 +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;
import React from "react";
import { NavBar, Button } from "antd-mobile";
import { PlusOutlined } from "@ant-design/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">
<PlusOutlined />
<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;

View File

@@ -1,12 +1,12 @@
import React, { useState } from "react";
import { Card, NavBar, TabBar, Grid } from "antd-mobile";
import {
AppOutline,
UserOutline,
PieOutline, // 替换 BarChartOutline
ShopbagOutline,
BellOutline,
} from "antd-mobile-icons";
AppstoreOutlined,
UserOutlined,
PieChartOutlined,
ShoppingOutlined,
BellOutlined,
} from "@ant-design/icons";
import MeauMobile from "@/components/MeauMobile/MeauMoible";
import Layout from "@/components/Layout/Layout";
import style from "./index.module.scss";
@@ -14,22 +14,22 @@ const sceneStats = [
{
label: "公众号获客",
value: 234,
icon: <ShopbagOutline style={{ fontSize: 28, color: "#4caf50" }} />,
icon: <ShoppingOutlined style={{ fontSize: 28, color: "#4caf50" }} />,
},
{
label: "海报获客",
value: 167,
icon: <AppOutline style={{ fontSize: 28, color: "#ff9800" }} />,
icon: <AppstoreOutlined style={{ fontSize: 28, color: "#ff9800" }} />,
},
{
label: "抖音获客",
value: 156,
icon: <PieOutline style={{ fontSize: 28, color: "#2196f3" }} />,
icon: <PieChartOutlined style={{ fontSize: 28, color: "#2196f3" }} />,
},
{
label: "小红书获客",
value: 89,
icon: <UserOutline style={{ fontSize: 28, color: "#e91e63" }} />,
icon: <UserOutlined style={{ fontSize: 28, color: "#e91e63" }} />,
},
];

View File

@@ -1,3 +1,4 @@
import Scene from "@/pages/scene";
import Scenarios from "@/pages/scenarios/Scenarios";
import NewPlan from "@/pages/scenarios/new/page";
import ScenarioList from "@/pages/scenarios/ScenarioList";
@@ -5,12 +6,12 @@ import ScenarioList from "@/pages/scenarios/ScenarioList";
const scenarioRoutes = [
{
path: "/scene",
element: <Scenarios />,
element: <Scene />,
auth: true,
},
{
path: "/scenarios",
element: <Scenarios />,
element: <Scene />,
auth: true,
},
{

View File

@@ -24,7 +24,7 @@ const PermissionRoute: React.FC<PermissionRouteProps> = ({
}
// 检查角色权限(如果需要)
if (requiredRole && user.role !== requiredRole) {
if (requiredRole && user.isAdmin !== 1) {
navigate("/");
return;
}
@@ -36,7 +36,7 @@ const PermissionRoute: React.FC<PermissionRouteProps> = ({
}
// 如果角色不匹配,不渲染子组件
if (requiredRole && user.role !== requiredRole) {
if (requiredRole && user.isAdmin !== 1) {
return null;
}

View File

@@ -1,28 +0,0 @@
迁移提示词Checklist
当你将其他项目迁移到 nkebao 项目结构时,可参考以下提示词进行自适应改造:
1. 目录与文件结构
按 nkebao 目录规范拆分api、router/module、store/module、pages、styles 等
公共样式、reset、rem 适配、兼容性样式统一放到 styles/global.scss
2. 路由迁移
将原有路由配置拆分为 module 目录下的多个路由模块,导出数组
路由配置中增加 auth/requiredRole 字段,适配权限拦截
如有嵌套路由,按 useRoutes 结构组织
3. 状态管理迁移
将原有全局状态拆分为多个 zustand store放到 store/module 下
如需持久化,使用 createPersistStore 工具
组件中统一用 useXxxStore 访问和修改状态
4. 请求管理迁移
所有接口请求统一用 src/api/request.ts 封装
需要防抖的接口可自定义 debounceGap 参数
错误提示统一用 Toast必要时细分错误类型
需要 token 的接口无需手动加 header自动注入
5. 组件与样式迁移
组件按业务拆分到 pages/ 下
样式统一用 scss变量、reset、兼容性样式集中管理
移动端适配用 remPC 端可用媒体查询补充
6. 工程化与规范
路径引用统一用 @/xxx
保持 tsconfig.json、vite.config.ts、.eslintrc.js、postcss.config.js 配置一致
代码风格、格式化、类型声明与 nkebao 保持一致
7. 其他
如有全局 loading、消息、权限等需求建议用 zustand/store 单独模块管理