diff --git a/ckApp/.gitignore b/ckApp/.gitignore new file mode 100644 index 00000000..efe77355 --- /dev/null +++ b/ckApp/.gitignore @@ -0,0 +1 @@ +unpackage \ No newline at end of file diff --git a/ckApp/App.vue b/ckApp/App.vue new file mode 100644 index 00000000..f3393a5b --- /dev/null +++ b/ckApp/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/ckApp/index.html b/ckApp/index.html new file mode 100644 index 00000000..b5d330d1 --- /dev/null +++ b/ckApp/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/ckApp/main.js b/ckApp/main.js new file mode 100644 index 00000000..c1caf360 --- /dev/null +++ b/ckApp/main.js @@ -0,0 +1,22 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +import './uni.promisify.adaptor' +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif \ No newline at end of file diff --git a/ckApp/manifest.json b/ckApp/manifest.json new file mode 100644 index 00000000..bb255e1e --- /dev/null +++ b/ckApp/manifest.json @@ -0,0 +1,74 @@ +{ + "name" : "ckApp", + "appid" : "__UNI__2B34F1A", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : { + "dSYMs" : false + }, + /* SDK配置 */ + "sdkConfigs" : {} + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "3" +} diff --git a/ckApp/pages.json b/ckApp/pages.json new file mode 100644 index 00000000..570407ef --- /dev/null +++ b/ckApp/pages.json @@ -0,0 +1,24 @@ +{ + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "", + "navigationStyle": "custom" + } + }, + { + "path": "pages/index/test", + "style": { + "navigationBarTitleText": "", + "navigationStyle": "custom" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "uniIdRouter": {} +} diff --git a/ckApp/pages/index/index.vue b/ckApp/pages/index/index.vue new file mode 100644 index 00000000..7d0341d2 --- /dev/null +++ b/ckApp/pages/index/index.vue @@ -0,0 +1,100 @@ + + + \ No newline at end of file diff --git a/ckApp/pages/index/test.vue b/ckApp/pages/index/test.vue new file mode 100644 index 00000000..538d3a3c --- /dev/null +++ b/ckApp/pages/index/test.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/ckApp/static/logo.png b/ckApp/static/logo.png new file mode 100644 index 00000000..b5771e20 Binary files /dev/null and b/ckApp/static/logo.png differ diff --git a/ckApp/uni.promisify.adaptor.js b/ckApp/uni.promisify.adaptor.js new file mode 100644 index 00000000..5fec4f33 --- /dev/null +++ b/ckApp/uni.promisify.adaptor.js @@ -0,0 +1,13 @@ +uni.addInterceptor({ + returnValue (res) { + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) { + return res; + } + return new Promise((resolve, reject) => { + res.then((res) => { + if (!res) return resolve(res) + return res[0] ? reject(res[0]) : resolve(res[1]) + }); + }); + }, +}); \ No newline at end of file diff --git a/ckApp/uni.scss b/ckApp/uni.scss new file mode 100644 index 00000000..b9249e9d --- /dev/null +++ b/ckApp/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/ckApp/utils/common.js b/ckApp/utils/common.js new file mode 100644 index 00000000..f50cc5c5 --- /dev/null +++ b/ckApp/utils/common.js @@ -0,0 +1,36 @@ +/** + * 异步获取设备顶部安全区域高度 + * @returns {Promise} 顶部安全区域高度 + */ +export function getTopSafeAreaHeightAsync() { + return new Promise((resolve, reject) => { + uni.getSystemInfo({ + success: (res) => { + try { + const safeAreaInsets = res.safeAreaInsets; + + if (safeAreaInsets && safeAreaInsets.top !== undefined) { + resolve(safeAreaInsets.top); + return; + } + + if (res.safeArea) { + const safeArea = res.safeArea; + const statusBarHeight = res.statusBarHeight || 0; + const topSafeHeight = safeArea.top - statusBarHeight; + resolve(Math.max(0, topSafeHeight)); + return; + } + + resolve(`${res.statusBarHeight*2 || 0}px`); + + } catch (error) { + reject(error); + } + }, + fail: (error) => { + reject(error); + } + }); + }); + } \ No newline at end of file diff --git a/ckApp/vite.config.js b/ckApp/vite.config.js new file mode 100644 index 00000000..3910715c --- /dev/null +++ b/ckApp/vite.config.js @@ -0,0 +1,29 @@ +import { defineConfig } from 'vite' +import uni from '@dcloudio/vite-plugin-uni' +import { resolve } from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [uni()], + resolve: { + alias: { + // 根目录别名 + '@': resolve(__dirname, '/'), + '@root': resolve(__dirname, '/'), + + // 页面和组件别名 + '@pages': resolve(__dirname, 'pages'), + '@components': resolve(__dirname, 'components'), + + // 工具和配置别名 + '@utils': resolve(__dirname, 'utils'), + } + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/uni.scss";` + } + } + } +}) \ No newline at end of file diff --git a/nkebao/.env.development b/nkebao/.env.development index d130c9ed..9ac98215 100644 --- a/nkebao/.env.development +++ b/nkebao/.env.development @@ -1,4 +1,4 @@ # 基础环境变量示例 -VITE_API_BASE_URL=http://www.yishi.com -# VITE_API_BASE_URL=https://ckbapi.quwanzhi.com +# VITE_API_BASE_URL=http://www.yishi.com +VITE_API_BASE_URL=https://ckbapi.quwanzhi.com VITE_APP_TITLE=Nkebao Base diff --git a/nkebao/AI_ICON_GUIDE.md b/nkebao/AI_ICON_GUIDE.md deleted file mode 100644 index bc536673..00000000 --- a/nkebao/AI_ICON_GUIDE.md +++ /dev/null @@ -1,274 +0,0 @@ -# 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'; - -// 使用示例 - - - -``` - -### 场景2: PC端管理页面 -```typescript -// AI应该生成的代码 -import { - PlusOutlined, // 添加按钮 - EditOutlined, // 编辑按钮 - DeleteOutlined, // 删除按钮 - CopyOutlined, // 复制按钮 - SearchOutlined, // 搜索框 - ReloadOutlined, // 刷新按钮 - UserOutlined, // 用户信息 - CalendarOutlined, // 时间信息 - RiseOutlined, // 上升趋势(PC端存在) -} from '@ant-design/icons'; - -// 使用示例 - - - -``` - -## 错误检测和修正 - -### 常见错误模式 -1. **混用图标包**: 同时导入PC端和移动端图标 -2. **使用不存在的图标**: 在移动端使用PC端特有的图标 -3. **命名错误**: 图标名称大小写错误 -4. **导入路径错误**: 使用错误的包名 - -### 修正策略 -1. **统一图标包**: 根据项目类型选择单一图标包 -2. **查找替代**: 使用对照表查找替代图标 -3. **验证存在**: 确保图标在目标包中存在 -4. **测试验证**: 在代码中测试图标是否正常显示 - -## AI使用检查清单 - -### 代码生成前 -- [ ] 确认项目类型(PC端/移动端) -- [ ] 选择对应的图标包 -- [ ] 根据功能需求选择合适图标 -- [ ] 检查图标是否存在 - -### 代码生成中 -- [ ] 使用正确的导入语法 -- [ ] 图标名称大小写正确 -- [ ] 避免混用不同包的图标 -- [ ] 为不存在的图标提供替代方案 - -### 代码生成后 -- [ ] 验证图标导入正确 -- [ ] 检查图标使用语法 -- [ ] 确保样式设置合理 -- [ ] 提供使用示例 - -## 更新和维护 - -- 定期更新图标对照表 -- 记录新发现的图标差异 -- 更新替换规则 -- 优化AI使用流程 - -## 注意事项 - -1. **优先使用语义化图标**: 选择最能表达功能的图标 -2. **保持一致性**: 在同一项目中保持图标风格一致 -3. **考虑可访问性**: 为图标添加适当的aria-label -4. **性能优化**: 按需导入图标,避免全量导入 -5. **版本兼容**: 注意图标包版本与UI框架版本的兼容性 \ No newline at end of file diff --git a/nkebao/ICON_DETAILED_MAPPING.md b/nkebao/ICON_DETAILED_MAPPING.md deleted file mode 100644 index b2c73166..00000000 --- a/nkebao/ICON_DETAILED_MAPPING.md +++ /dev/null @@ -1,205 +0,0 @@ -# 详细图标对照表 - -## 基础图标对照 - -### 导航类 -| 功能 | 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. **性能**: 大量使用图标时注意性能优化 \ No newline at end of file diff --git a/nkebao/ICON_MAPPING_GUIDE.md b/nkebao/ICON_MAPPING_GUIDE.md deleted file mode 100644 index 2b237842..00000000 --- a/nkebao/ICON_MAPPING_GUIDE.md +++ /dev/null @@ -1,230 +0,0 @@ -# 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: 初始版本,包含常用图标对照 -- 后续根据实际使用情况持续更新 \ No newline at end of file diff --git a/nkebao/ICON_QUICK_REFERENCE.md b/nkebao/ICON_QUICK_REFERENCE.md deleted file mode 100644 index 1e64fe9a..00000000 --- a/nkebao/ICON_QUICK_REFERENCE.md +++ /dev/null @@ -1,271 +0,0 @@ -# 图标快速参考表 - -## 快速查找 - -### 🔍 按功能查找 - -| 功能 | 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'; - - - - -// 移动端 -import { HomeOutline, UserOutline } from 'antd-mobile-icons'; - - - -``` - -### 按钮中使用 -```typescript -// PC端 -import { PlusOutlined, EditOutlined } from '@ant-design/icons'; - - - - -// 移动端 -import { AddOutline, EditSOutline } from 'antd-mobile-icons'; - - - -``` - -### 列表中使用 -```typescript -// PC端 -import { DeleteOutlined, CopyOutlined } from '@ant-design/icons'; - - - - -// 移动端 -import { DeleteOutline, CopyOutline } from 'antd-mobile-icons'; - - - -``` - -## 常见错误 - -### ❌ 错误示例 -```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: 初始版本 -- 添加常用图标对照 -- 添加错误示例和正确示例 -- 添加快速检查清单 -- 添加紧急替换方案 \ No newline at end of file diff --git a/nkebao/index.html b/nkebao/index.html index 784f0cf8..22f819b6 100644 --- a/nkebao/index.html +++ b/nkebao/index.html @@ -1,13 +1,19 @@ - + Nkebao Base - + + +
- \ No newline at end of file + diff --git a/nkebao/package-lock.json b/nkebao/package-lock.json index 5932cee5..3a81b88f 100644 --- a/nkebao/package-lock.json +++ b/nkebao/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@ant-design/icons": "^5.6.1", + "@capacitor/android": "^7.4.2", "antd": "^5.13.1", "antd-mobile": "^5.39.1", "axios": "^1.6.7", @@ -444,6 +445,25 @@ "node": ">=6.9.0" } }, + "node_modules/@capacitor/android": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-7.4.2.tgz", + "integrity": "sha512-FZ7M9NwFkljR7EP5eXiE32mAIfZNcYw2CzRMCG3rQu0u0ZaIoeOeq5/oK4YcDnGpNmu8jpngKJqZ+9OiSQSwDg==", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^7.4.0" + } + }, + "node_modules/@capacitor/core": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.2.tgz", + "integrity": "sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", diff --git a/nkebao/public/manifest.json b/nkebao/public/manifest.json new file mode 100644 index 00000000..fc1e9f5a --- /dev/null +++ b/nkebao/public/manifest.json @@ -0,0 +1,30 @@ +{ + "name": "Cunkebao", + "short_name": "Cunkebao", + "description": "Cunkebao Mobile App", + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone", + "orientation": "portrait", + "scope": "/", + "start_url": "/", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "logo.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ] +} \ No newline at end of file diff --git a/nkebao/public/websdk.js b/nkebao/public/websdk.js new file mode 100644 index 00000000..99870fa4 --- /dev/null +++ b/nkebao/public/websdk.js @@ -0,0 +1,308 @@ +!(function (e, n) { + "object" == typeof exports && "undefined" != typeof module + ? (module.exports = n()) + : "function" == typeof define && define.amd + ? define(n) + : ((e = e || self).uni = n()); +})(this, function () { + "use strict"; + try { + var e = {}; + (Object.defineProperty(e, "passive", { + get: function () { + !0; + }, + }), + window.addEventListener("test-passive", null, e)); + } catch (e) {} + var n = Object.prototype.hasOwnProperty; + function i(e, i) { + return n.call(e, i); + } + var t = []; + function o() { + return window.__dcloud_weex_postMessage || window.__dcloud_weex_; + } + function a() { + return window.__uniapp_x_postMessage || window.__uniapp_x_; + } + var r = function (e, n) { + var i = { options: { timestamp: +new Date() }, name: e, arg: n }; + if (a()) { + if ("postMessage" === e) { + var r = { data: n }; + return window.__uniapp_x_postMessage + ? window.__uniapp_x_postMessage(r) + : window.__uniapp_x_.postMessage(JSON.stringify(r)); + } + var d = { + type: "WEB_INVOKE_APPSERVICE", + args: { data: i, webviewIds: t }, + }; + window.__uniapp_x_postMessage + ? window.__uniapp_x_postMessageToService(d) + : window.__uniapp_x_.postMessageToService(JSON.stringify(d)); + } else if (o()) { + if ("postMessage" === e) { + var s = { data: [n] }; + return window.__dcloud_weex_postMessage + ? window.__dcloud_weex_postMessage(s) + : window.__dcloud_weex_.postMessage(JSON.stringify(s)); + } + var w = { + type: "WEB_INVOKE_APPSERVICE", + args: { data: i, webviewIds: t }, + }; + window.__dcloud_weex_postMessage + ? window.__dcloud_weex_postMessageToService(w) + : window.__dcloud_weex_.postMessageToService(JSON.stringify(w)); + } else { + if (!window.plus) + return window.parent.postMessage( + { type: "WEB_INVOKE_APPSERVICE", data: i, pageId: "" }, + "*", + ); + if (0 === t.length) { + var u = plus.webview.currentWebview(); + if (!u) throw new Error("plus.webview.currentWebview() is undefined"); + var g = u.parent(), + v = ""; + ((v = g ? g.id : u.id), t.push(v)); + } + if (plus.webview.getWebviewById("__uniapp__service")) + plus.webview.postMessageToUniNView( + { type: "WEB_INVOKE_APPSERVICE", args: { data: i, webviewIds: t } }, + "__uniapp__service", + ); + else { + var c = JSON.stringify(i); + plus.webview + .getLaunchWebview() + .evalJS( + 'UniPlusBridge.subscribeHandler("' + .concat("WEB_INVOKE_APPSERVICE", '",') + .concat(c, ",") + .concat(JSON.stringify(t), ");"), + ); + } + } + }, + d = { + navigateTo: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + n = e.url; + r("navigateTo", { url: encodeURI(n) }); + }, + navigateBack: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + n = e.delta; + r("navigateBack", { delta: parseInt(n) || 1 }); + }, + switchTab: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + n = e.url; + r("switchTab", { url: encodeURI(n) }); + }, + reLaunch: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + n = e.url; + r("reLaunch", { url: encodeURI(n) }); + }, + redirectTo: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, + n = e.url; + r("redirectTo", { url: encodeURI(n) }); + }, + getEnv: function (e) { + a() + ? e({ uvue: !0 }) + : o() + ? e({ nvue: !0 }) + : window.plus + ? e({ plus: !0 }) + : e({ h5: !0 }); + }, + postMessage: function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; + r("postMessage", e.data || {}); + }, + }, + s = /uni-app/i.test(navigator.userAgent), + w = /Html5Plus/i.test(navigator.userAgent), + u = /complete|loaded|interactive/; + var g = + window.my && + navigator.userAgent.indexOf( + ["t", "n", "e", "i", "l", "C", "y", "a", "p", "i", "l", "A"] + .reverse() + .join(""), + ) > -1; + var v = + window.swan && window.swan.webView && /swan/i.test(navigator.userAgent); + var c = + window.qq && + window.qq.miniProgram && + /QQ/i.test(navigator.userAgent) && + /miniProgram/i.test(navigator.userAgent); + var p = + window.tt && + window.tt.miniProgram && + /toutiaomicroapp/i.test(navigator.userAgent); + var _ = + window.wx && + window.wx.miniProgram && + /micromessenger/i.test(navigator.userAgent) && + /miniProgram/i.test(navigator.userAgent); + var m = window.qa && /quickapp/i.test(navigator.userAgent); + var f = + window.ks && + window.ks.miniProgram && + /micromessenger/i.test(navigator.userAgent) && + /miniProgram/i.test(navigator.userAgent); + var l = + window.tt && + window.tt.miniProgram && + /Lark|Feishu/i.test(navigator.userAgent); + var E = + window.jd && window.jd.miniProgram && /jdmp/i.test(navigator.userAgent); + var x = + window.xhs && + window.xhs.miniProgram && + /xhsminiapp/i.test(navigator.userAgent); + for ( + var S, + h = function () { + ((window.UniAppJSBridge = !0), + document.dispatchEvent( + new CustomEvent("UniAppJSBridgeReady", { + bubbles: !0, + cancelable: !0, + }), + )); + }, + y = [ + function (e) { + if (s || w) + return ( + window.__uniapp_x_postMessage || + window.__uniapp_x_ || + window.__dcloud_weex_postMessage || + window.__dcloud_weex_ + ? document.addEventListener("DOMContentLoaded", e) + : window.plus && u.test(document.readyState) + ? setTimeout(e, 0) + : document.addEventListener("plusready", e), + d + ); + }, + function (e) { + if (_) + return ( + window.WeixinJSBridge && window.WeixinJSBridge.invoke + ? setTimeout(e, 0) + : document.addEventListener("WeixinJSBridgeReady", e), + window.wx.miniProgram + ); + }, + function (e) { + if (c) + return ( + window.QQJSBridge && window.QQJSBridge.invoke + ? setTimeout(e, 0) + : document.addEventListener("QQJSBridgeReady", e), + window.qq.miniProgram + ); + }, + function (e) { + if (g) { + document.addEventListener("DOMContentLoaded", e); + var n = window.my; + return { + navigateTo: n.navigateTo, + navigateBack: n.navigateBack, + switchTab: n.switchTab, + reLaunch: n.reLaunch, + redirectTo: n.redirectTo, + postMessage: n.postMessage, + getEnv: n.getEnv, + }; + } + }, + function (e) { + if (v) + return ( + document.addEventListener("DOMContentLoaded", e), + window.swan.webView + ); + }, + function (e) { + if (p) + return ( + document.addEventListener("DOMContentLoaded", e), + window.tt.miniProgram + ); + }, + function (e) { + if (m) { + window.QaJSBridge && window.QaJSBridge.invoke + ? setTimeout(e, 0) + : document.addEventListener("QaJSBridgeReady", e); + var n = window.qa; + return { + navigateTo: n.navigateTo, + navigateBack: n.navigateBack, + switchTab: n.switchTab, + reLaunch: n.reLaunch, + redirectTo: n.redirectTo, + postMessage: n.postMessage, + getEnv: n.getEnv, + }; + } + }, + function (e) { + if (f) + return ( + window.WeixinJSBridge && window.WeixinJSBridge.invoke + ? setTimeout(e, 0) + : document.addEventListener("WeixinJSBridgeReady", e), + window.ks.miniProgram + ); + }, + function (e) { + if (l) + return ( + document.addEventListener("DOMContentLoaded", e), + window.tt.miniProgram + ); + }, + function (e) { + if (E) + return ( + window.JDJSBridgeReady && window.JDJSBridgeReady.invoke + ? setTimeout(e, 0) + : document.addEventListener("JDJSBridgeReady", e), + window.jd.miniProgram + ); + }, + function (e) { + if (x) return window.xhs.miniProgram; + }, + function (e) { + return (document.addEventListener("DOMContentLoaded", e), d); + }, + ], + M = 0; + M < y.length && !(S = y[M](h)); + M++ + ); + S || (S = {}); + var P = "undefined" != typeof uni ? uni : {}; + if (!P.navigateTo) for (var b in S) i(S, b) && (P[b] = S[b]); + return ((P.webView = S), P); +}); diff --git a/nkebao/src/components/NavCommon/index.tsx b/nkebao/src/components/NavCommon/index.tsx index 8002fc25..0ee5ec1b 100644 --- a/nkebao/src/components/NavCommon/index.tsx +++ b/nkebao/src/components/NavCommon/index.tsx @@ -1,40 +1,61 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { NavBar } from "antd-mobile"; import { ArrowLeftOutlined } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; - +import { getSafeAreaHeight } from "@/utils/common"; interface NavCommonProps { title: string; backFn?: () => void; right?: React.ReactNode; + left?: React.ReactNode; } -const NavCommon: React.FC = ({ title, backFn, right }) => { +const NavCommon: React.FC = ({ + title, + backFn, + right, + left, +}) => { const navigate = useNavigate(); + const [paddingTop, setPaddingTop] = useState("0px"); + useEffect(() => { + setPaddingTop(getSafeAreaHeight() + "px"); + }, []); + return ( - - { - if (backFn) { - backFn(); - } else { - navigate(-1); - } - }} - /> - - } - right={right} +
- - {title} - - + + { + if (backFn) { + backFn(); + } else { + navigate(-1); + } + }} + /> +
+ ) + } + right={right} + > + + {title} + +
+ ); }; diff --git a/nkebao/src/pages/iframe/index.module.scss b/nkebao/src/pages/iframe/index.module.scss new file mode 100644 index 00000000..748327a6 --- /dev/null +++ b/nkebao/src/pages/iframe/index.module.scss @@ -0,0 +1,323 @@ +.iframe-debug-page { + min-height: 100vh; + background: var(--primary-gradient); + padding: 20px; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +.header { + text-align: center; + color: white; + margin-bottom: 30px; + + h1 { + font-size: 2.5rem; + margin: 0 0 10px 0; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + } + + p { + font-size: 1.1rem; + margin: 0; + opacity: 0.9; + } +} + +.content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 30px; + margin-bottom: 30px; + + @media (max-width: 768px) { + grid-template-columns: 1fr; + gap: 20px; + } +} + +.control-panel, +.message-panel { + background: white; + border-radius: 15px; + padding: 25px; + box-shadow: 0 10px 30px var(--primary-shadow); + + h3 { + margin: 0 0 20px 0; + color: #333; + font-size: 1.3rem; + border-bottom: 2px solid var(--primary-color); + padding-bottom: 10px; + } +} + +.input-group { + display: flex; + gap: 10px; + margin-bottom: 20px; + + @media (max-width: 480px) { + flex-direction: column; + } +} + +.button-group { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.btn { + padding: 12px 20px; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 0.5px; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + } + + &:active { + transform: translateY(0); + } + + &.btn-primary { + background: var(--primary-gradient); + color: white; + + &:hover { + background: linear-gradient( + 135deg, + var(--primary-color-dark) 0%, + var(--primary-color) 100% + ); + } + } + + &.btn-secondary { + background: linear-gradient( + 135deg, + var(--primary-color-light) 0%, + var(--primary-color) 100% + ); + color: white; + + &:hover { + background: linear-gradient( + 135deg, + var(--primary-color) 0%, + var(--primary-color-dark) 100% + ); + } + } + + &.btn-danger { + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%); + color: white; + + &:hover { + background: linear-gradient(135deg, #ee5a52 0%, #d63031 100%); + } + } +} + +.message-list { + max-height: 400px; + overflow-y: auto; + border: 1px solid #e1e5e9; + border-radius: 8px; + padding: 10px; + background: #f8f9fa; + font-size: 12px; +} + +.no-messages { + text-align: center; + color: #6c757d; + font-style: italic; + padding: 20px; +} + +.message-item { + background: white; + padding: 12px 15px; + margin-bottom: 8px; + border-radius: 6px; + border-left: 4px solid var(--primary-color); + box-shadow: 0 2px 4px var(--primary-shadow-light); + font-size: 12px; + &:last-child { + margin-bottom: 0; + } +} + +.message-text { + font-family: "Courier New", monospace; + font-size: 12px; + color: #333; + word-break: break-all; + line-height: 1.4; +} + +.info-panel { + background: white; + border-radius: 15px; + padding: 25px; + box-shadow: 0 10px 30px var(--primary-shadow); + + h3 { + margin: 0 0 20px 0; + color: #333; + font-size: 1.3rem; + border-bottom: 2px solid var(--primary-color); + padding-bottom: 10px; + } +} + +.info-item { + margin-bottom: 12px; + padding: 10px; + background: #f8f9fa; + border-radius: 6px; + border-left: 3px solid var(--primary-color); + + strong { + color: #495057; + margin-right: 8px; + } + + &:last-child { + margin-bottom: 0; + } +} + +// 滚动条样式 +.message-list::-webkit-scrollbar { + width: 8px; +} + +.message-list::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +.message-list::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; + + &:hover { + background: #a8a8a8; + } +} + +// 响应式设计 +@media (max-width: 768px) { + .iframe-debug-page { + padding: 15px; + } + + .header h1 { + font-size: 2rem; + } + + .control-panel, + .message-panel, + .info-panel { + padding: 20px; + } + + .btn { + padding: 10px 16px; + font-size: 0.9rem; + } +} + +@media (max-width: 480px) { + .header h1 { + font-size: 1.8rem; + } + + .content { + gap: 15px; + } + + .control-panel, + .message-panel, + .info-panel { + padding: 15px; + } + + .button-group { + flex-direction: column; + } + + .btn { + width: 100%; + } +} + +// URL 参数区域样式 +.url-params-section { + margin-top: 20px; + padding-top: 20px; + border-top: 2px solid #e1e5e9; + + h4 { + margin: 0 0 15px 0; + color: #333; + font-size: 1.1rem; + font-weight: 600; + } +} + +.no-params { + color: #666; + font-style: italic; + text-align: center; + padding: 20px; + background: #f8f9fa; + border-radius: 8px; + border: 2px dashed #dee2e6; +} + +.params-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.param-item { + display: flex; + align-items: center; + padding: 10px 15px; + background: #f8f9fa; + border-radius: 8px; + border-left: 4px solid var(--primary-color); + transition: all 0.3s ease; + + &:hover { + background: #e9ecef; + transform: translateX(5px); + } +} + +.param-key { + font-weight: 600; + color: #333; + min-width: 80px; + margin-right: 10px; +} + +.param-value { + color: #666; + font-family: "Courier New", monospace; + word-break: break-all; + flex: 1; +} diff --git a/nkebao/src/pages/iframe/index.tsx b/nkebao/src/pages/iframe/index.tsx new file mode 100644 index 00000000..a935c6ce --- /dev/null +++ b/nkebao/src/pages/iframe/index.tsx @@ -0,0 +1,242 @@ +import React, { useState, useEffect } from "react"; +import style from "./index.module.scss"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import { LoadingOutlined, CheckCircleOutlined } from "@ant-design/icons"; +import { Input } from "antd"; +// 声明全局的 uni 对象 +declare global { + interface Window { + uni: any; + } +} + +interface Message { + type: number; // 数据类型:0数据交互 1App功能调用 + data: any; +} + +const TYPE_EMUE = { + CONNECT: 0, + DATA: 1, + FUNCTION: 2, + CONFIG: 3, +}; +const IframeDebugPage: React.FC = () => { + const [receivedMessages, setReceivedMessages] = useState([]); + const [messageId, setMessageId] = useState(0); + const [inputMessage, setInputMessage] = useState(""); + const [connectStatus, setConnectStatus] = useState(false); + + // 解析 URL 参数中的消息 + const parseUrlMessage = () => { + const search = window.location.search.substring(1); + let messageParam = null; + + if (search) { + const pairs = search.split("&"); + for (const pair of pairs) { + const [key, value] = pair.split("="); + if (key === "message" && value) { + messageParam = decodeURIComponent(value); + break; + } + } + } + + if (messageParam) { + try { + const message = JSON.parse(decodeURIComponent(messageParam)); + console.log("[存客宝]ReceiveMessage=>\n" + JSON.stringify(message)); + handleReceivedMessage(message); + // 清除URL中的message参数 + const newUrl = + window.location.pathname + + window.location.search + .replace(/[?&]message=[^&]*/, "") + .replace(/^&/, "?"); + window.history.replaceState({}, "", newUrl); + } catch (e) { + console.error("解析URL消息失败:", e); + } + } + }; + + useEffect(() => { + parseUrlMessage(); + // 监听 SDK 初始化完成事件 + }, []); + + // 处理接收到的消息 + const handleReceivedMessage = (message: Message) => { + const messageText = `[${new Date().toLocaleTimeString()}] 收到: ${JSON.stringify(message)}`; + setReceivedMessages(prev => [...prev, messageText]); + console.log("message.type", message.type); + if ([TYPE_EMUE.CONNECT].includes(message.type)) { + setConnectStatus(true); + } + }; + + // 向 App 发送消息 + const sendMessageToParent = (message: Message) => { + if (window.uni && window.uni.postMessage) { + try { + window.uni.postMessage({ + data: message, + }); + console.log("[存客宝]SendMessage=>\n" + JSON.stringify(message)); + } catch (e) { + console.error( + "[存客宝]SendMessage=>\n" + JSON.stringify(message) + "发送失败:", + e, + ); + } + } else { + console.error( + "[存客宝]SendMessage=>\n" + JSON.stringify(message) + "无法发送消息", + ); + } + }; + + // 发送自定义消息到 App + const sendCustomMessage = () => { + if (!inputMessage.trim()) return; + + const newMessageId = messageId + 1; + setMessageId(newMessageId); + + const message: Message = { + type: TYPE_EMUE.DATA, // 数据交互 + data: { + id: newMessageId, + content: inputMessage, + source: "存客宝消息源", + timestamp: Date.now(), + }, + }; + + sendMessageToParent(message); + setInputMessage(""); + }; + + // 发送测试消息到 App + const sendTestMessage = () => { + const newMessageId = messageId + 1; + setMessageId(newMessageId); + + const message: Message = { + type: TYPE_EMUE.DATA, // 数据交互 + data: { + id: newMessageId, + action: "ping", + content: `存客宝测试消息 ${newMessageId}`, + random: Math.random(), + }, + }; + + sendMessageToParent(message); + }; + + // 发送App功能调用消息 + const sendAppFunctionCall = () => { + const message: Message = { + type: 1, // App功能调用 + data: { + action: "showToast", + params: { + title: "来自H5的功能调用", + icon: "success", + }, + }, + }; + + sendMessageToParent(message); + }; + + // 清空消息列表 + const clearMessages = () => { + setInputMessage(""); + setReceivedMessages([]); + }; + + return ( + + ) : ( + + 连接中... + + + ) + } + /> + } + > +
+
+
+

接收到的消息

+
+ {receivedMessages.length === 0 ? ( +
暂无消息
+ ) : ( + receivedMessages.map((msg, index) => ( +
+ {msg} +
+ )) + )} +
+
+ +
+

控制面板

+ +
+ setInputMessage(e.target.value)} + placeholder="输入要发送的消息" + /> + +
+ +
+ + + +
+
+
+
+
+ ); +}; + +export default IframeDebugPage; diff --git a/nkebao/src/pages/iframe/init.tsx b/nkebao/src/pages/iframe/init.tsx new file mode 100644 index 00000000..b49e01db --- /dev/null +++ b/nkebao/src/pages/iframe/init.tsx @@ -0,0 +1,227 @@ +import React, { useState, useEffect } from "react"; +import style from "./index.module.scss"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import { Input } from "antd"; +import { useNavigate } from "react-router-dom"; + +// 声明全局的 uni 对象 +declare global { + interface Window { + uni: any; + } +} + +interface Message { + type: number; // 数据类型:0数据交互 1App功能调用 + data: any; +} + +const TYPE_EMUE = { + CONNECT: 0, + DATA: 1, + FUNCTION: 2, + CONFIG: 3, +}; +const IframeDebugPage: React.FC = () => { + const [receivedMessages, setReceivedMessages] = useState([]); + const [messageId, setMessageId] = useState(0); + const [inputMessage, setInputMessage] = useState(""); + const navigate = useNavigate(); + // 解析 URL 参数中的消息 + const parseUrlMessage = () => { + const search = window.location.search.substring(1); + let messageParam = null; + + if (search) { + const pairs = search.split("&"); + for (const pair of pairs) { + const [key, value] = pair.split("="); + if (key === "message" && value) { + messageParam = decodeURIComponent(value); + break; + } + } + } + + if (messageParam) { + try { + const message = JSON.parse(decodeURIComponent(messageParam)); + console.log("[存客宝]ReceiveMessage=>\n" + JSON.stringify(message)); + handleReceivedMessage(message); + // 清除URL中的message参数 + const newUrl = + window.location.pathname + + window.location.search + .replace(/[?&]message=[^&]*/, "") + .replace(/^&/, "?"); + window.history.replaceState({}, "", newUrl); + } catch (e) { + console.error("解析URL消息失败:", e); + } + } + }; + + useEffect(() => { + parseUrlMessage(); + // 监听 SDK 初始化完成事件 + }, []); + + // 处理接收到的消息 + const handleReceivedMessage = (message: Message) => { + const messageText = `[${new Date().toLocaleTimeString()}] 收到: ${JSON.stringify(message)}`; + setReceivedMessages(prev => [...prev, messageText]); + console.log("message.type", message.type); + if ([TYPE_EMUE.CONFIG].includes(message.type)) { + localStorage.setItem("paddingTop", message.data.paddingTop); + navigate("/"); + } + }; + + // 向 App 发送消息 + const sendMessageToParent = (message: Message) => { + if (window.uni && window.uni.postMessage) { + try { + window.uni.postMessage({ + data: message, + }); + console.log("[存客宝]SendMessage=>\n" + JSON.stringify(message)); + } catch (e) { + console.error( + "[存客宝]SendMessage=>\n" + JSON.stringify(message) + "发送失败:", + e, + ); + } + } else { + console.error( + "[存客宝]SendMessage=>\n" + JSON.stringify(message) + "无法发送消息", + ); + } + }; + + // 发送自定义消息到 App + const sendCustomMessage = () => { + if (!inputMessage.trim()) return; + + const newMessageId = messageId + 1; + setMessageId(newMessageId); + + const message: Message = { + type: TYPE_EMUE.DATA, // 数据交互 + data: { + id: newMessageId, + content: inputMessage, + source: "存客宝消息源", + timestamp: Date.now(), + }, + }; + + sendMessageToParent(message); + setInputMessage(""); + }; + + // 发送测试消息到 App + const sendTestMessage = () => { + const newMessageId = messageId + 1; + setMessageId(newMessageId); + + const message: Message = { + type: TYPE_EMUE.DATA, // 数据交互 + data: { + id: newMessageId, + action: "ping", + content: `存客宝测试消息 ${newMessageId}`, + random: Math.random(), + }, + }; + + sendMessageToParent(message); + }; + + // 发送App功能调用消息 + const sendAppFunctionCall = () => { + const message: Message = { + type: 1, // App功能调用 + data: { + action: "showToast", + params: { + title: "来自H5的功能调用", + icon: "success", + }, + }, + }; + + sendMessageToParent(message); + }; + + // 清空消息列表 + const clearMessages = () => { + setInputMessage(""); + setReceivedMessages([]); + }; + + return ( + }> +
+
+
+

接收到的消息

+
+ {receivedMessages.length === 0 ? ( +
暂无消息
+ ) : ( + receivedMessages.map((msg, index) => ( +
+ {msg} +
+ )) + )} +
+
+ +
+

控制面板

+ +
+ setInputMessage(e.target.value)} + placeholder="输入要发送的消息" + /> + +
+ +
+ + + +
+
+
+
+
+ ); +}; + +export default IframeDebugPage; diff --git a/nkebao/src/pages/login/login.module.scss b/nkebao/src/pages/login/login.module.scss index 28a3a722..7574341f 100644 --- a/nkebao/src/pages/login/login.module.scss +++ b/nkebao/src/pages/login/login.module.scss @@ -116,10 +116,6 @@ margin: 0; } -.form-container { - margin-bottom: 20px; -} - // 标签页样式 .tab-container { display: flex; diff --git a/nkebao/src/pages/login/login.tsx b/nkebao/src/pages/login/login.tsx index c1b52807..1bc13773 100644 --- a/nkebao/src/pages/login/login.tsx +++ b/nkebao/src/pages/login/login.tsx @@ -9,6 +9,8 @@ import { import { useUserStore } from "@/store/module/user"; import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api"; import style from "./login.module.scss"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; const Login: React.FC = () => { const [form] = Form.useForm(); @@ -137,9 +139,11 @@ const Login: React.FC = () => { const handleAppleLogin = () => { Toast.show({ content: "Apple登录功能开发中", position: "top" }); }; - + const paddingTop = localStorage.getItem("paddingTop") || "44px"; return (
+
+
{/* 背景装饰 */}
diff --git a/nkebao/src/pages/mobile/home/index.tsx b/nkebao/src/pages/mobile/home/index.tsx index 25fadb07..3293455c 100644 --- a/nkebao/src/pages/mobile/home/index.tsx +++ b/nkebao/src/pages/mobile/home/index.tsx @@ -1,10 +1,8 @@ import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { NavBar } from "antd-mobile"; +import NavCommon from "@/components/NavCommon"; import { - BellOutlined, MobileOutlined, - UserOutlined, MessageOutlined, TeamOutlined, RiseOutlined, @@ -27,13 +25,6 @@ interface DashboardData { aliveWechatNum?: number; } -interface TodayStatsData { - momentsNum?: number; - groupPushNum?: number; - passRate?: string; - sysActive?: string; -} - interface SevenDayStatsData { date?: string[]; allNum?: number[]; @@ -146,13 +137,7 @@ const Home: React.FC = () => { return ( -
- 存客宝 -
- - } + header={} title="存客宝" />} footer={} loading={isLoading} > diff --git a/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx b/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx index 9a70464c..71398a96 100644 --- a/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx +++ b/nkebao/src/pages/mobile/mine/devices/DeviceDetail.tsx @@ -1,7 +1,15 @@ import React, { useEffect, useState, useCallback, useRef } from "react"; import { useParams, useNavigate } from "react-router-dom"; -import { NavBar, Tabs, Switch, Toast, SpinLoading, Button } from "antd-mobile"; -import { SettingOutlined, RedoOutlined } from "@ant-design/icons"; +import { + NavBar, + Tabs, + Switch, + Toast, + SpinLoading, + Button, + Avatar, +} from "antd-mobile"; +import { SettingOutlined, RedoOutlined, UserOutlined } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import { fetchDeviceDetail, @@ -262,15 +270,32 @@ const DeviceDetail: React.FC = () => { navigate(`/wechat-accounts/detail/${acc.wechatId}`); }} > - {acc.nickname} + +
+ } />
{acc.nickname}
diff --git a/nkebao/src/pages/mobile/mine/main/index.tsx b/nkebao/src/pages/mobile/mine/main/index.tsx index 605dc1ec..95fb4090 100644 --- a/nkebao/src/pages/mobile/mine/main/index.tsx +++ b/nkebao/src/pages/mobile/mine/main/index.tsx @@ -1,13 +1,11 @@ import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { Card, NavBar, List, Button, Dialog, Toast } from "antd-mobile"; +import { Card, NavBar, List, Button } from "antd-mobile"; import { - LogoutOutlined, PhoneOutlined, MessageOutlined, DatabaseOutlined, FolderOpenOutlined, - BellOutlined, SettingOutlined, } from "@ant-design/icons"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; @@ -15,6 +13,7 @@ import Layout from "@/components/Layout/Layout"; import style from "./index.module.scss"; import { useUserStore } from "@/store/module/user"; import { getDashboard } from "./api"; +import NavCommon from "@/components/NavCommon"; const Mine: React.FC = () => { const navigate = useNavigate(); const { user } = useUserStore(); @@ -25,7 +24,6 @@ const Mine: React.FC = () => { content: 156, balance: 0, }); - const [showLogoutDialog, setShowLogoutDialog] = useState(false); // 用户信息 const currentUserInfo = { @@ -102,19 +100,6 @@ const Mine: React.FC = () => { loadStats(); }, []); - const handleLogout = () => { - // 清除本地存储的用户信息 - localStorage.removeItem("token"); - localStorage.removeItem("token_expired"); - localStorage.removeItem("userInfo"); - setShowLogoutDialog(false); - navigate("/login"); - Toast.show({ - content: "退出成功", - position: "top", - }); - }; - const handleFunctionClick = (path: string) => { navigate(path); }; @@ -140,13 +125,7 @@ const Mine: React.FC = () => { return ( -
- 我的 -
- - } + header={} footer={} >
@@ -227,42 +206,7 @@ const Mine: React.FC = () => { ))} - - {/* 退出登录按钮 */} -
- - {/* 退出登录确认对话框 */} - setShowLogoutDialog(false)} - />
); }; diff --git a/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx b/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx index bdd371b9..9ae89537 100644 --- a/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx +++ b/nkebao/src/pages/mobile/mine/setting/SecuritySetting.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { NavBar, List, Button, Dialog, Toast, Card, Input } from "antd-mobile"; +import { NavBar, List, Dialog, Toast, Card, Input } from "antd-mobile"; import { LockOutlined, MobileOutlined, @@ -10,7 +10,7 @@ import { import Layout from "@/components/Layout/Layout"; import { useUserStore } from "@/store/module/user"; import style from "./index.module.scss"; - +import NavCommon from "@/components/NavCommon"; const SecuritySetting: React.FC = () => { const navigate = useNavigate(); const { user } = useUserStore(); @@ -92,15 +92,7 @@ const SecuritySetting: React.FC = () => { ]; return ( - navigate(-1)} style={{ background: "#fff" }}> - - 安全设置 - - - } - > + }>
{/* 安全提示卡片 */} @@ -125,9 +117,7 @@ const SecuritySetting: React.FC = () => { prefix={item.icon} title={item.title} description={item.description} - extra={} onClick={item.onClick} - arrow /> ))} @@ -163,30 +153,36 @@ const SecuritySetting: React.FC = () => { title="修改密码" content={
- - setPasswordForm(prev => ({ ...prev, oldPassword: value })) - } - /> - - setPasswordForm(prev => ({ ...prev, newPassword: value })) - } - /> - - setPasswordForm(prev => ({ ...prev, confirmPassword: value })) - } - /> +
+ + setPasswordForm(prev => ({ ...prev, oldPassword: value })) + } + /> +
+
+ + setPasswordForm(prev => ({ ...prev, newPassword: value })) + } + /> +
+
+ + setPasswordForm(prev => ({ ...prev, confirmPassword: value })) + } + /> +
} closeOnAction diff --git a/nkebao/src/pages/mobile/mine/setting/index.module.scss b/nkebao/src/pages/mobile/mine/setting/index.module.scss index d3cc2ff5..ef57738d 100644 --- a/nkebao/src/pages/mobile/mine/setting/index.module.scss +++ b/nkebao/src/pages/mobile/mine/setting/index.module.scss @@ -1,30 +1,39 @@ .save-buttons { padding: 12px; - background: #fff; } .setting-page { padding: 12px; .user-card { - margin-bottom: 12px; - border-radius: 12px; + margin-bottom: 16px; + border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + background: linear-gradient( + 135deg, + var(--primary-color) 0%, + var(--primary-color-dark) 100% + ); + position: relative; .user-info { display: flex; align-items: center; - padding: 16px; + padding: 20px; gap: 16px; + position: relative; + z-index: 1; .avatar { - width: 60px; - height: 60px; - border-radius: 30px; + width: 70px; + height: 70px; + border-radius: 35px; overflow: hidden; flex-shrink: 0; - background: #f0f0f0; - border: 2px solid #e0e0e0; + background: rgba(255, 255, 255, 0.2); + border: 3px solid rgba(255, 255, 255, 0.3); + position: relative; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); img { width: 100%; @@ -35,13 +44,18 @@ .avatar-placeholder { width: 100%; height: 100%; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient( + 135deg, + var(--primary-color) 0%, + var(--primary-color-dark) 100% + ); display: flex; align-items: center; justify-content: center; color: white; - font-size: 24px; + font-size: 28px; font-weight: bold; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } } @@ -50,19 +64,20 @@ min-width: 0; .username { - font-size: 18px; - font-weight: 600; - color: #333; - margin-bottom: 4px; + font-size: 20px; + font-weight: 700; + color: white; + margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .account { font-size: 14px; - color: #666; - margin-bottom: 4px; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -70,99 +85,205 @@ .role { font-size: 12px; - color: #999; - background: #f0f0f0; - padding: 2px 8px; - border-radius: 10px; + color: rgba(255, 255, 255, 0.9); + background: rgba(255, 255, 255, 0.2); + padding: 4px 12px; + border-radius: 12px; display: inline-block; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); } } + + .user-actions { + flex-shrink: 0; + } } } .setting-group { - margin-bottom: 12px; - border-radius: 12px; + margin-bottom: 16px; + border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + background: white; + border: 1px solid rgba(0, 0, 0, 0.05); .group-title { - padding: 12px 16px 8px; - font-size: 14px; - font-weight: 600; - color: #666; - background: #fafafa; + padding: 16px 20px 12px; + font-size: 15px; + font-weight: 700; + color: #333; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-bottom: 1px solid #f0f0f0; + display: flex; + align-items: center; + gap: 8px; + + .group-icon { + font-size: 16px; + } } - :global(.adm-list) { - --border-inner: solid 1px #f0f0f0; - --border-top: none; - --border-bottom: none; - --adm-font-size-main: 16px; - --adm-color-text: #333; - --adm-color-text-secondary: #666; + .setting-list { + :global(.adm-list) { + --border-inner: solid 1px #f0f0f0; + --border-top: none; + --border-bottom: none; + --adm-font-size-main: 16px; + --adm-color-text: #333; + --adm-color-text-secondary: #666; - .adm-list-item { - padding: 16px; - min-height: 56px; + .adm-list-item { + padding: 16px 20px; + min-height: 64px; + transition: all 0.2s ease; + position: relative; - .adm-list-item-content { - padding: 0; - } - - .adm-list-item-content-prefix { - margin-right: 12px; - font-size: 20px; - color: var(--primary-color); - } - - .adm-list-item-content-main { - flex: 1; - min-width: 0; - - .adm-list-item-content-main-title { - font-size: 16px; - font-weight: 500; - color: #333; - margin-bottom: 4px; + &:hover { + background: #f8f9fa; + transform: translateX(4px); } - .adm-list-item-content-main-description { - font-size: 14px; - color: #666; - line-height: 1.4; + &:active { + background: #e9ecef; + transform: scale(0.98); } - } - .adm-list-item-content-extra { - margin-left: 8px; - } + .adm-list-item-content { + padding: 0; + } - &:last-child { - border-bottom: none; - } + .adm-list-item-content-prefix { + margin-right: 16px; + } - &:active { - background-color: #f5f5f5; + .adm-list-item-content-main { + flex: 1; + min-width: 0; + + .adm-list-item-content-main-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 4px; + } + + .adm-list-item-content-main-description { + font-size: 14px; + color: #666; + line-height: 1.4; + } + } + + .adm-list-item-content-extra { + margin-left: 8px; + } + + &:last-child { + border-bottom: none; + } } } } } + .setting-icon { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + background: rgba(24, 144, 255, 0.1); + color: var(--primary-color); + transition: all 0.2s ease; + + &:hover { + transform: scale(1.1); + background: rgba(24, 144, 255, 0.2); + } + } + + .setting-title { + display: flex; + align-items: center; + gap: 8px; + } + + .setting-badge { + background: #ff4d4f; + color: white; + font-size: 10px; + padding: 2px 6px; + border-radius: 8px; + font-weight: 500; + } + + .setting-item { + transition: all 0.2s ease; + } + .version-info { - text-align: center; - padding: 24px 16px; - color: #999; - font-size: 12px; - line-height: 1.6; + .version-card { + background: white; + border-radius: 16px; + padding: 24px; + margin-bottom: 16px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + gap: 16px; - span { - display: block; - margin-bottom: 4px; + .app-logo { + flex-shrink: 0; + width: 60px; + height: 60px; + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } - &:last-child { - margin-bottom: 0; + .version-details { + flex: 1; + text-align: left; + + .app-name { + font-size: 18px; + font-weight: 700; + color: #333; + margin-bottom: 4px; + } + + .version-text { + font-size: 14px; + color: #666; + margin-bottom: 2px; + } + + .build-info { + font-size: 12px; + color: #999; + } + } + } + + .copyright { + color: #999; + font-size: 12px; + line-height: 1.6; + text-align: center; + span { + display: block; + margin-bottom: 4px; + + &:last-child { + margin-bottom: 0; + color: #ccc; + } } } } @@ -252,7 +373,9 @@ flex-direction: column; gap: 12px; padding: 16px 0; - + .line { + margin-bottom: 12px; + } :global(.adm-input) { border: 1px solid #d9d9d9; border-radius: 8px; diff --git a/nkebao/src/pages/mobile/mine/setting/index.tsx b/nkebao/src/pages/mobile/mine/setting/index.tsx index 3f728d75..8090bdc2 100644 --- a/nkebao/src/pages/mobile/mine/setting/index.tsx +++ b/nkebao/src/pages/mobile/mine/setting/index.tsx @@ -7,12 +7,15 @@ import { InfoCircleOutlined, LogoutOutlined, SettingOutlined, + LockOutlined, + HeartOutlined, + StarOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import { useUserStore } from "@/store/module/user"; import { useSettingsStore } from "@/store/module/settings"; import style from "./index.module.scss"; - +import NavCommon from "@/components/NavCommon"; interface SettingItem { id: string; title: string; @@ -22,6 +25,8 @@ interface SettingItem { value?: boolean; path?: string; onClick?: () => void; + badge?: string; + color?: string; } const Setting: React.FC = () => { @@ -29,15 +34,11 @@ const Setting: React.FC = () => { const { user, logout } = useUserStore(); const { settings, updateSetting } = useSettingsStore(); const [showLogoutDialog, setShowLogoutDialog] = useState(false); + const [avatarError, setAvatarError] = useState(false); - // 处理开关变化 - const handleSwitchChange = (key: keyof typeof settings, value: boolean) => { - updateSetting(key, value); - - Toast.show({ - content: `设置已${value ? "开启" : "关闭"}`, - position: "top", - }); + // 处理头像加载错误 + const handleAvatarError = () => { + setAvatarError(true); }; // 退出登录 @@ -78,6 +79,7 @@ const Setting: React.FC = () => { icon: , type: "navigate", path: "/userSet", + color: "var(--primary-color)", }, { id: "security", @@ -86,12 +88,22 @@ const Setting: React.FC = () => { icon: , type: "navigate", path: "/security", + color: "var(--primary-color)", }, ], }, { - title: "其他", + title: "应用设置", items: [ + { + id: "privacy", + title: "隐私保护", + description: "数据隐私、权限管理", + icon: , + type: "navigate", + path: "/privacy", + color: "var(--primary-color)", + }, { id: "clearCache", title: "清除缓存", @@ -99,15 +111,14 @@ const Setting: React.FC = () => { icon: , type: "button", onClick: handleClearCache, + color: "var(--primary-color)", + badge: "2.3MB", }, - { - id: "privacy", - title: "用户隐私协议", - description: "查看用户隐私协议和数据处理说明", - icon: , - type: "navigate", - path: "/privacy", - }, + ], + }, + { + title: "其他", + items: [ { id: "about", title: "关于我们", @@ -115,6 +126,7 @@ const Setting: React.FC = () => { icon: , type: "navigate", path: "/about", + color: "var(--primary-color)", }, { id: "logout", @@ -123,6 +135,7 @@ const Setting: React.FC = () => { icon: , type: "button", onClick: () => setShowLogoutDialog(true), + color: "#ff4d4f", }, ], }, @@ -143,37 +156,55 @@ const Setting: React.FC = () => { return ( + {item.icon} +
+ } + title={ +
+ {item.title} + {item.badge && ( + {item.badge} + )} +
+ } description={item.description} extra={ item.type === "switch" ? ( - item.onClick?.()} /> + item.onClick?.()} + style={ + { + "--checked-color": item.color || "var(--primary-color)", + } as React.CSSProperties + } + /> ) : null } onClick={handleClick} arrow={item.type === "navigate"} + className={style["setting-item"]} /> ); }; return ( - navigate(-1)} style={{ background: "#fff" }}> - - 设置 - - - } - > + }>
{/* 用户信息卡片 */}
- {user?.avatar ? ( - 头像 + {user?.avatar && !avatarError ? ( + 头像 ) : (
{user?.username?.charAt(0) || "用"} @@ -191,21 +222,54 @@ const Setting: React.FC = () => { {user?.isAdmin === 1 ? "管理员" : "普通用户"}
+
+ +
{/* 设置列表 */} {settingGroups.map((group, groupIndex) => ( -
{group.title}
- {group.items.map(renderSettingItem)} +
+ ⚙️ + {group.title} +
+ + {group.items.map(renderSettingItem)} +
))} {/* 版本信息 */}
- 版本 1.0.0 - © 2024 存客宝管理系统 +
+
+ +
+
+
存客宝
+
版本 3.0.0
+
Build 2025-7-30
+
+
+
+ © 2024 存客宝管理系统 + 让客户管理更简单 +
diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts b/nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts index 0a0a1e92..6d4c4b83 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts +++ b/nkebao/src/pages/mobile/mine/traffic-pool/detail/api.ts @@ -1,13 +1,7 @@ import request from "@/api/request"; -import type { - TrafficPoolUserDetail, - UserJourneyResponse, - UserTagsResponse, -} from "./data"; +import type { UserTagsResponse } from "./data"; -export function getTrafficPoolDetail( - wechatId: string, -): Promise { +export function getTrafficPoolDetail(wechatId: string) { return request("/v1/wechats/getWechatInfo", { wechatId }, "GET"); } @@ -16,7 +10,7 @@ export function getUserJourney(params: { page: number; pageSize: number; userId: string; -}): Promise { +}) { return request("/v1/traffic/pool/getUserJourney", params, "GET"); } diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts b/nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts index fd55131e..43615992 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts +++ b/nkebao/src/pages/mobile/mine/traffic-pool/detail/data.ts @@ -30,3 +30,79 @@ export interface TrafficPoolUserDetail { value?: number; }>; } + +// 扩展的用户详情类型 +export interface ExtendedUserDetail extends TrafficPoolUserDetail { + userInfo: { + nickname: string; + avatar: string; + wechatId: string; + friendShip: { + totalFriend: number; + maleFriend: number; + femaleFriend: number; + unknowFriend: number; + }; + }; + rfmScore: { + recency: number; + frequency: number; + monetary: number; + totalScore: number; + }; + trafficPools: { + currentPool: string; + availablePools: string[]; + }; + userTags: Array<{ + id: string; + name: string; + color: string; + type: string; + }>; + valueTags: Array<{ + id: string; + name: string; + color: string; + icon: string; + rfmScore: number; + valueLevel: string; + }>; + restrictions?: Array<{ + id: string; + reason: string; + level: number; + date: number | null; + }>; +} + +// 互动记录类型 +export interface InteractionRecord { + id: string; + type: string; + content: string; + timestamp: string; + value?: number; +} + +// 用户旅程记录类型 +export interface UserJourneyRecord { + id: string; + type: number; + remark: string; + createTime: string; +} + +// 用户标签响应类型 +export interface UserTagsResponse { + wechat: string[]; + siteLabels: UserTagItem[]; +} + +// 用户标签项类型 +export interface UserTagItem { + id: string; + name: string; + color?: string; + type?: string; +} diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss b/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss index 60165acb..6b896216 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss +++ b/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.module.scss @@ -47,6 +47,18 @@ flex-shrink: 0; } + .avatarFallback { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + font-size: 24px; + border-radius: 50%; + } + .userDetails { flex: 1; min-width: 0; @@ -343,7 +355,7 @@ flex-direction: column; align-items: center; justify-content: center; - padding: 60px 16px; + padding: 20px 16px; text-align: center; } @@ -353,14 +365,14 @@ } .emptyText { - font-size: 16px; + font-size: 14px; color: #666; - margin-bottom: 8px; + margin-bottom: 4px; font-weight: 500; } .emptyDesc { - font-size: 14px; + font-size: 12px; color: #999; line-height: 1.4; } diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx b/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx index 88fd1c41..4c7ce51b 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx +++ b/nkebao/src/pages/mobile/mine/traffic-pool/detail/index.tsx @@ -1,20 +1,9 @@ import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; -import { - Card, - Button, - Avatar, - Tag, - Tabs, - List, - Badge, - SpinLoading, -} from "antd-mobile"; +import { Card, Button, Avatar, Tag, List, SpinLoading } from "antd-mobile"; import { UserOutlined, CrownOutlined, - PlusOutlined, - CloseOutlined, EyeOutlined, DollarOutlined, MobileOutlined, @@ -26,9 +15,7 @@ import Layout from "@/components/Layout/Layout"; import NavCommon from "@/components/NavCommon"; import { getTrafficPoolDetail, getUserJourney, getUserTags } from "./api"; import type { - TrafficPoolUserDetail, ExtendedUserDetail, - InteractionRecord, UserJourneyRecord, UserTagsResponse, UserTagItem, @@ -52,6 +39,7 @@ const TrafficPoolDetail: React.FC = () => { // 用户标签相关状态 const [tagsLoading, setTagsLoading] = useState(false); const [userTagsList, setUserTagsList] = useState([]); + const [wechatTagsList, setWechatTagsList] = useState([]); useEffect(() => { if (!wxid) return; @@ -61,6 +49,8 @@ const TrafficPoolDetail: React.FC = () => { // 将API数据转换为扩展的用户详情数据 const extendedUser: ExtendedUserDetail = { ...res, + // 添加userInfo属性 + userInfo: res.userInfo, // 模拟RFM评分数据 rfmScore: { recency: 5, @@ -92,6 +82,8 @@ const TrafficPoolDetail: React.FC = () => { }, ], }; + console.log(extendedUser); + setUser(extendedUser); }) .finally(() => setLoading(false)); @@ -131,6 +123,7 @@ const TrafficPoolDetail: React.FC = () => { try { const response: UserTagsResponse = await getUserTags(userId); setUserTagsList(response.siteLabels || []); + setWechatTagsList(response.wechat || []); } catch (error) { console.error("获取用户标签失败:", error); } finally { @@ -149,10 +142,6 @@ const TrafficPoolDetail: React.FC = () => { } }; - const handleClose = () => { - navigate(-1); - }; - const getJourneyTypeIcon = (type: number) => { switch (type) { case 0: // 浏览 @@ -207,32 +196,6 @@ const TrafficPoolDetail: React.FC = () => { } }; - const formatCurrency = (amount: number) => { - return `¥${amount.toLocaleString()}`; - }; - - const getGenderText = (gender: number) => { - switch (gender) { - case 1: - return "男"; - case 2: - return "女"; - default: - return "未知"; - } - }; - - const getGenderColor = (gender: number) => { - switch (gender) { - case 1: - return "#1677ff"; - case 2: - return "#eb2f96"; - default: - return "#999"; - } - }; - const getRestrictionLevelText = (level: number) => { switch (level) { case 1: @@ -297,7 +260,11 @@ const TrafficPoolDetail: React.FC = () => { } + fallback={ +
+ +
+ } />
{user.userInfo.nickname}
@@ -617,20 +584,22 @@ const TrafficPoolDetail: React.FC = () => { {activeTab === "tags" && (
- {/* 用户标签 */} - + {/* 站内标签 */} + {tagsLoading && userTagsList.length === 0 ? (
- +
加载中...
) : userTagsList.length === 0 ? (
- + +
+
暂无站内标签
+
+ 该用户还没有任何站内标签
-
暂无用户标签
-
该用户还没有任何标签
) : (
@@ -648,6 +617,39 @@ const TrafficPoolDetail: React.FC = () => { )} + {/* 微信标签 */} + + {tagsLoading && wechatTagsList.length === 0 ? ( +
+ +
加载中...
+
+ ) : wechatTagsList.length === 0 ? ( +
+
+ +
+
暂无微信标签
+
+ 该用户还没有任何微信标签 +
+
+ ) : ( +
+ {wechatTagsList.map((tag, index) => ( + + {tag} + + ))} +
+ )} +
+ {/* 价值标签 */} {user.valueTags && user.valueTags.length > 0 ? ( diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts b/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts index d8bc9af4..0a6162a8 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts +++ b/nkebao/src/pages/mobile/mine/traffic-pool/list/api.ts @@ -1,6 +1,4 @@ import request from "@/api/request"; -import type { TrafficPoolListResponse, DeviceOption } from "./data"; -import { fetchDeviceList } from "@/pages/guide/api"; // 获取流量池列表 export function fetchTrafficPoolList(params: { @@ -11,16 +9,6 @@ export function fetchTrafficPoolList(params: { return request("/v1/traffic/pool", params, "GET"); } -// 获取设备列表(真实接口) -export async function fetchDeviceOptions(): Promise { - const res = await fetchDeviceList({ page: 1, limit: 100 }); - // 假设返回 { list: [{ id, name, ... }], ... } - return (res.list || []).map((item: any) => ({ - id: String(item.id), - name: item.name, - })); -} - // 获取分组列表(如无真实接口可用mock) export async function fetchPackageOptions(): Promise { // TODO: 替换为真实接口 diff --git a/nkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx b/nkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx index 1cd11561..7a84040a 100644 --- a/nkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx +++ b/nkebao/src/pages/mobile/mine/traffic-pool/list/dataAnyx.tsx @@ -1,9 +1,5 @@ import { useState, useEffect, useMemo } from "react"; -import { - fetchTrafficPoolList, - fetchDeviceOptions, - fetchPackageOptions, -} from "./api"; +import { fetchTrafficPoolList, fetchPackageOptions } from "./api"; import type { TrafficPoolUser, DeviceOption, @@ -69,7 +65,6 @@ export function useTrafficPoolListLogic() { // 获取筛选项 useEffect(() => { - fetchDeviceOptions().then(setDeviceOptions); fetchPackageOptions().then(setPackageOptions); }, []); diff --git a/nkebao/src/pages/mobile/scenarios/list/index.module.scss b/nkebao/src/pages/mobile/scenarios/list/index.module.scss index 60c5c10a..d200fddb 100644 --- a/nkebao/src/pages/mobile/scenarios/list/index.module.scss +++ b/nkebao/src/pages/mobile/scenarios/list/index.module.scss @@ -35,7 +35,13 @@ min-height: 100vh; padding: 0 0 60px 0; } - +.error { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 50px 30px; +} // 错误提示 .error-notice { margin-bottom: 12px; diff --git a/nkebao/src/pages/mobile/scenarios/list/index.tsx b/nkebao/src/pages/mobile/scenarios/list/index.tsx index 9df19b24..0b50536c 100644 --- a/nkebao/src/pages/mobile/scenarios/list/index.tsx +++ b/nkebao/src/pages/mobile/scenarios/list/index.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { NavBar, Button, Toast } from "antd-mobile"; +import { Button, Toast } from "antd-mobile"; import { PlusOutlined, RiseOutlined } from "@ant-design/icons"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; +import NavCommon from "@/components/NavCommon"; import Layout from "@/components/Layout/Layout"; import { getScenarios } from "./api"; import style from "./index.module.scss"; @@ -17,17 +18,6 @@ interface Scenario { status: number; } -const scenarioDescriptions: Record = { - douyin: "通过抖音平台进行精准获客", - xiaohongshu: "利用小红书平台进行内容营销获客", - gongzhonghao: "通过微信公众号进行获客", - haibao: "通过海报分享进行获客", - phone: "通过电话营销进行获客", - weixinqun: "通过微信群进行获客", - payment: "通过付款码进行获客", - api: "通过API接口进行获客", -}; - const Scene: React.FC = () => { const navigate = useNavigate(); const [scenarios, setScenarios] = useState([]); @@ -79,13 +69,17 @@ const Scene: React.FC = () => { return ( -
场景获客
- - + } + title="场景获客" + right={ + + } + /> } + footer={} >
{error}
@@ -101,10 +95,9 @@ const Scene: React.FC = () => { 场景获客
} + title={""} right={ } - > + /> } footer={} > diff --git a/nkebao/src/pages/mobile/scenarios/plan/list/index.module.scss b/nkebao/src/pages/mobile/scenarios/plan/list/index.module.scss index ee58c81c..6b4ca580 100644 --- a/nkebao/src/pages/mobile/scenarios/plan/list/index.module.scss +++ b/nkebao/src/pages/mobile/scenarios/plan/list/index.module.scss @@ -39,6 +39,47 @@ gap: 12px; } +.pagination-container { + display: flex; + justify-content: center; + padding: 14px 0; + background: white; + border-radius: 12px; + margin-top: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + :global(.ant-pagination) { + .ant-pagination-item { + border-radius: 6px; + border: 1px solid #d9d9d9; + + &:hover { + border-color: var(--primary-color); + } + + &.ant-pagination-item-active { + background: var(--primary-color); + border-color: var(--primary-color); + + a { + color: white; + } + } + } + + .ant-pagination-prev, + .ant-pagination-next { + border-radius: 6px; + border: 1px solid #d9d9d9; + + &:hover { + border-color: var(--primary-color); + color: var(--primary-color); + } + } + } +} + .plan-item { background: white; border-radius: 12px; diff --git a/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx b/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx index f1bd371c..cac548d8 100644 --- a/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx +++ b/nkebao/src/pages/mobile/scenarios/plan/list/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { - NavBar, Button, Toast, SpinLoading, @@ -10,7 +9,7 @@ import { Card, Tag, } from "antd-mobile"; -import { Input } from "antd"; +import { Input, Pagination } from "antd"; import { PlusOutlined, CopyOutlined, @@ -22,7 +21,6 @@ import { EditOutlined, MoreOutlined, ClockCircleOutlined, - DownOutlined, } from "@ant-design/icons"; import NavCommon from "@/components/NavCommon"; import Layout from "@/components/Layout/Layout"; @@ -157,6 +155,12 @@ const ScenarioList: React.FC = () => { await fetchPlanList(currentPage + 1, true); }; + // 分页改变处理 + const handlePageChange = async (page: number) => { + setCurrentPage(page); + await fetchPlanList(page, false); + }; + const handleCopyPlan = async (taskId: string) => { const taskToCopy = tasks.find(task => task.id === taskId); if (!taskToCopy) return; @@ -252,6 +256,21 @@ const ScenarioList: React.FC = () => { } }; + // 卡片点击处理 - 执行二维码动作 + const handleCardClick = (taskId: string, event: React.MouseEvent) => { + // 检查点击是否在更多按钮区域内 + const target = event.target as HTMLElement; + const moreButton = target.closest(`.${style["more-btn"]}`); + + // 如果点击的是更多按钮或其子元素,不执行卡片点击动作 + if (moreButton) { + return; + } + + // 执行二维码动作 + handleShowQrCode(taskId); + }; + const getStatusColor = (status: number) => { switch (status) { case 1: @@ -389,6 +408,18 @@ const ScenarioList: React.FC = () => { } loading={loading} + footer={ +
+ +
+ } >
{/* 计划列表 */} @@ -409,7 +440,11 @@ const ScenarioList: React.FC = () => { ) : ( <> {filteredTasks.map(task => ( - + handleCardClick(task.id, e)} + > {/* 头部:标题、状态和操作菜单 */}
{task.name}
@@ -421,7 +456,10 @@ const ScenarioList: React.FC = () => { size="mini" fill="none" className={style["more-btn"]} - onClick={() => setShowActionMenu(task.id)} + onClick={e => { + e.stopPropagation(); // 阻止事件冒泡 + setShowActionMenu(task.id); + }} > @@ -465,39 +503,6 @@ const ScenarioList: React.FC = () => {
))} - - {/* 加载更多按钮 */} - {hasMore && ( -
- -
- )} - - {/* 没有更多数据提示 */} - {!hasMore && filteredTasks.length > 0 && ( -
- 没有更多数据了 -
- )} )}
diff --git a/nkebao/src/pages/mobile/test/index.tsx b/nkebao/src/pages/mobile/test/index.tsx new file mode 100644 index 00000000..5ddee21e --- /dev/null +++ b/nkebao/src/pages/mobile/test/index.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import { Card, Button, Space, Typography, Tag } from "antd"; +import { MessageOutlined, SelectOutlined } from "@ant-design/icons"; +import { useNavigate } from "react-router-dom"; +import { isDevelopment } from "@/utils/env"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; + +const { Title, Text } = Typography; + +const TestIndex: React.FC = () => { + const navigate = useNavigate(); + + return ( + }> +
+ + 测试页面 + {isDevelopment && ( + <Tag color="orange" style={{ marginLeft: 8, fontSize: "12px" }}> + 开发环境 + </Tag> + )} + + + + + + + + + + + + + 这里提供各种功能的测试页面,方便开发和调试。 + + +
+
+ ); +}; + +export default TestIndex; diff --git a/nkebao/src/pages/mobile/component-test/index.tsx b/nkebao/src/pages/mobile/test/select.tsx similarity index 93% rename from nkebao/src/pages/mobile/component-test/index.tsx rename to nkebao/src/pages/mobile/test/select.tsx index 1334cda6..c8127880 100644 --- a/nkebao/src/pages/mobile/component-test/index.tsx +++ b/nkebao/src/pages/mobile/test/select.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { NavBar, Tabs } from "antd-mobile"; +import { NavBar, Tabs, Tag } from "antd-mobile"; import { ArrowLeftOutlined } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; import Layout from "@/components/Layout/Layout"; @@ -9,6 +9,7 @@ import FriendSelection from "@/components/FriendSelection"; import GroupSelection from "@/components/GroupSelection"; import ContentLibrarySelection from "@/components/ContentLibrarySelection"; import AccountSelection from "@/components/AccountSelection"; +import { isDevelopment } from "@/utils/env"; const ComponentTest: React.FC = () => { const navigate = useNavigate(); @@ -26,11 +27,19 @@ const ComponentTest: React.FC = () => { // 内容库选择状态 const [selectedLibraries, setSelectedLibraries] = useState([]); - const [selectedAccounts, setSelectedAccounts] = useState([]); + const [selectedAccounts, setSelectedAccounts] = useState([]); return ( }>
+ {isDevelopment && ( +
+ + 开发环境 - 组件测试 + +
+ )} +
diff --git a/nkebao/src/pages/mobile/workspace/main/index.module.scss b/nkebao/src/pages/mobile/workspace/main/index.module.scss index 4867a014..e8537ed6 100644 --- a/nkebao/src/pages/mobile/workspace/main/index.module.scss +++ b/nkebao/src/pages/mobile/workspace/main/index.module.scss @@ -1,7 +1,5 @@ .workspace { - padding: 16px; - background-color: #f5f5f5; - min-height: 100vh; + padding: 12px; } .section { diff --git a/nkebao/src/pages/mobile/workspace/main/index.tsx b/nkebao/src/pages/mobile/workspace/main/index.tsx index 2f443115..734a941e 100644 --- a/nkebao/src/pages/mobile/workspace/main/index.tsx +++ b/nkebao/src/pages/mobile/workspace/main/index.tsx @@ -3,19 +3,15 @@ import { Link } from "react-router-dom"; import { Card, NavBar, Badge } from "antd-mobile"; import { LikeOutlined, - MessageOutlined, SendOutlined, TeamOutlined, LinkOutlined, - AppstoreOutlined, - PieChartOutlined, - BarChartOutlined, ClockCircleOutlined, } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import MeauMobile from "@/components/MeauMobile/MeauMoible"; import styles from "./index.module.scss"; - +import NavCommon from "@/components/NavCommon"; const Workspace: React.FC = () => { // 常用功能 const commonFeatures = [ @@ -75,60 +71,9 @@ const Workspace: React.FC = () => { }, ]; - // AI智能助手 - const aiFeatures = [ - { - id: "ai-analyzer", - name: "AI数据分析", - description: "智能分析客户行为特征", - icon: ( - - ), - path: "/workspace/ai-analyzer", - bgColor: "#f0f0ff", - isNew: true, - }, - { - id: "ai-strategy", - name: "AI策略优化", - description: "智能优化获客策略", - icon: ( - - ), - path: "/workspace/ai-strategy", - bgColor: "#e6fffb", - isNew: true, - }, - { - id: "ai-forecast", - name: "AI销售预测", - description: "智能预测销售趋势", - icon: ( - - ), - path: "/workspace/ai-forecast", - bgColor: "#fffbe6", - }, - ]; - return ( -
- 工作台 -
- - } + header={} title="工作台" />} footer={} >
diff --git a/nkebao/src/router/module/component-test.tsx b/nkebao/src/router/module/component-test.tsx deleted file mode 100644 index c2ccd384..00000000 --- a/nkebao/src/router/module/component-test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import ComponentTest from "@/pages/mobile/component-test"; - -const componentTestRoutes = [ - { - path: "/component-test", - element: , - auth: true, - }, -]; - -export default componentTestRoutes; diff --git a/nkebao/src/router/module/iframe.tsx b/nkebao/src/router/module/iframe.tsx new file mode 100644 index 00000000..32af69e2 --- /dev/null +++ b/nkebao/src/router/module/iframe.tsx @@ -0,0 +1,12 @@ +import IframeDebugPage from "@/pages/iframe"; + +// iframe 调试路由 +const iframeRoutes = [ + { + path: "/iframe", + element: , + auth: false, // 不需要认证,方便调试 + }, +]; + +export default iframeRoutes; diff --git a/nkebao/src/router/module/index.tsx b/nkebao/src/router/module/index.tsx index 9a2489ee..4adcd781 100644 --- a/nkebao/src/router/module/index.tsx +++ b/nkebao/src/router/module/index.tsx @@ -1,4 +1,5 @@ import Home from "@/pages/mobile/home/index"; +import Init from "@/pages/iframe/init"; const routes = [ // 基础路由 @@ -7,6 +8,11 @@ const routes = [ element: , auth: true, // 需要登录 }, + { + path: "/init", + element: , + auth: false, // 需要登录 + }, ]; export default routes; diff --git a/nkebao/src/router/module/test.tsx b/nkebao/src/router/module/test.tsx new file mode 100644 index 00000000..becad3a3 --- /dev/null +++ b/nkebao/src/router/module/test.tsx @@ -0,0 +1,22 @@ +import SelectTest from "@/pages/mobile/test/select"; +import PostMessageTest from "@/pages/mobile/test/postMessage"; +import TestIndex from "@/pages/mobile/test/index"; +import { DEV_FEATURES } from "@/utils/env"; + +// 只在开发环境启用测试路由 +const componentTestRoutes = DEV_FEATURES.SHOW_TEST_PAGES + ? [ + { + path: "/test", + element: , + auth: true, + }, + { + path: "/test/select", + element: , + auth: true, + }, + ] + : []; + +export default componentTestRoutes; diff --git a/nkebao/src/store/module/app.ts b/nkebao/src/store/module/app.ts new file mode 100644 index 00000000..9b6e73a4 --- /dev/null +++ b/nkebao/src/store/module/app.ts @@ -0,0 +1,130 @@ +import { createPersistStore } from "@/store/createPersistStore"; + +export interface AppState { + // 应用状态 + isLoading: boolean; + isOnline: boolean; + lastActiveTime: number; + + // 主题设置 + theme: "light" | "dark" | "auto"; + + // 缓存设置 + cacheEnabled: boolean; + cacheExpiry: number; // 缓存过期时间(毫秒) + + // 调试设置 + debugMode: boolean; + logLevel: "error" | "warn" | "info" | "debug"; +} + +interface AppStoreState { + app: AppState; + setAppState: (app: Partial) => void; + setLoading: (loading: boolean) => void; + setOnline: (online: boolean) => void; + setTheme: (theme: AppState["theme"]) => void; + setDebugMode: (debug: boolean) => void; + updateLastActiveTime: () => void; + resetAppState: () => void; +} + +// 默认应用状态 +const defaultAppState: AppState = { + isLoading: false, + isOnline: navigator.onLine, + lastActiveTime: Date.now(), + theme: "auto", + cacheEnabled: true, + cacheExpiry: 24 * 60 * 60 * 1000, // 24小时 + debugMode: false, + logLevel: "info", +}; + +export const useAppStore = createPersistStore( + (set, get) => ({ + app: defaultAppState, + + setAppState: newAppState => + set(state => ({ + app: { ...state.app, ...newAppState }, + })), + + setLoading: loading => + set(state => ({ + app: { ...state.app, isLoading: loading }, + })), + + setOnline: online => + set(state => ({ + app: { ...state.app, isOnline: online }, + })), + + setTheme: theme => + set(state => ({ + app: { ...state.app, theme }, + })), + + setDebugMode: debug => + set(state => ({ + app: { ...state.app, debugMode: debug }, + })), + + updateLastActiveTime: () => + set(state => ({ + app: { ...state.app, lastActiveTime: Date.now() }, + })), + + resetAppState: () => set({ app: defaultAppState }), + }), + "app-store", + state => ({ + app: state.app, + }), +); + +// 应用状态工具函数 +export const getAppState = (): AppState => { + const { app } = useAppStore.getState(); + return app; +}; + +export const setAppLoading = (loading: boolean): void => { + const { setLoading } = useAppStore.getState(); + setLoading(loading); +}; + +export const setAppTheme = (theme: AppState["theme"]): void => { + const { setTheme } = useAppStore.getState(); + setTheme(theme); +}; + +export const toggleDebugMode = (): void => { + const { app, setDebugMode } = useAppStore.getState(); + setDebugMode(!app.debugMode); +}; + +// 监听网络状态变化 +if (typeof window !== "undefined") { + window.addEventListener("online", () => { + const { setOnline } = useAppStore.getState(); + setOnline(true); + }); + + window.addEventListener("offline", () => { + const { setOnline } = useAppStore.getState(); + setOnline(false); + }); + + // 监听用户活动 + const updateLastActive = () => { + const { updateLastActiveTime } = useAppStore.getState(); + updateLastActiveTime(); + }; + + ["mousedown", "mousemove", "keypress", "scroll", "touchstart"].forEach( + event => { + document.addEventListener(event, updateLastActive, true); + }, + ); +} diff --git a/nkebao/src/styles/global.scss b/nkebao/src/styles/global.scss index 261aaf87..ca4e01d0 100644 --- a/nkebao/src/styles/global.scss +++ b/nkebao/src/styles/global.scss @@ -160,6 +160,17 @@ textarea { } /* 6. 移动端/PC端兼容基础样式 */ + +// 安全区域CSS变量定义 +:root { + --safe-area-top: 0px; + --safe-area-bottom: 0px; + --safe-area-left: 0px; + --safe-area-right: 0px; + --status-bar-height: 0px; + --nav-bar-height: 44px; +} + html, body { height: 100%; diff --git a/nkebao/src/utils/common.ts b/nkebao/src/utils/common.ts index 2dd200f8..a71c3f87 100644 --- a/nkebao/src/utils/common.ts +++ b/nkebao/src/utils/common.ts @@ -35,3 +35,36 @@ export const comfirm = ( }); }); }; + +export function getSafeAreaHeight() { + // 1. 优先使用 CSS 环境变量 + if (CSS.supports("padding-top", "env(safe-area-inset-top)")) { + const safeAreaTop = getComputedStyle( + document.documentElement, + ).getPropertyValue("env(safe-area-inset-top)"); + const height = parseInt(safeAreaTop) || 0; + if (height > 0) return height; + } + + // 2. 设备检测 + const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + const isAndroid = /Android/.test(navigator.userAgent); + + if (isIOS) { + // iOS 设备 + const isIPhoneX = window.screen.height >= 812; + return isIPhoneX ? 44 : 20; + } else if (isAndroid) { + // Android 设备 + return 24; + } + + // 3. 默认值 + return 0; +} +// 设置全局 CSS 变量 +export function initSafeArea() { + const root = document.documentElement; + const height = getSafeAreaHeight(); + root.style.setProperty("--safe-area-top", `${height}px`); +} diff --git a/nkebao/src/utils/env.ts b/nkebao/src/utils/env.ts new file mode 100644 index 00000000..d80a41f2 --- /dev/null +++ b/nkebao/src/utils/env.ts @@ -0,0 +1,46 @@ +// 环境配置 +export const isDevelopment = import.meta.env.DEV; +export const isProduction = import.meta.env.PROD; +export const isTest = import.meta.env.MODE === "test"; + +// 开发环境特性开关 +export const DEV_FEATURES = { + // 是否显示测试页面 + SHOW_TEST_PAGES: isDevelopment, + + // 是否启用调试日志 + ENABLE_DEBUG_LOGS: isDevelopment, + + // 是否显示开发工具 + SHOW_DEV_TOOLS: isDevelopment, + + // 是否启用Mock数据 + ENABLE_MOCK_DATA: isDevelopment, +}; + +// 获取环境变量 +export const getEnvVar = (key: string, defaultValue?: string): string => { + return import.meta.env[key] || defaultValue || ""; +}; + +// 环境信息 +export const ENV_INFO = { + MODE: import.meta.env.MODE, + DEV: import.meta.env.DEV, + PROD: import.meta.env.PROD, + VITE_APP_TITLE: getEnvVar("VITE_APP_TITLE", "存客宝"), + VITE_API_BASE_URL: getEnvVar("VITE_API_BASE_URL", ""), + VITE_APP_VERSION: getEnvVar("VITE_APP_VERSION", "1.0.0"), +}; + +// 开发环境检查 +export const checkDevEnvironment = () => { + if (isDevelopment) { + // console.log("🚀 开发环境已启用"); + // console.log("📋 环境信息:", ENV_INFO); + // console.log("⚙️ 开发特性:", DEV_FEATURES); + } +}; + +// 初始化环境检查 +checkDevEnvironment(); diff --git a/nkebao/vite-pwa.config.ts b/nkebao/vite-pwa.config.ts new file mode 100644 index 00000000..4c544f8a --- /dev/null +++ b/nkebao/vite-pwa.config.ts @@ -0,0 +1,58 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { VitePWA } from "vite-plugin-pwa"; + +export default defineConfig({ + plugins: [ + react(), + VitePWA({ + registerType: "autoUpdate", + workbox: { + globPatterns: ["**/*.{js,css,html,ico,png,svg}"], + runtimeCaching: [ + { + urlPattern: /^https:\/\/api\./, + handler: "NetworkFirst", + options: { + cacheName: "api-cache", + expiration: { + maxEntries: 100, + maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days + }, + }, + }, + ], + }, + manifest: { + name: "Cunkebao", + short_name: "Cunkebao", + description: "Cunkebao Mobile App", + theme_color: "#ffffff", + background_color: "#ffffff", + display: "standalone", + orientation: "portrait", + scope: "/", + start_url: "/", + icons: [ + { + src: "favicon.ico", + sizes: "64x64 32x32 24x24 16x16", + type: "image/x-icon", + }, + { + src: "logo.png", + sizes: "192x192", + type: "image/png", + purpose: "any maskable", + }, + { + src: "logo.png", + sizes: "512x512", + type: "image/png", + purpose: "any maskable", + }, + ], + }, + }), + ], +}); diff --git a/nkebao/vite.config.ts b/nkebao/vite.config.ts index 921473b6..eb0ce0a3 100644 --- a/nkebao/vite.config.ts +++ b/nkebao/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ }, server: { open: true, + port: 3000, }, build: { chunkSizeWarningLimit: 2000,