From 3f5105a3ff3b1dcba4930edd1bff407012622bcb Mon Sep 17 00:00:00 2001 From: Ghost <106998207@qq.com> Date: Tue, 17 Mar 2026 12:01:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/README.md | 82 ++ miniprogram/app.js | 344 ++++++ miniprogram/app.json | 64 ++ miniprogram/app.wxss | 176 ++++ miniprogram/custom-tab-bar/index.js | 74 ++ miniprogram/custom-tab-bar/index.json | 3 + miniprogram/custom-tab-bar/index.wxml | 35 + miniprogram/custom-tab-bar/index.wxss | 104 ++ miniprogram/images/camera-active.png | Bin 0 -> 1877 bytes miniprogram/images/camera.png | Bin 0 -> 1877 bytes miniprogram/images/default-avatar.png | Bin 0 -> 70 bytes miniprogram/images/home-active.png | Bin 0 -> 2480 bytes miniprogram/images/home.png | Bin 0 -> 2442 bytes miniprogram/images/mbti-team-image.png | Bin 0 -> 270339 bytes miniprogram/images/user-active.png | Bin 0 -> 2135 bytes miniprogram/images/user.png | Bin 0 -> 2104 bytes miniprogram/pages/ai-test/camera.js | 1 + miniprogram/pages/ai-test/camera.json | 1 + miniprogram/pages/ai-test/camera.wxml | 1 + miniprogram/pages/ai-test/camera.wxss | 1 + miniprogram/pages/ai-test/index.js | 1 + miniprogram/pages/ai-test/index.json | 1 + miniprogram/pages/ai-test/index.wxml | 1 + miniprogram/pages/ai-test/index.wxss | 1 + miniprogram/pages/ai-test/result.js | 1 + miniprogram/pages/ai-test/result.json | 1 + miniprogram/pages/ai-test/result.wxml | 1 + miniprogram/pages/ai-test/result.wxss | 1 + miniprogram/pages/enterprise/index.js | 214 ++++ miniprogram/pages/enterprise/index.json | 7 + miniprogram/pages/enterprise/index.wxml | 59 ++ miniprogram/pages/enterprise/index.wxss | 283 +++++ .../pages/enterprise/resume-history.js | 209 ++++ .../pages/enterprise/resume-history.json | 6 + .../pages/enterprise/resume-history.wxml | 66 ++ .../pages/enterprise/resume-history.wxss | 342 ++++++ miniprogram/pages/history/index.js | 161 +++ miniprogram/pages/history/index.json | 6 + miniprogram/pages/history/index.wxml | 74 ++ miniprogram/pages/history/index.wxss | 208 ++++ miniprogram/pages/index/camera.js | 196 ++++ miniprogram/pages/index/camera.json | 6 + miniprogram/pages/index/camera.wxml | 69 ++ miniprogram/pages/index/camera.wxss | 312 ++++++ miniprogram/pages/index/index.js | 177 ++++ miniprogram/pages/index/index.json | 6 + miniprogram/pages/index/index.wxml | 61 ++ miniprogram/pages/index/index.wxss | 278 +++++ miniprogram/pages/index/result.js | 539 ++++++++++ miniprogram/pages/index/result.json | 6 + miniprogram/pages/index/result.wxml | 304 ++++++ miniprogram/pages/index/result.wxss | 984 ++++++++++++++++++ miniprogram/pages/index/upload.js | 193 ++++ miniprogram/pages/index/upload.json | 8 + miniprogram/pages/index/upload.wxml | 113 ++ miniprogram/pages/index/upload.wxss | 243 +++++ miniprogram/pages/phone-auth/index.js | 74 ++ miniprogram/pages/phone-auth/index.json | 4 + miniprogram/pages/phone-auth/index.wxml | 5 + miniprogram/pages/phone-auth/index.wxss | 29 + miniprogram/pages/profile/index.js | 318 ++++++ miniprogram/pages/profile/index.json | 6 + miniprogram/pages/profile/index.wxml | 212 ++++ miniprogram/pages/profile/index.wxss | 534 ++++++++++ miniprogram/pages/promo/index.js | 270 +++++ miniprogram/pages/promo/index.json | 5 + miniprogram/pages/promo/index.wxml | 219 ++++ miniprogram/pages/promo/index.wxss | 550 ++++++++++ miniprogram/pages/promo/poster.js | 74 ++ miniprogram/pages/promo/poster.json | 6 + miniprogram/pages/promo/poster.wxml | 16 + miniprogram/pages/promo/poster.wxss | 83 ++ miniprogram/pages/promo/withdrawals.js | 236 +++++ miniprogram/pages/promo/withdrawals.json | 5 + miniprogram/pages/promo/withdrawals.wxml | 77 ++ miniprogram/pages/promo/withdrawals.wxss | 244 +++++ miniprogram/pages/purchase/index.js | 247 +++++ miniprogram/pages/purchase/index.json | 6 + miniprogram/pages/purchase/index.wxml | 135 +++ miniprogram/pages/purchase/index.wxss | 789 ++++++++++++++ miniprogram/pages/recharge/index.js | 83 ++ miniprogram/pages/recharge/index.json | 3 + miniprogram/pages/recharge/index.wxml | 20 + miniprogram/pages/recharge/index.wxss | 106 ++ miniprogram/pages/result/disc.js | 157 +++ miniprogram/pages/result/disc.json | 6 + miniprogram/pages/result/disc.wxml | 81 ++ miniprogram/pages/result/disc.wxss | 197 ++++ miniprogram/pages/result/mbti.js | 224 ++++ miniprogram/pages/result/mbti.json | 6 + miniprogram/pages/result/mbti.wxml | 107 ++ miniprogram/pages/result/mbti.wxss | 240 +++++ miniprogram/pages/result/pdp.js | 157 +++ miniprogram/pages/result/pdp.json | 6 + miniprogram/pages/result/pdp.wxml | 87 ++ miniprogram/pages/result/pdp.wxss | 211 ++++ miniprogram/pages/result/resume.js | 524 ++++++++++ miniprogram/pages/result/resume.json | 4 + miniprogram/pages/result/resume.wxml | 86 ++ miniprogram/pages/result/resume.wxss | 348 +++++++ miniprogram/pages/test-select/index.js | 21 + miniprogram/pages/test-select/index.json | 5 + miniprogram/pages/test-select/index.wxml | 40 + miniprogram/pages/test-select/index.wxss | 107 ++ miniprogram/pages/test/disc.js | 134 +++ miniprogram/pages/test/disc.json | 6 + miniprogram/pages/test/disc.wxml | 48 + miniprogram/pages/test/disc.wxss | 172 +++ miniprogram/pages/test/mbti.js | 180 ++++ miniprogram/pages/test/mbti.json | 6 + miniprogram/pages/test/mbti.wxml | 48 + miniprogram/pages/test/mbti.wxss | 172 +++ miniprogram/pages/test/pdp.js | 137 +++ miniprogram/pages/test/pdp.json | 6 + miniprogram/pages/test/pdp.wxml | 48 + miniprogram/pages/test/pdp.wxss | 171 +++ miniprogram/pages/user-profile/index.js | 205 ++++ miniprogram/pages/user-profile/index.json | 4 + miniprogram/pages/user-profile/index.wxml | 66 ++ miniprogram/pages/user-profile/index.wxss | 206 ++++ miniprogram/project.config.json | 51 + miniprogram/project.private.config.json | 34 + miniprogram/sitemap.json | 7 + miniprogram/utils/descriptions.js | 44 + miniprogram/utils/payment.js | 628 +++++++++++ miniprogram/utils/phoneAuth.js | 113 ++ miniprogram/utils/questions.js | 117 +++ miniprogram/utils/request.js | 109 ++ miniprogram/utils/resultFormat.js | 95 ++ miniprogram/utils/share.js | 60 ++ 130 files changed, 15861 insertions(+) create mode 100644 miniprogram/README.md create mode 100644 miniprogram/app.js create mode 100644 miniprogram/app.json create mode 100644 miniprogram/app.wxss create mode 100644 miniprogram/custom-tab-bar/index.js create mode 100644 miniprogram/custom-tab-bar/index.json create mode 100644 miniprogram/custom-tab-bar/index.wxml create mode 100644 miniprogram/custom-tab-bar/index.wxss create mode 100644 miniprogram/images/camera-active.png create mode 100644 miniprogram/images/camera.png create mode 100644 miniprogram/images/default-avatar.png create mode 100644 miniprogram/images/home-active.png create mode 100644 miniprogram/images/home.png create mode 100644 miniprogram/images/mbti-team-image.png create mode 100644 miniprogram/images/user-active.png create mode 100644 miniprogram/images/user.png create mode 100644 miniprogram/pages/ai-test/camera.js create mode 100644 miniprogram/pages/ai-test/camera.json create mode 100644 miniprogram/pages/ai-test/camera.wxml create mode 100644 miniprogram/pages/ai-test/camera.wxss create mode 100644 miniprogram/pages/ai-test/index.js create mode 100644 miniprogram/pages/ai-test/index.json create mode 100644 miniprogram/pages/ai-test/index.wxml create mode 100644 miniprogram/pages/ai-test/index.wxss create mode 100644 miniprogram/pages/ai-test/result.js create mode 100644 miniprogram/pages/ai-test/result.json create mode 100644 miniprogram/pages/ai-test/result.wxml create mode 100644 miniprogram/pages/ai-test/result.wxss create mode 100644 miniprogram/pages/enterprise/index.js create mode 100644 miniprogram/pages/enterprise/index.json create mode 100644 miniprogram/pages/enterprise/index.wxml create mode 100644 miniprogram/pages/enterprise/index.wxss create mode 100644 miniprogram/pages/enterprise/resume-history.js create mode 100644 miniprogram/pages/enterprise/resume-history.json create mode 100644 miniprogram/pages/enterprise/resume-history.wxml create mode 100644 miniprogram/pages/enterprise/resume-history.wxss create mode 100644 miniprogram/pages/history/index.js create mode 100644 miniprogram/pages/history/index.json create mode 100644 miniprogram/pages/history/index.wxml create mode 100644 miniprogram/pages/history/index.wxss create mode 100644 miniprogram/pages/index/camera.js create mode 100644 miniprogram/pages/index/camera.json create mode 100644 miniprogram/pages/index/camera.wxml create mode 100644 miniprogram/pages/index/camera.wxss create mode 100644 miniprogram/pages/index/index.js create mode 100644 miniprogram/pages/index/index.json create mode 100644 miniprogram/pages/index/index.wxml create mode 100644 miniprogram/pages/index/index.wxss create mode 100644 miniprogram/pages/index/result.js create mode 100644 miniprogram/pages/index/result.json create mode 100644 miniprogram/pages/index/result.wxml create mode 100644 miniprogram/pages/index/result.wxss create mode 100644 miniprogram/pages/index/upload.js create mode 100644 miniprogram/pages/index/upload.json create mode 100644 miniprogram/pages/index/upload.wxml create mode 100644 miniprogram/pages/index/upload.wxss create mode 100644 miniprogram/pages/phone-auth/index.js create mode 100644 miniprogram/pages/phone-auth/index.json create mode 100644 miniprogram/pages/phone-auth/index.wxml create mode 100644 miniprogram/pages/phone-auth/index.wxss create mode 100644 miniprogram/pages/profile/index.js create mode 100644 miniprogram/pages/profile/index.json create mode 100644 miniprogram/pages/profile/index.wxml create mode 100644 miniprogram/pages/profile/index.wxss create mode 100644 miniprogram/pages/promo/index.js create mode 100644 miniprogram/pages/promo/index.json create mode 100644 miniprogram/pages/promo/index.wxml create mode 100644 miniprogram/pages/promo/index.wxss create mode 100644 miniprogram/pages/promo/poster.js create mode 100644 miniprogram/pages/promo/poster.json create mode 100644 miniprogram/pages/promo/poster.wxml create mode 100644 miniprogram/pages/promo/poster.wxss create mode 100644 miniprogram/pages/promo/withdrawals.js create mode 100644 miniprogram/pages/promo/withdrawals.json create mode 100644 miniprogram/pages/promo/withdrawals.wxml create mode 100644 miniprogram/pages/promo/withdrawals.wxss create mode 100644 miniprogram/pages/purchase/index.js create mode 100644 miniprogram/pages/purchase/index.json create mode 100644 miniprogram/pages/purchase/index.wxml create mode 100644 miniprogram/pages/purchase/index.wxss create mode 100644 miniprogram/pages/recharge/index.js create mode 100644 miniprogram/pages/recharge/index.json create mode 100644 miniprogram/pages/recharge/index.wxml create mode 100644 miniprogram/pages/recharge/index.wxss create mode 100644 miniprogram/pages/result/disc.js create mode 100644 miniprogram/pages/result/disc.json create mode 100644 miniprogram/pages/result/disc.wxml create mode 100644 miniprogram/pages/result/disc.wxss create mode 100644 miniprogram/pages/result/mbti.js create mode 100644 miniprogram/pages/result/mbti.json create mode 100644 miniprogram/pages/result/mbti.wxml create mode 100644 miniprogram/pages/result/mbti.wxss create mode 100644 miniprogram/pages/result/pdp.js create mode 100644 miniprogram/pages/result/pdp.json create mode 100644 miniprogram/pages/result/pdp.wxml create mode 100644 miniprogram/pages/result/pdp.wxss create mode 100644 miniprogram/pages/result/resume.js create mode 100644 miniprogram/pages/result/resume.json create mode 100644 miniprogram/pages/result/resume.wxml create mode 100644 miniprogram/pages/result/resume.wxss create mode 100644 miniprogram/pages/test-select/index.js create mode 100644 miniprogram/pages/test-select/index.json create mode 100644 miniprogram/pages/test-select/index.wxml create mode 100644 miniprogram/pages/test-select/index.wxss create mode 100644 miniprogram/pages/test/disc.js create mode 100644 miniprogram/pages/test/disc.json create mode 100644 miniprogram/pages/test/disc.wxml create mode 100644 miniprogram/pages/test/disc.wxss create mode 100644 miniprogram/pages/test/mbti.js create mode 100644 miniprogram/pages/test/mbti.json create mode 100644 miniprogram/pages/test/mbti.wxml create mode 100644 miniprogram/pages/test/mbti.wxss create mode 100644 miniprogram/pages/test/pdp.js create mode 100644 miniprogram/pages/test/pdp.json create mode 100644 miniprogram/pages/test/pdp.wxml create mode 100644 miniprogram/pages/test/pdp.wxss create mode 100644 miniprogram/pages/user-profile/index.js create mode 100644 miniprogram/pages/user-profile/index.json create mode 100644 miniprogram/pages/user-profile/index.wxml create mode 100644 miniprogram/pages/user-profile/index.wxss create mode 100644 miniprogram/project.config.json create mode 100644 miniprogram/project.private.config.json create mode 100644 miniprogram/sitemap.json create mode 100644 miniprogram/utils/descriptions.js create mode 100644 miniprogram/utils/payment.js create mode 100644 miniprogram/utils/phoneAuth.js create mode 100644 miniprogram/utils/questions.js create mode 100644 miniprogram/utils/request.js create mode 100644 miniprogram/utils/resultFormat.js create mode 100644 miniprogram/utils/share.js diff --git a/miniprogram/README.md b/miniprogram/README.md new file mode 100644 index 0000000..03d1070 --- /dev/null +++ b/miniprogram/README.md @@ -0,0 +1,82 @@ +# MBTI性格测试小程序 + +## 小程序信息 +- **AppID**: wx3d15ed02e98b04e3 +- **名称**: MBTI性格测试 +- **版本**: 1.0.0 + +## 功能模块 + +### 1. MBTI测试 +- 30道精简题目 +- 4个维度分析(E/I、S/N、T/F、J/P) +- 16种人格类型描述 +- 置信度计算 + +### 2. DISC测试 +- 20道题目 +- 4种行为风格(D/I/S/C) +- 职业建议和沟通技巧 + +### 3. PDP测试 +- 20道题目 +- 5种动物性格(老虎/孔雀/考拉/猫头鹰/变色龙) +- 团队角色分析 + +## 目录结构 + +``` +miniprogram/ +├── app.js # 小程序入口 +├── app.json # 全局配置 +├── app.wxss # 全局样式 +├── project.config.json # 项目配置 +├── sitemap.json # 站点地图 +├── pages/ +│ ├── index/ # 首页 +│ ├── test/ # 测试页面 +│ │ ├── mbti.* # MBTI测试 +│ │ ├── disc.* # DISC测试 +│ │ └── pdp.* # PDP测试 +│ ├── result/ # 结果页面 +│ │ ├── mbti.* +│ │ ├── disc.* +│ │ └── pdp.* +│ └── profile/ # 个人中心 +├── utils/ +│ ├── questions.js # 题库 +│ └── descriptions.js # 类型描述 +└── images/ # 图片资源 +``` + +## 使用方法 + +1. 下载并安装 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html) + +2. 打开微信开发者工具,选择"导入项目" + +3. 选择 `miniprogram` 目录作为项目目录 + +4. 填入AppID: `wx3d15ed02e98b04e3` + +5. 点击"编译"即可预览 + +## 上传发布 + +1. 在微信开发者工具中点击"上传" + +2. 填写版本号和项目备注 + +3. 登录 [微信公众平台](https://mp.weixin.qq.com) 提交审核 + +## 注意事项 + +- tabBar图标需要替换为实际的PNG图片(81x81像素) +- 分享图片需要放置到 images/share.png +- 后端API地址需要在 app.js 中配置 + +## 技术栈 + +- 微信小程序原生开发 +- CSS3动画和过渡效果 +- 本地存储(wx.setStorageSync) diff --git a/miniprogram/app.js b/miniprogram/app.js new file mode 100644 index 0000000..27b3684 --- /dev/null +++ b/miniprogram/app.js @@ -0,0 +1,344 @@ +// app.js - MBTI小程序主入口 +const { request } = require('./utils/request.js') + +App({ + globalData: { + userInfo: null, + openId: null, + token: null, + siteTitle: '神仙团队AI性格测试', + textConfig: null, // 从 /api/config/runtime 动态加载:analyzingTitle, startButtonText, reportTitle, aiAnalysisText 等 + // 当前使用范围:personal 个人版 / enterprise 企业版(影响定价与 enterpriseId 写入) + appScope: 'personal', + // 扫码进入企业页时 scene 解析出的企业ID(e_123),提交测试/分析时优先使用 + enterpriseIdFromScene: null, + // API基础地址(开发时用本地,生产环境替换为实际域名) + apiBase: 'https://mbtiapi.quwanzhi.com', + //apiBase: 'http://mbti.com', + // VIP信息 + vipInfo: null, + // 测试次数 + testCount: 0, + // 已解锁的测试 + unlockedTests: [], + // 测试结果缓存 + mbtiResult: null, + discResult: null, + pdpResult: null, + aiResult: null + }, + + onLaunch() { + // 加载本地存储的数据 + this.loadStoredData() + + // 静默登录获取openId + this.silentLogin() + + // 预加载站点/小程序名称(供导航栏展示) + this.getRuntimeConfig().then((cfg) => { + if (cfg && cfg.siteTitle) { + this.globalData.siteTitle = cfg.siteTitle + } + }).catch(() => {}) + }, + + // 加载本地存储数据 + loadStoredData() { + const token = wx.getStorageSync('token') + if (token) { + this.globalData.token = token + } + const userInfo = wx.getStorageSync('userInfo') + if (userInfo) { + this.globalData.userInfo = userInfo + // 优先使用后端返回的 openid 字段,而不是内部自增 id + this.globalData.openId = userInfo.openid || userInfo.openId || null + } + + // VIP信息 + const vipInfo = wx.getStorageSync('vipInfo') + if (vipInfo) { + this.globalData.vipInfo = vipInfo + } + + // 测试次数 + const testCount = wx.getStorageSync('testCount') + if (testCount) { + this.globalData.testCount = testCount + } + + // 已解锁测试 + const unlockedTests = wx.getStorageSync('unlockedTests') + if (unlockedTests) { + this.globalData.unlockedTests = unlockedTests + } + + // 测试结果 + this.globalData.mbtiResult = wx.getStorageSync('mbtiResult') || null + this.globalData.discResult = wx.getStorageSync('discResult') || null + this.globalData.pdpResult = wx.getStorageSync('pdpResult') || null + this.globalData.aiResult = wx.getStorageSync('aiResult') || null + }, + + /** + * 静默登录:wx.login 取 code,请求后端换 token 与用户信息 + * @returns {Promise} 是否登录成功(拿到 token) + */ + silentLogin() { + return new Promise((resolve) => { + wx.login({ + success: (res) => { + if (!res.code) { + resolve(false) + return + } + const url = `${this.globalData.apiBase}/api/auth/wechat` + wx.request({ + url, + method: 'POST', + header: { 'Content-Type': 'application/json' }, + data: { code: res.code }, + success: (response) => { + if (response.statusCode === 200 && response.data && response.data.code === 200) { + const data = response.data.data || {} + const { token, user } = data + if (token) { + this.globalData.token = token + wx.setStorageSync('token', token) + } + if (user) { + this.globalData.userInfo = user + // 使用微信真实 openid,而不是 wechat_users 表的 id + this.globalData.openId = user.openid || user.openId || null + wx.setStorageSync('userInfo', user) + } + // 登录成功后处理分销绑定(若进入时携带了推荐人参数) + if (token) { + this._tryDistributionBind() + } + resolve(!!token) + return + } + resolve(false) + }, + fail: (err) => { + console.error('登录请求失败:', err) + const storedToken = wx.getStorageSync('token') + const storedUser = wx.getStorageSync('userInfo') + if (storedToken) { + this.globalData.token = storedToken + this.globalData.userInfo = storedUser || null + this.globalData.openId = storedUser ? storedUser.id : null + resolve(true) + } else { + resolve(false) + } + } + }) + }, + fail: () => resolve(false) + }) + }) + }, + + /** + * 确保已登录:有 token 直接 resolve,否则先执行静默登录 + * @returns {Promise} 当前是否有有效登录态 + */ + ensureLogin() { + if (this.globalData.token) return Promise.resolve(true) + return this.silentLogin() + }, + + /** + * 登录成功后尝试分销绑定(处理进入时携带的 uid 参数) + */ + _tryDistributionBind() { + const inviterId = this.globalData._pendingInviterId + const scope = this.globalData._pendingInviterScope || 'personal' + const eid = this.globalData._pendingInviterEid || null + + if (!inviterId || inviterId <= 0) return + + // 清除 pending 状态,避免重复绑定 + this.globalData._pendingInviterId = null + this.globalData._pendingInviterScope = null + this.globalData._pendingInviterEid = null + + const token = this.globalData.token + if (!token) return + + const data = { inviterId, scope } + if (eid) data.eid = eid + + request({ + url: '/api/distribution/bind', + method: 'POST', + data, + success: () => {}, + fail: () => {} + }) + }, + + /** 清除登录态(退出登录时调用) */ + logout() { + this.globalData.token = null + this.globalData.userInfo = null + this.globalData.openId = null + wx.removeStorageSync('token') + wx.removeStorageSync('userInfo') + }, + + // 获取用户信息(弹窗授权),并同步昵称/头像到后端 + getUserInfo(callback) { + wx.getUserProfile({ + desc: '用于展示用户头像和昵称', + success: (res) => { + const u = res.userInfo + this.globalData.userInfo = { ...this.globalData.userInfo, ...u, avatarUrl: u.avatarUrl || u.avatar } + wx.setStorageSync('userInfo', this.globalData.userInfo) + callback && callback(this.globalData.userInfo) + this.syncProfileToServer({ nickname: u.nickName, avatar: u.avatarUrl || u.avatar }) + }, + fail: () => { + callback && callback(this.globalData.userInfo || null) + } + }) + }, + + // 同步昵称、头像到后端(需已登录) + syncProfileToServer(profile, callback) { + if (!this.globalData.token || !profile) { + callback && callback(false) + return + } + + // 记录同步请求(调试用) + console.log('同步用户资料到服务器:', profile) + + request({ + url: '/api/auth/wechat/profile', + method: 'PUT', + data: profile, + success: (res) => { + console.log('同步用户资料响应:', res) + if (res.statusCode === 200 && res.data && res.data.code === 200) { + // 如果服务器返回了更新后的数据,使用服务器数据 + if (res.data.data) { + const updatedUserInfo = { ...this.globalData.userInfo, ...res.data.data } + // 确保 nickname 和 nickName 字段都更新 + if (profile.nickname) { + updatedUserInfo.nickname = profile.nickname + updatedUserInfo.nickName = profile.nickname + } + if (profile.avatar) { + updatedUserInfo.avatar = profile.avatar + updatedUserInfo.avatarUrl = profile.avatar + } + if (profile.birthday !== undefined) { + updatedUserInfo.birthday = profile.birthday + } + this.globalData.userInfo = updatedUserInfo + wx.setStorageSync('userInfo', updatedUserInfo) + console.log('用户资料已同步:', updatedUserInfo) + } else { + // 服务器没有返回数据,使用本地更新的数据 + const updatedUserInfo = { ...this.globalData.userInfo, ...profile } + if (profile.nickname) { + updatedUserInfo.nickname = profile.nickname + updatedUserInfo.nickName = profile.nickname + } + if (profile.avatar) { + updatedUserInfo.avatar = profile.avatar + updatedUserInfo.avatarUrl = profile.avatar + } + if (profile.birthday !== undefined) { + updatedUserInfo.birthday = profile.birthday + } + this.globalData.userInfo = updatedUserInfo + wx.setStorageSync('userInfo', updatedUserInfo) + console.log('用户资料已更新(本地):', updatedUserInfo) + } + callback && callback(true) + } else { + console.error('更新用户资料失败:', res.data) + callback && callback(false) + } + }, + fail: (err) => { + console.error('请求失败:', err) + callback && callback(false) + } + }) + }, + + // 保存测试结果 + saveTestResult(type, result) { + const key = `${type}Result` + wx.setStorageSync(key, result) + this.globalData[key] = result + + // 同步到服务器(需携带 token,后端从 JWT 解析 userId) + if (this.globalData.token) { + const scope = this.globalData.appScope || 'personal' + const storedUser = wx.getStorageSync('userInfo') || null + const enterpriseId = + scope === 'enterprise' + ? (this.globalData.enterpriseIdFromScene || (this.globalData.userInfo && this.globalData.userInfo.enterpriseId) || (storedUser && storedUser.enterpriseId) || null) + : null + wx.request({ + url: `${this.globalData.apiBase}/api/test/submit`, + method: 'POST', + header: { + 'Authorization': `Bearer ${this.globalData.token}`, + 'Content-Type': 'application/json' + }, + data: { + testType: type, + answers: result.answers || [], + result: result, + userId: this.globalData.userInfo?.id ?? this.globalData.openId, + enterpriseId: enterpriseId || undefined, + testDuration: result.testDuration || 0, + timestamp: new Date().toISOString() + } + }) + } + }, + + // 获取测试结果 + getTestResult(type) { + return this.globalData[`${type}Result`] || wx.getStorageSync(`${type}Result`) || null + }, + + /** + * 获取运行配置:个人/企业定价 + 当前 AI 服务商(超管配置,默认第一个启用的) + * 有 token 且用户属于企业则返回企业定价,否则个人定价 + * @returns {Promise<{pricingType, pricing, aiProviderId, aiProviderName}>} + */ + getRuntimeConfig() { + return new Promise((resolve, reject) => { + const scope = this.globalData.appScope || 'personal' + const base = this.globalData.apiBase.replace(/\/$/, '') + const url = `${base}/api/config/runtime?scope=${encodeURIComponent(scope)}` + const token = this.globalData.token || wx.getStorageSync('token') || '' + wx.request({ + url, + method: 'GET', + header: token ? { Authorization: 'Bearer ' + token } : {}, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const data = res.data.data || {} + if (data.siteTitle) this.globalData.siteTitle = data.siteTitle + if (data.textConfig) this.globalData.textConfig = data.textConfig + resolve(data) + } else { + reject(new Error(res.data && res.data.message ? res.data.message : '获取配置失败')) + } + }, + fail: reject + }) + }) + } +}) diff --git a/miniprogram/app.json b/miniprogram/app.json new file mode 100644 index 0000000..c6f3cd7 --- /dev/null +++ b/miniprogram/app.json @@ -0,0 +1,64 @@ +{ + "pages": [ + "pages/index/index", + "pages/index/camera", + "pages/index/upload", + "pages/index/result", + "pages/test-select/index", + "pages/test/mbti", + "pages/test/disc", + "pages/test/pdp", + "pages/result/mbti", + "pages/result/disc", + "pages/result/pdp", + "pages/result/resume", + "pages/purchase/index", + "pages/recharge/index", + "pages/enterprise/index", + "pages/enterprise/resume-history", + "pages/profile/index", + "pages/user-profile/index", + "pages/history/index", + "pages/phone-auth/index", + "pages/promo/index", + "pages/promo/poster", + "pages/promo/withdrawals" + ], + "tabBar": { + "custom": true, + "color": "#999999", + "selectedColor": "#7c3aed", + "backgroundColor": "#ffffff", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/index/index", + "text": "首页", + "iconPath": "images/home.png", + "selectedIconPath": "images/home-active.png" + }, + { + "pagePath": "pages/index/camera", + "text": "查看报告", + "iconPath": "images/camera.png", + "selectedIconPath": "images/camera-active.png" + }, + { + "pagePath": "pages/profile/index", + "text": "我的", + "iconPath": "images/user.png", + "selectedIconPath": "images/user-active.png" + } + ] + }, + "window": { + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTitleText": "神仙团队AI性格测试", + "navigationBarTextStyle": "black", + "backgroundColor": "#F5F5F5" + }, + "style": "v2", + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss new file mode 100644 index 0000000..8e11059 --- /dev/null +++ b/miniprogram/app.wxss @@ -0,0 +1,176 @@ +/* app.wxss - 全局样式 */ + +/* 主题颜色 */ +page { + --primary-color: #FF6B8A; + --primary-light: #FFE4E9; + --secondary-color: #8B5CF6; + --secondary-light: #EDE9FE; + --success-color: #10B981; + --warning-color: #F59E0B; + --danger-color: #EF4444; + --text-primary: #1F2937; + --text-secondary: #6B7280; + --text-muted: #9CA3AF; + --bg-primary: #FFFFFF; + --bg-secondary: #F3F4F6; + --border-color: #E5E7EB; + + background-color: var(--bg-secondary); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 28rpx; + color: var(--text-primary); + line-height: 1.6; +} + +/* 玻璃卡片效果 */ +.glass-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 24rpx; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08); + padding: 32rpx; + margin-bottom: 24rpx; +} + +/* 渐变背景 */ +.gradient-personal { + background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%); +} + +.gradient-enterprise { + background: linear-gradient(135deg, #8B5CF6 0%, #3B82F6 100%); +} + +/* 按钮样式 */ +.btn { + display: flex; + align-items: center; + justify-content: center; + height: 88rpx; + border-radius: 44rpx; + font-size: 32rpx; + font-weight: 600; + border: none; + transition: all 0.3s ease; +} + +.btn-primary { + background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%); + color: #FFFFFF; +} + +.btn-secondary { + background: #FFFFFF; + color: var(--primary-color); + border: 2rpx solid var(--primary-color); +} + +.btn-outline { + background: transparent; + color: var(--text-secondary); + border: 2rpx solid var(--border-color); +} + +.btn:active { + transform: scale(0.98); + opacity: 0.9; +} + +/* 进度条 */ +.progress-bar { + width: 100%; + height: 12rpx; + background: var(--bg-secondary); + border-radius: 6rpx; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + border-radius: 6rpx; + transition: width 0.3s ease; +} + +/* 标签 */ +.tag { + display: inline-flex; + align-items: center; + padding: 8rpx 20rpx; + border-radius: 24rpx; + font-size: 24rpx; + font-weight: 500; +} + +.tag-primary { + background: var(--primary-light); + color: var(--primary-color); +} + +.tag-secondary { + background: var(--secondary-light); + color: var(--secondary-color); +} + +/* 文字样式 */ +.text-primary { + color: var(--text-primary); +} + +.text-secondary { + color: var(--text-secondary); +} + +.text-muted { + color: var(--text-muted); +} + +.text-center { + text-align: center; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +/* 间距 */ +.mt-2 { margin-top: 16rpx; } +.mt-4 { margin-top: 32rpx; } +.mb-2 { margin-bottom: 16rpx; } +.mb-4 { margin-bottom: 32rpx; } +.p-4 { padding: 32rpx; } + +/* 弹性布局 */ +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.flex-1 { + flex: 1; +} + +/* 安全区域 */ +.safe-area-bottom { + padding-bottom: calc(constant(safe-area-inset-bottom) + 16rpx); + padding-bottom: calc(env(safe-area-inset-bottom) + 16rpx); +} diff --git a/miniprogram/custom-tab-bar/index.js b/miniprogram/custom-tab-bar/index.js new file mode 100644 index 0000000..dc50193 --- /dev/null +++ b/miniprogram/custom-tab-bar/index.js @@ -0,0 +1,74 @@ +// 自定义 tabBar(不使用微信内置):灰线 + 三栏,中间为浮起圆钮 +Component({ + data: { + selected: 0, + list: [ + { pagePath: '/pages/index/index', text: '首页', textKey: 'home', icon: 'home' }, + { pagePath: '/pages/index/camera', text: '查看报告', textKey: 'camera', icon: 'camera' }, + { pagePath: '/pages/profile/index', text: '我的', textKey: 'profile', icon: 'user' } + ] + }, + lifetimes: { + attached() { + this.updateSelected() + } + }, + pageLifetimes: { + show() { + this.updateSelected() + } + }, + methods: { + updateSelected() { + try { + const pages = getCurrentPages() + if (!pages || pages.length === 0) { + return + } + + const currentPage = pages[pages.length - 1] + if (!currentPage || !currentPage.route) { + return + } + + const url = currentPage.route + let selected = 0 + + if (url === 'pages/index/index' || url === 'pages/enterprise/index') { + selected = 0 + } else if (url === 'pages/index/camera') { + selected = 1 + } else if (url === 'pages/profile/index') { + selected = 2 + } + + this.setData({ selected }) + } catch (error) { + console.error('updateSelected error:', error) + // 默认选中第一个 + this.setData({ selected: 0 }) + } + }, + switchTab(e) { + const index = parseInt(e.currentTarget.dataset.index, 10) + let url = e.currentTarget.dataset.path + + // 点击"首页"(index=0)时,根据当前 scope 跳到企业版或个人版首页 + if (index === 0) { + try { + const app = getApp() + const scope = (app && app.globalData && app.globalData.appScope) || 'personal' + if (scope === 'enterprise') { + // 企业版:navigateTo(不是 tabBar 页面,不能用 switchTab) + wx.navigateTo({ url: '/pages/enterprise/index' }) + this.setData({ selected: index }) + return + } + } catch (e) {} + } + + wx.switchTab({ url }) + this.setData({ selected: index }) + } + } +}) diff --git a/miniprogram/custom-tab-bar/index.json b/miniprogram/custom-tab-bar/index.json new file mode 100644 index 0000000..467ce29 --- /dev/null +++ b/miniprogram/custom-tab-bar/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} diff --git a/miniprogram/custom-tab-bar/index.wxml b/miniprogram/custom-tab-bar/index.wxml new file mode 100644 index 0000000..9bc06e8 --- /dev/null +++ b/miniprogram/custom-tab-bar/index.wxml @@ -0,0 +1,35 @@ + + + + + + + 首页 + + + + + + + + + + 我的 + + + diff --git a/miniprogram/custom-tab-bar/index.wxss b/miniprogram/custom-tab-bar/index.wxss new file mode 100644 index 0000000..f866393 --- /dev/null +++ b/miniprogram/custom-tab-bar/index.wxss @@ -0,0 +1,104 @@ +/* 自定义 tabBar:白底、顶部分割线、中间浮起圆钮 */ +.tab-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + background: #ffffff; + padding-bottom: env(safe-area-inset-bottom); + z-index: 9999; +} + +.tab-bar-line { + height: 1rpx; + background: #e5e7eb; + width: 100%; +} + +.tab-bar-inner { + display: flex; + align-items: flex-end; + justify-content: space-around; + height: 100rpx; + padding: 0 20rpx 8rpx; + position: relative; +} + +.tab-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + padding-bottom: 8rpx; +} + +.tab-icon { + width: 48rpx; + height: 48rpx; + margin-bottom: 4rpx; +} + +.tab-text { + font-size: 20rpx; + color: #999999; +} + +.tab-item.active .tab-text { + color: #7c3aed; +} + +/* 中间项:浮起彩色圆钮 + 相机图标 */ +.tab-item-center { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + margin-top: -56rpx; + padding-bottom: 8rpx; +} + +.center-circle { + width: 96rpx; + height: 96rpx; + min-width: 96rpx; + min-height: 96rpx; + border-radius: 50%; + background: linear-gradient(135deg, #7c3aed 0%, #a78bfa 100%); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 4rpx; + box-shadow: 0 4rpx 20rpx rgba(124, 58, 237, 0.4); + border: 4rpx solid #ffffff; + flex-shrink: 0; + overflow: hidden; +} + +.tab-item-center.active .center-circle { + background: linear-gradient(135deg, #6d28d9 0%, #7c3aed 100%); + box-shadow: 0 4rpx 20rpx rgba(124, 58, 237, 0.6); +} + +.center-icon { + width: 44rpx; + height: 44rpx; +} + +/* 占位:与「首页」「我的」文字等高,保证中间圆钮与两侧图标对齐 */ +.center-text-placeholder { + height: 24rpx; + width: 1rpx; + visibility: hidden; +} + +.tab-item-center .center-text { + color: #7c3aed; + font-size: 20rpx; + font-weight: 600; +} + +.tab-item-center.active .center-text { + color: #6d28d9; +} diff --git a/miniprogram/images/camera-active.png b/miniprogram/images/camera-active.png new file mode 100644 index 0000000000000000000000000000000000000000..80c59a20a92a86558aef7f769e55564cd358d239 GIT binary patch literal 1877 zcmV-b2demqP)Yvm6{v1uqQr^KQ$OvWr5ijfXe z3KgOx&E{+){*l>!KA)%Oe)s%(-{0>&-H-M5`@Orq_jTRReP7pe-PdzJ_vv}}D|GN* zX8<~Q2Pky*fX*J!!6VT53Uu%eknbSa($X>syb0>1_febyPR{E=<@O5T^fNg92)G#3 zOT$ro06YP5p;a~8@pdliFA7J1; zAnn}&po?CJmji&b80WuJh=35t-rVEb2=zma5}CEGaB3)W!eEg(&8(~;c;-@}%G`@s<^w1sv9fbs$qXM$0n zH>idj{C=*cVjXp=4eYVlz6AJCHqegKkjqk=0YIq*jaz{9H_2A8yaML(q$W})7sB=; z437bo{MbO9KY-MeWE1V_0WYDl0ig1WnVL@`_u}S#!TA+1gSu4{_vr&pty+PK-2Yin z$&Z25Ig;vO6^U5Lw#j^gk^tb4bNN+CX`%2ghHeEvrul{#wg-TTz+K*-7~29ilivl_ z0mqx6T#G{ZJsLwkKv-e8_7$=y2>^wCfn>Lzpde7*AJ~8rSl%AcyACX+&PMPUaF=%& zJkWs%uKQ=gS5S8&m=2s;xzrHdgSNqj6~ZD-IYGGqgaKm*5&YU(IC2R5Utj}DK>3}N zl_kn-_;VCq1On|uSOw+-x4OR5?i$G^q@#0?R-Grc)Ljcsp`r&Vu!NpY(#T2G z0jT!AG~QAC_DuZ|8M!8rAk0F+Lls!zQfyCAh>7R{z`3lGV`)SffhnqsuFq&qAqY`l zg{T3*xpQ##av&|G@e36i0X9BC|2`m{M6y?(2e|&m$An%@(m2Tr88hxnGTHXUN~KYI z05=;30<8{0qnuvwIX03t3491W&4m+U;}kmpVcOe{(=Krj#o0#Z@X*SSf&dTqL2Qij z3V>?>J$r-1IXt_#;Zj``4n|epYXhKj!iA6g3=@zY$;sA5!Dd~`Z7Id>0pTEINP5E^ zDeup#&@YwiWx=NU;XcX@A+G?q&v#v)eQ%v28*#QN0^mCz8mYtCWwPf0$A`jNIK9V1 z6QSDM46y$w#6avGkUa_?gQI@jd?achlO6J?LtmeizKp>os!&|p_>VFBcP z{UG;uUl3*mPn*KGK8h+60dwIK0^qJR*srWyMUAKdNbff<i!ltIyb-|

o z!O_X^Z9wu2aTh{&u8#s9f01pQXm~HMf*PI?!Vi`9Pi<`jO=V^c_FK z_2@kJ0O&D|cpi*I_F(l6xZ{BTAVEyXh0S`TxnLx9JvsI>Tg^*e^lPI3B>=%A8s@?3 zY`DkZ_5we`^dsLD3<5Jiwxp`Mn{S0S17Izb{L{1l_I7@D ztLx=vfHT>{I69g(oCo|$O}3QU4S>~A_5{ZH`ApYvm6{v1uqQr^KQ$OvWr5ijfXe z3KgOx&E{+){*l>!KA)%Oe)s%(-{0>&-H-M5`@Orq_jTRReP7pe-PdzJ_vv}}D|GN* zX8<~Q2Pky*fX*J!!6VT53Uu%eknbSa($X>syb0>1_febyPR{E=<@O5T^fNg92)G#3 zOT$ro06YP5p;a~8@pdliFA7J1; zAnn}&po?CJmji&b80WuJh=35t-rVEb2=zma5}CEGaB3)W!eEg(&8(~;c;-@}%G`@s<^w1sv9fbs$qXM$0n zH>idj{C=*cVjXp=4eYVlz6AJCHqegKkjqk=0YIq*jaz{9H_2A8yaML(q$W})7sB=; z437bo{MbO9KY-MeWE1V_0WYDl0ig1WnVL@`_u}S#!TA+1gSu4{_vr&pty+PK-2Yin z$&Z25Ig;vO6^U5Lw#j^gk^tb4bNN+CX`%2ghHeEvrul{#wg-TTz+K*-7~29ilivl_ z0mqx6T#G{ZJsLwkKv-e8_7$=y2>^wCfn>Lzpde7*AJ~8rSl%AcyACX+&PMPUaF=%& zJkWs%uKQ=gS5S8&m=2s;xzrHdgSNqj6~ZD-IYGGqgaKm*5&YU(IC2R5Utj}DK>3}N zl_kn-_;VCq1On|uSOw+-x4OR5?i$G^q@#0?R-Grc)Ljcsp`r&Vu!NpY(#T2G z0jT!AG~QAC_DuZ|8M!8rAk0F+Lls!zQfyCAh>7R{z`3lGV`)SffhnqsuFq&qAqY`l zg{T3*xpQ##av&|G@e36i0X9BC|2`m{M6y?(2e|&m$An%@(m2Tr88hxnGTHXUN~KYI z05=;30<8{0qnuvwIX03t3491W&4m+U;}kmpVcOe{(=Krj#o0#Z@X*SSf&dTqL2Qij z3V>?>J$r-1IXt_#;Zj``4n|epYXhKj!iA6g3=@zY$;sA5!Dd~`Z7Id>0pTEINP5E^ zDeup#&@YwiWx=NU;XcX@A+G?q&v#v)eQ%v28*#QN0^mCz8mYtCWwPf0$A`jNIK9V1 z6QSDM46y$w#6avGkUa_?gQI@jd?achlO6J?LtmeizKp>os!&|p_>VFBcP z{UG;uUl3*mPn*KGK8h+60dwIK0^qJR*srWyMUAKdNbff<i!ltIyb-|

o z!O_X^Z9wu2aTh{&u8#s9f01pQXm~HMf*PI?!Vi`9Pi<`jO=V^c_FK z_2@kJ0O&D|cpi*I_F(l6xZ{BTAVEyXh0S`TxnLx9JvsI>Tg^*e^lPI3B>=%A8s@?3 zY`DkZ_5we`^dsLD3<5Jiwxp`Mn{S0S17Izb{L{1l_I7@D ztLx=vfHT>{I69g(oCo|$O}3QU4S>~A_5{ZH`Apv17{2KlRFEt RzX2r}JYD@<);T3K0RZ+h5`h2! literal 0 HcmV?d00001 diff --git a/miniprogram/images/home-active.png b/miniprogram/images/home-active.png new file mode 100644 index 0000000000000000000000000000000000000000..44188534c7015cf6e1ec3360ee22d37f7c0073ee GIT binary patch literal 2480 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�TT!993zgPqF3mt* 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

+p?W7k6Jpl9DP>;A@jB{RXKBo3cdFBS?}iP@iO{ z!jpw}6NJvSls_F+aXr$E@aN(X+nACpbE1%3wEP&YeJfD}PAy}?2xub63TmNBy@6gX z>905O9a*dqm6#}y+(B(K`}_maES8nD?aH3Zi6!F@o+sX2OceCQ_CoMeDrrhhIp|z6 z>xf1yyX>RjLFae0sRIX zVb*pg#)!cE5t8KSK?w#Rv?n?Q{~=n4y{=(SEIo{v;%cl%4NxM+@j`&TWvyG_NMv7A z;S(*!00szLuBq)0?=PXKRKf+&(h(rTWmYpm!N0r)YI{tg0V3FNaXZ6C4Fy!hiy)z5 zTPl6R{q4)9#VXl*4eUgj=zRl#69m-#)vqM?S^oOk*F5w%UC<6uX|^*u=?~wGEhvNP z##fZ78d146hCXrxp!4&J#Tlf-3=%g6*%65I*8`x5ty--VGq;L#2xd5n^dd6``sSN` z$9md{`77T?J_Ql$ofqnGe|Gp(nAKWkXW{Suu`+-P=%*y>VWxnX&duG7(Bj2jP!;8J}d)##fi~qpUcIGjoN6rRb4KR8D`50#omg zM+s^DA$zyrj;D;{phRnuqeomx!9rk7*q_HfrTabdq|o08cLpF{nF?x@i8#oT`{nQR z-xkC;bYp-|ZMH;#%A9;`(U1y!Nc@f&#j!nGk0E}p@t@^8k!xr}dvwJR^Q~Q_N;NYU zhX{e$O~Yv9l}TTThsx7-%Wne0a|1;Qh-iYvK+{f2F{GsRxgRy(ztZd7TZX`hbyR@f zDc*T&Z;3g!soW9h&QLQ_dM*R4TwB#aXkD@eS2lBOm?YvRAwJjMTov z1Oyo~yF~qo0EB1SUAdex%For9^c`{e-${dC%H~SxuKhdKr^c9P3PpW7Nq}oXCc>(H zW6NAZf|I9(?<#*>+OsC2Fk!|LP+jGd4A3#J*}&_(BZ8zt<^XS?Y5I(nE=>HEFVB$S zzxy54W(T*J&lmx3EV1!hZL!e6&^As%?>{YzyUalNJI6Q_4Op1I5yHg@`I)rm>!&0n zP6F3DU)tx1sy2~`u6RRFO9rz*^6@P28!MUaclrqZ2EpiB4B^PxA#%XVzKa=k6Bwcm z=RrdsJUAaGKQ?jXN>saivsgach&iTPGrt3Z&rKi)QKWK34!G|t=(tCQ96&re^n(b) zIvpG+p+Ierz>0mId)--LCRNZxwLGmVz!*d$?}w*NlSo-K>NxSxql|#A$@P}W1j=iV zzaoT{u7Jnte6#-J_wU~YbsWWNStq34!@Lm$e=i<9CfM)nrvTK( zby(929u^R_ezEg{E5~>iGMIWxY+5?h-yW6n^ZJ zI;CRxX;(ZO+y9-hsF0d_ZW9yHXSA21nL+wz+mV=pbAiY^M5~?-h{pu86e+$4yw-C? zgc-0%30IXpS!*xt+9urIHU%A&-;Fm-O!0S?kSR*m(d; zZAGVV?2#31F|p0$AeMW>4hnfzf%}iTP4(?Qf3}m31wL8t=R~nx8Qz#8zL*Y3sl($Z zKJ~~%3CWH0I_>)ZQV$awBgf_gn!Z|L1f`Os%eYXreCGKFN{Ep(hbdF$natY=3*B^?*oSmNMf;-;~YLL-&V!E!;Ye0Xg!?46?M={-JHN7 zB5;T>5z0&TmPt!gwpVHK4cd2rw?NlS<3T+ci|%>_k_j*38NC6+RkNuw4ySoCU;91L(x6pht;9-jweAFR|l=Ey&-QOnoI|?BfpSq_FUd}Z$lmhR$ zm1}$`Wf{S_h_e(E(Xx%Iz$xX;#vU@wk{_(dO~1cyXlctX(jW5d*K0ng$wK|POTag( zAi1K=z@0`B(Yx4rU3}?!^Y%s97ul`a()DX!DqzIC1ryb@VjrpY1s&KPdK>-lBu3uV ztx?W6>$O2L=@Vv4`+@h<23F)K_ew#zzypq=;`ni^`&mq98N6bzN-cY*+h+P&)|?rP z6$}n$NBr`vNI#XnMp=6nW*`@0dwzWpVbyURHMA|osCXa!_p_-sn(OcBw?0GOJ}U2y z7ET+*SC~Yba3&GlwkcMBCLXSt5T_#eJ zYJ0eyKJynbdY!((Cb&8JK0lZouyPA0)3U6Eay`x3+A2)>a%o}aO9v=4zJu?uMF)BG z$ukx^$5pQjiGrY-hw$cEPhX}o>0^__*2GC>XbeOeRLb7cR6F7_I}4HZ$<-4g4RwW7 zFCchrp2iwELfh8iVsIiEL(mh)g{Icg810g2!(N$%IfM{=-xQC}zV(<;3cH-}!K{tZ zm=#9sF<{nyn-ZG*1Ly*xHsA0lWUe1E0>1>ILh)pPPC3!1U$lJj7~*gmB3O4qvhQ4t zx}1!HD`o1YsW6SpCqL(%mRu8MjBb`1vI6D6Q&>O4ep`@y&Y*GTQQNH;7&(Lq=yNnH z$OLNDkjM-qu~D!Q5e0Rz$&_UzDQX%a%_*wQ2D`Dhq^{N8-)c<_hvg6*r#{cD&+l?i^FAV-q3=j1}^7*)nJvq9n z|6_6sY5}*^_ojT1RUo~pPwR~zysUbk#qSYx-VxviA64{u0(N2g+%scAJw1_6S=4kO z{Vb{r5Xnt?IxGh&eJFyY0))-^Eofm>$}4?o=KfjusVB^1BiqdxU1C_N!UIWl_dDjc z?DL^&8n9<(zmXZJSC8|`KJH?ZQR=?AJSY(e;?uk|pWIw4F)Xg!Z{Ah?vjXTQzi7MCM14Ha0~O#eVzhFYqr@k!{@#Td-{Ko^Iqxl z-5Q@Lkkgka@BqgSX3G4C=Paxr7jsbslK`dHJqQW@@67 zdoq`XY@0U5Fjg9UQ(rDiEt{MUw4pWv0_PsmJm|HVe~xh==#0!?=f<#UywFlr zJ%veNc{dHB)w)GA{Rr;Em%8;@6);+??? zggvR8K0Ism=TtTbfm0{cp6Sn)Th_BC>1}TujPt8E}H4b84mwt4x#7`TO2!_n1fF(D{L4s=F^8#LQ76K`%`9M8~sF zt&b$1^pkykugTK14tu>bl zzv`j^mt5iZ_C+y*?)rt9Syx2pr10=MTcTTfX?f)}5bQLUI`l^eDUg})7LjEzaR}_Rhfan>Aa^6w;e7NX0Zoc-Bav&Dx$zO$!mRO1oVaB zg#?agu|XJn6Fn4cW3^W_sg5_xi^deEgxw^mHLJ zL7{9+hU-kd4K0ktV`_lt!MmmpnJ75c273f4(FuKBLf}}M75M8m<_JwJ=rDV0ql*z3 zmRSQ~4Q=i0!1Z))ySs>`>`h3r9v#qHVU9`pRTLJ7pL+W5)lJ;&ObZ##ESfasr@RRI zJ_(rC*T6SSpS6f|mw@#r5;pB;$*JHUtv*iu{*2{G!V#*o_|iK|AL1ff?*4%OrutzH zft&n#tzrjVextZ2tiU8M@TUA*ry$xAYnYz_WVyZ}Y7M#!=lt!C?l<;&Cyh~mFlT<> z_pNR*uj-(EtloQk9)3|D-c0i8pB)mZF$x|ragrvp9Jwgu(!49Dup}#I5%e|SIn@ik z&pztT%Cgv7dd?J+(dk80z?;cstF=!BHyoDSw}A^+SNOH|LWFynhm+cKGyHP<RSW$q$^kkzKe)+LjV<6-N9`Mi?1?!Jq;JlRo;!WFLM&!%q-^;-JdIle zz0T62R4GGhM>sw-6t!C6eoF=&BI8(p&*G;VufPi5WQXs&!*5=~M{jRyTu!rv zxNxa}*}odww*LOy+*yK42AtRS=tjMq1)0RbG&JU(b5g70Bw(|`pIfO^fRc~*QQE7w z>F&acFmLM#nfMwtjdUS7INlYa18Q4;Op`(~`zUII4s%>gAn$Sjg^GcYX9VX3d#RBw z!m;H2-Np-9u`EDAOn*<$fwYqeder%4xQ=bq^GznH)FwT_1~jsoxuNHxVZSMVH(*v7ehb{;DsiyTS!Zx zdq|?4+Km8gVxmE~+D2d9_szJ@eas=hNycEfZVuN^v_Bi%Vxw;M=|koK7hmBNligoK zm#oxrYH;Uvdakx@o`}4Y*GV+sTETx!7?lbwB6$J$nu9c%L?4r^4Stt=!}U2tuH@fI z$-kRF{hrp{9v$^T-Bbv?@OT>)`n1^Mtdf(3iwfX@y~Rs%L|}O*wJm>Mu!(U^vlg8u zu7N~E;cUo(w6Yg}8-}x;F;N@E*yC^-@bHKjzFDM01HrVhL~HDAGWaj@HuLj|?)P~} z4}A=;1rNJh@LebLA!dR|^J#{_?YAt8(lDg*U30ziO7 zumf^4(hv^Zkw5JrV&Zo@PTU&Hy%L$7kA)DKm<>B1)kM)%hNBKR=*0;Wb4*G`B=+`c+~5fR+T&Yr?xpvH+K zBKVyfsDX7NDuuxiAj%Vue@g5AS%-q60q#rn@}z3a?)rpUE z!e3hNv%2{~%He{qb>Iy=lBZSdd-M`b*#R;5IG1eR%QScJ6(u@3kuL~-VS_3F z6hByKD}|Lj1qb&@7*2{WK9 zPIH)gNrWl6ReM5QKG*En4B)MRUNF6N6;|jbb|O*_bb}195mr8%`45l3Uvb=m@;7HM zS)nr&=(kTuVJySg>0e|hIoaJBjH!z4RpkXY~rf4b5M8j$^2TeqhSs$SO#rt8o+@?!0mFnqF|yJ zE43H2dx?*aiM|a7-k{!&zqFA;H{c}~qB6}>^~}7MO_~C~Bo#dPO!K5ZSOo3+dcDR> zitdDVK!u9z{{F-M(r!ctfzx`Ps&v&L2rX;_eGNSJ}e8z!V{73a@Dx|IyivDUCRTUAwn>fhHxuyO}#`*!D&a&jBDw-kFgcoX}oV6DFpAfCmZNTzbwgek|bRq{5 z^4F3?rf+FU-P6jE!1%Y-540sdR<#riqWK|6jMH+2H|Ly}IGcjI--*~J4i=fV$iM3S5Fv8x?RLQRh0|spd`<6wVYQS_2(Vps@;96Vmd9T74%QI1}xB8h|*P zXg`Ti_(g+0{Z9FU2wlibu__tKSFs+s^f+RFxqAagi_lQzPIS7;7(BQrO(~0Qp*twr z1SyK_7iu3SI*pyyK~*A=t{8`;Pd~nVXiGSh1o_%a!)E+(@9&~4Aa^J$;{W}On41T| zhtOsBYUz}Gs30It-;S6>guBHu#sOoPZHefa1;O+{V|Hd;t+!7;CF7?ywLaqNOvpS7 z=lNYs-oLDhsjA4}GtDaEe*UvnRO{0`z0a6GSZ>)8u(Kio18Xk#!yR*@D6+yYzV-JETM!L`7D z@sY@lL>lOvzPl;9Z#2U%-+H5rFChb2oEVz#&2&0_c;8Uq)-Nh(GHX4N5xsyUlY#-g zk^V5Ye*+z3Izj$wQ!ZTdvEAysS0CdPgAP}Ea@c|Gr*L8PYhtieU(6@Sbwqm_#7?VQ zWGF5Yxw-oxp#HykaT{_XoC(RC*YP09cGws(k%vk&2xnBcf#Km4LD$ti+S1SfH%Yy4 zHT#-AQ98_bFK{=(-h?roJQ4YYoWWvzfWVYedW{)9;^)*+4PCo~ zZqv)#2c%G?lnlS#7z_>Cjt+Q3NbH^eYiC7!UN*hPC%^8s8JAWu0YcSK-OsEr>EdlA zjpfipSY#KiY-?=^CG+o)pd+mjrJFY5mQ3av=wbaTf@?H_U!F}+iyZcHbHKP!wZSke z1e6w1#8;;i=$po;mhzns+LtOIcV2hlHOzZtK?z2f5@4X6}nHgPN845V~-Tia9Mw)tBM z`qH8Y4Qm1Vs_w`^A57F(+{*>B9NlC-w(@m*39bG}KK()u8nk2N;K#U8TIx^=hUHDS z7uG5vBW#p5A6nR`1!2h}Ev=c%BH`un4kop5(%Cc?5pYBm39iqV@avy7FliRZcR21h!G^l&<QG)9{E31Xp^9Z)w7VD zS}2Qxn#$%b8Ap%Y+oc|ciQlO{U2mowUj3;X;5OwLiRjG~+04ySBgCCh{YYVx5tfL< z8>GyuP+JSHHv4|-=u}pG*-F2>cqeh=OToW&Rd)#R_A?g^y76-%3DoU|`nL1j+#mAV z#newoCp|KpgmT1TK-$JsihNqdGmKP$!SYc$M<|u|J+jBM8jSNe5BR=?2R_)E*(U1M z51N1#1!1@+5kD8)$w`w-ayv|Hh7o~4UIi{?r$j-<*jzntKZNVUC&Q}}@;*)w#Y7o8 z>J%iTIP!Inz_bA2px58>9}uBlm`T~8WTd~+o<~7+)A?33u`L4!by>T}H%cSz=aEl- zLR{Bt{;QiT=f*GJhhbIVU9Bx|6m#&wv{)&NE~uQFQbvW@U0k?5Dot%hephTD3#RLGRluAYjO2v4>PB$O$ zLHg^|^fMR?i;O1+fBm)KO|4RPK~Q3Xwg=w0Rwzncp_AcGNEDeSC+#r3Cks3*G@^aS z!{Gt(r%wdee7?l(bJBkCprl35?4rW>MP9H#>~f--xhdi}aw%Rmx)&$e>*y~vtn%)^ z!;inhtEqn*Ch`$#u7D9fI0`>%<-%L`L6uK_$cf&Lzr4e1e6&)F!pZlb*}AiN`^HFd z8k@68a$j0hLM}4|kcBQ6hw#`1Rw3#H-#Z8E-9Yb zFI^WpP^Y$X+Caa5Vx=JpCa05ys(4GTj2}la0YFyvFVUV>kF5)?lQqv(PqnM02POwCn zFl0gU8N=FA4RZTwQrTcCYRp)}vlOb<;t6u_OYY27UsGw$QLo9J6gau&!z-s)$h%hw zm?>rxdyr!JG{bi*i#IzrA{ZACg(d|9p!_#>M^m8JIUOI2#*cDr+6%*Y&l~)Q6`dy@ zkDI51oMT>x-r>yRxI-}idJL{Xqv_KIC8Tvm5&HPYM&wD`#ed8Q5UQFPN+1PLcarMa zbMSKlCd1b(tR1R2>(4%wIMt^dsk(Dg5mm9E3BCH50Hu!7a+T=2kjH3_$7*C0#bL2Q zkg=3i$gI5Fzw>ew~O2I*M=p zjRyqKzxj)c$fijvhc=K4_zXKtY!FF5rnV4mpx$11sYMP?5<+vk z!*fl&i8~j8{@!wPC}}S%dCXNGmf|d z@j>cC`4Pmm{MXdT>_yu|QaomWhi&AKT)PD_ELr~DdS(7E*);?qPg(hT%O~Ora!AGX zjjMki8Y|T-H9x*f#5RFOXRwbGp-mU&y&glx-@lN%&HErshBEa-YpSx2_q8pi<={H! zdDC~mlbsBzd%Z4D}vcY?ytSwSNyG>K4$wjYaN~=PJ%8wrtLgTp47avl23># zVR6pV;Z--UdxOKT2ZhbpeasD(7&d&^%)v61`0SM z-P)&xwa{Qj&V$|33b#@z5p0+5yW4v46hY z$t0WGOs)WaL@^fi5$~M{(R+O5+bse{;i0;4_+5`#4(;qJL)_XgpPRf$r&J;E&x7;J z@j?y|thc45znr=Kk0R`7Y{7LDVW%CCoQTgY$7%Zu)go~If_Z8;l)dTcUCbZNj1l|p zJkW$-5O!P?w&E%sKCglrtrCu6)7in zPtb)}>=~uP&y6wybs`jAxNu5t>*+BK>G$%JfnM|tz6$z#nvf|-%Y8F`fAun{loU0! zA+-Os;v2`zvus5UAk*J0n7gp&@<)A>?O?da`Q&$%;GpEowiq7|Pl5|bMAxya@oi6d z(^t==1Cq+*7xrume1%gJBDtW#U(axBZr;I~gt^OFoFb?x?X!TfUHe9^zeB4%z4vkJ z88?~SIe)Esw5dgJs0h?ucJ;ZV)F-1_V8V}TJm@26G}S4&OXMPul6I?3X1V|4w^tqg ze-rLh@87Bx;~J3$4BCXs5d@5 zzuGi-Qu3LQy{rwlC$Ee|wvtk`ih$!_E{=z9)Sp8XemDfjB$2!zY>0?k=)_1;35~kV zU#192h-iSoH?Ls_F(P5{`>>Gj?NFI3xwoHGGNaxyD`|KaUT=^2aJ@OaNW!R%@*Y9^ z8%IR`TQ8dx5#YC$3_kf%YySDxL+_RD+y3$2yFtpsIe>qmy`oWTR>_*CyNZB>&B<(% z+b|n~n4xu(WTr&dkJ+wRahQHqpr)apwI*k1 zHph{|%TDWA=^0x}D$7!hp9**tR99cGW`+2qjGt8E?I2dVcImW6wBDeiA)%q(+K`fS~ZEfVSp#=;<>~Z%hw>Fp`OOM@t{Ql*hSdpef z$gom6@Z#q!F#V+f^v4U^U&AS|phs4#|6&TcG+Z=XPR;B&IN|0@3cI!O%XrSv*7E!e zzNenKH=6wqxaAIn+*T?uhL(4qFgN{2sr*;!q(!80-Io1VWqZ1i8-AOL2z{zf3HI9) zh9KA3b{>q=QWIP?e)B(vUylZhyj1{anq892idZsJ*u|!iF7IyC_=T6UXsGGi66L}C zDWUg7^<#@c;ny!kHiVo#O;|{FTKU7I=!yknNQuE-B@x$u`f>jzv<-AIQ4<}m zq6w=$ORx6n?fpB1a97&frSG@6t?x#9 z1u1NNoJS70MkfIoYIW&DqbcB+Cz)e=Y=e;TMFf%WB3EK)Rt(i=SP4OS@iwF+(#h@t zR>e<(VOEi6^~&ju=}dN>Ih}#D z=Bl3<4fpe?52>OF#zMN!NdRi1_ubgNcV*hskbKBUB>K6MN0*BKH6a6W?$YE2XCO`^LF_s0fA{=Uf6%|9Gdeb;@{G1!4E>#zl?l^DkFDJAZ;Z zXCqc4H)0nE@EH${S^dUyr)wo~koJXe182|6KcDvN7hHdVy_aH^=Dzx&CMAdGRfh=- z^l5nyKhwsjUt<0O74)2YkCo|LVuf!3j2Bn&4K&b52f^Nv5$K5K(og}|c z)zot$>dtn5R?UTKO<{d?O)%}aSY87-?929Ik)dEZX;PVM(erd#1h;g9ZhytQr5=RdAX9{fl`d_WU+gtT~9bBlfop_4*A3}%!&;|X+dESydH zw!~I$_+)Es?e4>Dt3-|d*VXIb51|mp3PN&IqznuEax#>k@eg(b;#@3Iu&$*m<>Y$_ z!Z(@DH8kSxF%)Bk~7S!>HkvpEGl7zZB=*ff1>ExwuNDMtI} zw?(8{XZL?BB|mHfh^P z6?|A;VFGl$7?#qZ-FivwC$z>JO)7VGborSS;Gvjlc}j#!cn;OE3=uhGLj_h%HU54@ zjJwi5_}*h5Ey8FoI|Uj$jlr7Q}d!|XRLarCsJUCcBao< z*R#To2FIIgV_4`jkIU&}G*j@B_!>&fWlZP9q^pmp+pAR%j{KVm<=>0u|C~m+BR=xL ztR8;ksCNos{vpXy%51WqMKv?0ZCuKir|x6q|897}D3Z7}?Bjls0~!&@^3(Se74e@- z-u=@-L4p&m%#XT>FtvF-<=FPg*t!}IsbP9L^R3lSMG>BvA)L6Yt0$c@6Kc=g7+DM2 zTi#h&JIOVv%~o1I|L`>*Y=%rMpRNg>R`1=^dxTbe%*YSbP0Qq)E|f?1Ls`~_ccy8r?*I!#N&U=EzQ_s!rdMpq+b+O$#YS{0< z=41aX##DCw5R!?cro9@|9(ewX&LwhG>7cicdFqsTcT2>3gjL_qw#1b?Dj0_a$i=JF zA%m5~G&Kz3FEKyr9u!wb572O9pD-Q`E@Ptvan6jR{4Zg!KdI)eU2UCOu2R`&r14-2 z9ZyNf3v*RZg{lV>LP zwaHti*DmXW5B`aB)>t1mw}MQ^+B!|8gq1^939dR4?0`z!V4c9bnGssAEj7U6@^dtC zxk3lVbnH`XHlW?Jort4ru9TF6AEs)QJSj3iG}$4hG)57d|SOaAM+U$=zG*+J5_dGnp3bGqKVj0q)_U-*&NF>?kc)IXWE}R z`G?xZTRC~u|NSf}E260PGCL=Cum05CE>;D6Dc>(#RsG^KVkl2>V<&CjVxo|$Z@anW zOFho6o!`{4qb)Jf&;Bhvz_8VP8yueLqcu7({)ZyY46u4M@W@zppPLPyw9fO;BtxyY zfMCEEPCBRCCI0?J2mKlY7m8oE+v?G9Ww@^ zDm;AihfbX*3Tb`*a-rI|Pj03BHF-L%_hSm4i*EFC?yLj5b?4z1V<|-i8K{`?r3FOc z?W%DXUD)WeZPRe1b<9SANH=r zYf58GO1XyEuRPt8-s?E$?(E&v*^H{)oTf9!olr$t1;1=)Xb&`WE*@X)P6RnsyIq_tDCqHh0pW}8-_``R^b2EJ5L@!>A_)$!}j zM_(JDu7BpN|EPPPaw-MvFLpF}U;Sv=u<-efl~|qH#A{dfcSjnnP4{k8<9zmudvi4X zsw_qCqnj-GmLGez1?Xu^=}Z#D~>`HR@3X&660!jjLB>_#3$ zZ4yC;48IP-Vjv|E)FooprxwF1sUfz)VCtipJLy2Lg%X_+UxN6v&iT-n{h-z;zSln* z1T0+9-F1Jtg^j<&4cw*ZC@vb&{8g?x@D_SV2e30}rt{HwS5BADu(=%OoJg=Y4(RV4 zio3ejI8{Fqn7sT~9$69cN}<3d?mvMwn$Z;2&T!_AgL+g6z!psWB8^IZvGHbWHSDD2 ze*m{YNWUR-#i?=FF$^T$nqwT^hsQYJLn&} z=LTGm3D`4C#J->ImLyeB?18$`pB*@G;1^HZp8K=!On!{@jE{CzWT$#5hX**6K7{UQ z1_vSu;;XQd!=^YSStg1D;JAZT{C*B4fcWPK$k9r4K#D9KTRjeNoU-(Wl~%DUj(hj+ zDKCXz6dd*#heSCp3OGiwMh`s|kwQ8)if_tQ5eU-=jsdRrLnAmgygJ%?W@>T}9B*US zC9q=77>Au4dr-^Kh>z+UNs4+6?Up2>CXNTM+E7@$kmnYrH*Vcp z-YVeuKyN8#w>+bxnlMUpkQ;8p8f2}&;f_`6RUAq`lCjMN=gt`=U<3!qas0`aioj)~ zhgCvKN9FLoZR7^VEx_?4oRyy*RpfXNmel&??_PLepl|nSfaAvT=4n4m#I!P`d%%rv zadk3FVm6nYs~`tD>z{_vSNXpbhpg!Ll${(CV$V!GLv+xD99O6~CZ7U05aqD^nkj(e z;43eT9+{AdwUL2Q%IMC;zb1FJI+ISP% zPxf-%QMf7$a{wH-_O(zPy2U_~M)-@kL`zZ(D!)TT*@LtVP&kF%nK9IOE=tVN|J4A`LH*wO$; z4&5jNx>fZZS|_ zY?h}|t1>uRkrmi&QBixFEWvSDd=(@wcFmaBF3pP;+CvGBS6|$A?5kn~N1yAl1>gLb zD(6662s>{Aykj?9ve92EzREfzWCa}#E5<=z#rxm_xk01onC-7DwM@Gz z))x%lEya;xA;r$l2jR6(I+7ee8D6$Wc4i( z96?m3j|e$n%Wth~tvCcUD$s#u+-Ki;c5vdTN-39Z?AC3od1K=PHcuwQ7o-O6A<^JK&;8y#h3WNy3A!nY(*BCI< zuv=4$fI-1g?XFgPo|A5txy~g*oW}qT>5#R2l_p}$z6ahMKm8(*VSyuv-heN?@|)iP z9Ly*G^fl}sHh}W7@ipV)>9|R!3@yI#uwmu$c#vv}S!Np3;5a@$z6>^gY*$~D^ol6?ASapdF9CY z^GA+czA`xlg8b~t<*T(~3(Lmh$r2XytZDn>-Mdp$6Px$Ez($qUX>&=5?;cyuS;vJv z-6V&_d9_kuhLb?JLD6xMqGM#;SPN{B9D85gpAcklDF!S5W`^;_;RDHWVbimlR&q%W zsWzVh4srvU?L9O)dS~^>>Lnv1tM{@p!!5B;%QQDsDG#&>!2xs-94B^rTOpgK z$?@jOO`BvZgsG=&o(jQ%f|-A-gsm_G_h}EXJH?RO(C5`{aElAAH)wEtwx6Y`+OMNW zg&G2miz9dgBYQPCoVdB?O9>c0jr7{Z?_DhYDt{`h{R+teYS5;^F-&g2MCgfY&b6f_ zOCsPfF=;YMu1DKD9O@X}n1&_tssT2Sh(>`|4SBjk2VY^%uttY~ThGK~v0ASa9Vl_1 zn;cql=1?Gw7#gfriusJjhKWg5uFSAsbe)CC9j;Sl>&#!7}M=syJwEfbr@$<$L&Y!tEIr*I{=U4e#nI{tJ z^!eM`+qCfo=dT{TkFL$vaDw#S&K5r`407J7@F*mQy&aKdPN;JgZfN`Oy+(QkMi*tK zts4V4Xt>_w8@n^bIdVW{H^A}Q(1Yqb?~oj{>zKk=pxAEb=%`#r!`f~Lj-|+xY}gwP z0^M$<8Eo&s%FCm8bYyuW%5m%Bq1pK_D+|wR&kXz{g5w=5PW-)lmf%p}INQjn?sI9&LeH^EUj1zUR;FP2Rb7XB%}^($Z7O3gvh0dVLBmBJ^<4YjDZTDKj7!6HGJ z5WqiU(cZyaD&R|1u?Ac|Tkfx7>i75LOJGRGOk{hy@$Yhxt5m}{>1vi$Dm_tdm%+U; zhJlQ(R3w$_?C7edRekB42>?rSyu1wS<y|hARmQi*z;M!DDvAjPvJu%!T-0`qJaq zuH6<3bQIHWHO-o%;u2%lt@Sp^(Ha{t+me0&hsBN7*dQjJdFJjBO${C&A3O5W!W+Zp(KxHT0 zm8D}S`f4)zD*K@|3ej49c(C4IE@GocjEP#MR?fyUL%jN2*!fZ_lIsFHZeVKVa;a1# z5DBDk6HBF1UE}u$Mn@4EMM>GUYpHY9T2CR4iEEb|cK#v0yH1k>isROexi)9StI*EM zE`A{gxB;8Irv-=TjqNL+J#rqmcQ##r`}(GLj+_rfUfS7x0WE7TjF`b326fkPHhE#w zOE@pu0_Owx!kw2^9=v+wNaXza2;Wg4;KAU*mFLE2-vH~sKrN|Um@kUM;Vvx_aP*=J z_3`A!wO{#~HcN42WdV+cPJAa^auq);9V-R{gsjdpI8u{UX zPcCbillV!q;qgdB{e(l9#(^A&L`}Uro84MPIllGsF-vk-o{1VHv%AtzWU=Pv%^Bbb zq8stCZyrVM{7VfY_){ltoPM`c4$3mWCsmF+GG%`|9-VEzqTxV!$&C{!Km+d=FYwip z4mX)W!O+YNm0kUukVBVp%*72F?@C5+V*S$!9G8U(tH^?8JAW07(1tUCXYq;WJ*!$C zIk{)O)J7IhXEuWirX}xZDGtZ(eajo+(V+gRJQ+pRpOTe#X4rM@uPk4>^rq&;^u5)4 z_wP>^1_K@f%o9ir4tJyiGHwF73VfCqN;`cXHO5jMIiFvS(>h>vH&9Kl7~Q=W+#tK< z#koqxVZ%mKtMveSLYAuqiN;o69B-}ll&fkXTHQOqm&$G(@IL?Rv_U&_t0WSoHY#<3|WgaC~lGr3+F-tjO zk7LW&(w47KaaiE!{CYP2xawG!m^S?}2zj-6SANBjNq4|$;d z|1^$|R3&a0^iSuO9J{uW91`VFBU$soN~+AtpL+Md*bI&!Q+7zLTzF%6l@=HHx;9=7 z+-_4x;)_sY@I3^|X1W9SfCalL=5@vqdObdlG3#%;?UC08q5vFEdUJOyZwqAE3jv2N z<(MlMsO_-_uN?j36{Jf(`kBvs^m9{3R<(NuE8R_2sr%a76f%6dzpmX_LT7t-&O`r$ zoJoc1-PL9r`RQ2##B~ zZwm~03W|sln0U#%0P5kOO|o%JXW}+6Dyw zNgUZ=oy1@&=st2G)k_Vl5}wcZ1xx`5(LDFu-)o%4G2+ zUS!KXmHtS3`|U`82jR)}7fbNC^Tqy7KaR;)EZ~Q}enb?b(PG1*JylPXK zxY(sC*af5$)3H0OE*EliGt;pmInHXyu|p)soZLVv5KhYXk6uL_^vYA45B%jXfBD7K z`S!M4$sVHB6pyIk+13$FC*f8j35F1y+{t!Mw`<`dhk>pG^q`7SMEV_C~_=czN8r(>-O(2g#ar*O_Km7Z)P}b zCOg3k4l$kv!TC&suEJS6y?gXk3J!7uZYRY{J?xl;9eB?(zLTvQMhjypu za#bh~hwdXFt9=!_eGll^e*H)r#w$GRfC7ex-nI`1+!1$$YaGj|TrQ&2bZ&5PFl5JDVmVz9~qk(4pL;&EZW=ywP#U!X^ z7#)>T%I5K?d+W92K<2YGILHm@l-Pc0;_hW;NG4F9@s|S!CeF8GTsmef6$AC;DuH1l zfC|oqTt^d!=XQomp;he^Aw@_=Bq(QE0WX2La2eVpag^xxVCq&(Ua(sz4q=Besxq=H zRa#*Ps8vAV!JQ^>5F4!7VuV!8Mvj{Tj@7U3&*D?`)iGEG)kQkhUF#n1r1FWm^ATKJ zx-0#)K}C*kjFTRlMawxr=EUfsF_e!(^W|#TKW^C5=IP4JTua#=E8qfx8%BQgBZN4X zVpU+rT#`eUG^hwTm{`$vMWn~%_wL<$7DFRf#I&dc+Q_!S64u)7_k$n>Ga14gDI-%* z33&WeK$tj5yQQ<@W|eIZ#R^fVM83-~k60WP=ltP@!x_#BITjCZM8nF$$DN3DkQ@Sz z=%1vH!mc1{lj=(_dD7zpD0&JVevH0wJd+N! zgZ}BbDh9J#kB>e;*lwA0sR}^*qEhZLXB#TRqq>&kNr{~|fuecM9q)=$GjhQBOnaJ+W^0^4GzCNCdBp8q)&tnbT-FLWH#e|j90W%z$wATay>}pOhU4bYP!@Re6SrQ&k*MR*Y9ikWSspdXF;6yDX=IkuTSRg^u_)<$d?DPG$K$1~2y*=D zuV0TQRa~@5ql63DBG_TKQcnO{EM^!B;fGQrcbQJS_jC$gW>+-e$E6s<{O!cVdfK-qWn;%l1&*t$+S_V4 z5Xiu|%`yNs4ls`jvTy%5CS4WV34_BD z4#;AxxWhG0w>&7;Hi#5Muz-WbRK!U^RZdXd+zb00$xd+Y;;XAi)@?}L#%r+xU%1FX zLD=yS;}VDp9P6-y$wRhQyHl~ip~-Rg*-i2diD7z&{j4;Q58v-8g27miDuIMxjLf-ezupQrrSB(;I3S>MLKS3 zaJ*VYsI1I6Q}l2yc}j*GM_!6htLESi_k;0fRKQU;dEN}mg5E@m;Mj-auZsw4`%Ab2 zge&+Zb<;7YX~`kG))Rf+R@GlLS0=i$BG{NAIX2;o!_KF}*WnzUe?ktr0jO-8prgcV7RSdI}LoO_ek=WK)#Dn<(8?y$QQ05~dX zaq1Eo{wDk_)=j{q9fr$t{Y)-5mF%FaW9Sn&_tK+Ua-d_y&_ND6Fzo7MVE49V@Kl-< z2L@dq`khuJYq(aFGXO_X0&WzJCdqa=s!(ZI&JcWzz0s6@xe5=(K6 z?G0hT6rAv&`z7TaNf-tXyapZ1{Hv$O5yU~Y5gZl6ivvt2(hW;yG0kq@VhzW++f=#U zppmHq95r)h8o#MwncLPcXLtP)qOVorpWmhh2MgS%v0;Iu-Cr~Z% zV}1ll4<G`ENa7^K1?^+~Q zdY>q8MRBJNbXa>YHr?pe=4XQggR|~Hi)|@DaQL$Zpoy7ZgUd?F4w^>T9igvpO zc6JVzph469A!WXHVT);GyR?-I1EZ&kUsg4Bysfn%CEJi3+Ya}^!+F@vJ9=te104U+ z;MfIn$O0XQj~~PJ6_Fa?p)6-O67wQeTjW_K@-i5--hw2HDq)=CY>Iq#`Qn%e&=T=RgL-NW!mKC z%?GwmoL|)*i6ep$3p7`AVYnfGIfQ7h2te_xT_(#35Fn+sw^hPG4uf(8A)k~=i~mI8IaA zC!oZV9dZHcV)2$6l0T$Vas^x)2m?RI7_1cP>}psjg?DrN;)n)EF6|AP@eanx49CMI z$LQYWAP0&^x%$N%ssBu|fsH0#MYwU!M|m_bU}U-^3!F;=YX-nnQ)$Ksj@G2(!f1^X z_sbC<@h#>N_db_EY&u?vAesy7$Qvv;t>G0Fw^|B%o@`gI4T3bW1=x7+d!JYWFEGq8Ah_%SLvb7C}ZP<=du#;_QagxkQn)4Ri@ML4x8?NemuI!E06_HV>!Ra zns)UtI_J9EwRPkH4h&n2ommd*%nI&c-(e*=UWT*6A+8x=`{QclXa|mOTej@uH*YSb z;;_I$+r52l!Nmh$ea@>7t$BsRd z07qvS;K=*zhe5;97KXw@L!}%Bw49MjtWZI$4cPE_2o6s%4DDRuk9+~4eP@ydfhmAP zE|Ogd0|DTE4;9BlC!W{f+M^;lxLg$~mKJa$gSWv9J2n(KKn+6U9T>=A7aau0>2WCj zPzGl{7LcLRZ6Xv#cEDMYlzjr=NOnp5JrAK6u6XQ8eVl{Th7s5`ymk;pHD($xi4=?` z0WovmdY4EJ3Gi;%yIZT1W{ENsvjhiwW3<%Zmyly8qja(0Kroq-Z0ukLUKE{`1Y}b^eccpphEy{WI!F$( z1An&?98P(`49NkClp_>s1V>a=J;m`p+2If_Fm(;cI>N4S$SMFsh^}B7u0j9}UWBV1%|EdXa6g)O? zPzBNP$YP`p#f!rck7y20z$3@WjyLqHwEGHd0IB1lu3EX9a3hyDUyzj-|i_xO2P?_{AB2fE9h&RK(?h>wwu zuD|;5QA==OpM*vgSg@&-!@7qr6&Z!zuuG2PhYv$+a7~|M$4=jF0~3N2xC;7l$Obc3 z>qzm85I0034pE{TrDvX#M9 z!0JeO>CDxuM?4-}9ueY5cJPA_8!>v~AhgR}ZNM-E@EEwzi|=hb{0uk-_Ky)99~_bR zsH_bNZrIfZ&7uZwN^-nKbXY;sbz|e_5Sl2s9gsqzojfI09L60k!z^;tAbyAwQjpKR zGmdx)2aGo1$~7o*(vfTx?`+!XVGPa+$+53hN>&SwCdsjBnj7c% zABMugp>Tmo75Y&K;wCa)XeAjm!x>PaM`dHl5V(XidzEYzo(eTKjE@asiB?r9+A8~zB}_a59E=_9T}pB+85u`A z=Y3#9`6+g6D7|q`kwY0IhlLIZh1KiEukk~7I2hBU1riQGBx}N5Nd#Dj%(#ZM3K8Bx zI0Ez*E%To{y&Bsv0UUgCAs9r+fJ8SP4>zu5_9|Y;*80`^M~9jm6(>_FM-?%eqa2zX zI|KO0>(HvG8zDG+c{nP`o-{*#bHu| z=F6JMuw()ZTI0OTKWtz|L9%Ydev6PCPi%=jX8;^7t5&epjp>k?ieuO#AYDiW#?PP( zaKx)9YQ(P|WYb9+206zO$P)Vp6YkW>j2!_X>7#UW=Xu~8?#cxy`aNWhEr z9yraR*c78HC)e)SL>F>P^^WhqkB|94I4JBsiv_($)VT#)Beha@ppkYhFXlcLu^!!@f{$=1<*;;g{i!uo;krkzzBNO-SA`PV&5 zb6mgPNT`V7c*jSHgDvLcRX=^bND}G&*kHlyG#t<%2)P@`SPc>!IB=)o49D5Ik))LZ z;v9OS1V<<$?V*7X(&5Z4;T0Vj0f%FL6@V#n80qp8@KqLr92qz&TzJF@lY!oRlh>Ft zB}WJ+m6cixsleJuO9yj=1^P8C-9yI2F#9>>dNO7_gwa?s4OJx2A9iP=@CzwHj6{ET zwYwi})Oi#Cf?jRsIC}Iuz|QsJtSm!LR>>qDymkCaCLwVPPlb}><2MoMfaI|3nBZgE zySLjsh-wW488i^RKZpYXIZW1Yc;nHWY60;_8-ZDeGCE!U5W4VBoM?nN@E{A+%Xpm} ziAt=v?IOt9G)k801309ZW7=79slD62dj;SiIfxDEiM@=Kd!G5mT}=N@rbb-YtWp~m z2#ASVf?hvWb`_7WU#YPA7TFQ#)UEyzJrqT}5Y=WZjW6^GVL^H`D3iQ~7I0u?S8 z$CCaWi)kQB$;GJ2glAA0QtI?t0`=8mNUA_mAwZR98E3`)NW~$0#7LX(2cbCz4EqG- zf<(zmMR_Qi8gJgY!(KPOC!xnn9=az0M{-aiO*vdR@Spc1B!egiE`KGahB{SJ#c&#` zl*!3I4gm)xhD^j#0ai+f;Ea@YUYI02LO*fykW~o$vEnTxz6o=hzZrgSZ z4N=`;?*L1KVSWjSCP59+9YJpxCsG2rS^f`rpfRK3-2g{t^@Gb-g{2ewPv&ACo z(27IvaVEI1-CWjo1grx4g*YoZv!d6i{Ov(;Ls(FhCx^$KILcj z#c)1CaKIQJT@G+8Sw8aLeZYoYZfI;wXJV&qlduJF;Z92D@Yx{&$D~T$BjBiHa2P!v zL>3Wcta{MthU3fnu^eZwD`p0-o@|jU0hB}r&43% znik1TIrte-j%W@uuKXM-$BN=}hSEI00mxwoLZEfHZw7o8j9X72Yntq*8cbr4rX#{x zBVA`m3{Q2-kd?}FCtUNIUm`<~)R}CPJ$y{%WYq$}q#kcdacDY1maJI=Vt^Yr0g$nq zKi&phXyZPwYf@;qESuheWIy9uss^mHUQ8g)5fo5m#7jj+!i$L|JuG5ul0c15;5LjU zi+&_15F3r)@DV_u)}Y!@ileC@-lZi6w^;eyFMc6Q`Y^Mi+au7+@{KEh|BeEOmK^IR zj~qF3=I+rWIL2~t$QVkOYtvQ1`M0c3ULY*hGLko^au!D8mh%NveKl;(?pTGHE&5f1vM>{GK;g|`I zXSF0DIbf324`V!LB{$^B;(`;d`{iP679yUqX?VjGB?e2V8?~&jFU(+C;2ijK?bbPh zqn^Rfqkbmv<{bGpRS_IOHHyltEJrzt+xD{s=%+uZxUuryoA)0)$PgSCNDOgQG&llr zFY@2Y$0{RLY-Ev;qOTRMGu7>Ou@6ULN=|0l#8_Y518~5=7F#hW zPd#p87%(h@gyz5^g1ynCVc#pM`R39&*^*6h^rTpr0Usw`lh}86W+D0uJm}M4D_Ayy zI&;$%2a7$qf*S?H^z-n)<~{frOO9}<3tS~@5XckeN_lFc-o|kopPLx=f%~L5E4qNR zPaVG{?wqPN9V>q=Wqd&~fK(Q_{DDL`?2=!R9N}P=( zKIc^e4!o(Xi%Be$bzHqPageJC)Z_TvGh8Q@3-|TZnEV0_k#l(jx|PB+M)$2=f{l&P zq5h+{{`=pz-f9vYR{D~mQM=@@$bk-)6#mDeiHTKd6L92#=VXqJ9<2KsP8AG<90yfl zM2u=a1J3AiwN1Y?1Rtq{{OjH##-x_`Kn!;#Vsc}=Xc)A^S`c5U}NRbfkTT+M;?Bi zhLPm>`YUszDF&w*aW)1sal8saLeOry5>Hc3030Cek+xZ%*QT#roVR!~ye~g0_^eD1 zS=#y0UWcz&>e2cyd=YXmSP_>)B}f^pD2Ib3l^!@K0$U|GPMm;0P)M1Uk6MQH7L3+Q z7Vx^It#S3svY#G{yjq8092P;=(-z;iw@2^70iN2pdw^t>6F(Tc8O(AX90N9AQ=2dF`AEG@O$Yirqdd-3SRk=G|~ zQ{X@=HVy-muS+4`u!oDNEyO$59+P%(j`Kk^=qnI*2Ssir#&6(GHAX5!d_FuZC`+5{ zmo(MXz%K84eg-)9Ef{QsF->GciMD^* zU=~4#SyR^$XL}`a-?FMba70nW;@plsKimRvK!Tdd!Qpcvogr>wg;?6x3d+J!f^|>We;{bD@et$~wK?HbQAN6IOP zth@{`@7A3`6~r50L}L*AE8s`E3EKc#m1Cx8LWtaR7ub2 zaoh>f=sIv9$w6>1Pup}XpT$1X^+V?dO)rwBd4fYMYJmXK9D5wqSOXYzyq32V6mXn6 z32+q3mUnAVLtDqChqdRIk1ecAkHT1AX-#F2 zKXN?)he|OHTL@lVd5HcEx*nEcJ;)tC%?*0{X!%zhW zKyZv6d~>Igf%TJv;P_b9Jm9}&azlY*zSZrGJ;sk9hKIv&ag+!*;4kw*QI3PzxL}H^ z-#5=*il#l=l*JAl)6bLMBT(n)hnV4;bh?Bj*%SIpb}+04WaU}G<^0dM0v+OF%aTWc?2m%;0= z0~|*(@EQ_4AH~fGxd9lweWCzXPoliy6zKS)@**A2!NN#=WTakea00C{VzOwD4r+{! zKtd5i1Q6@4+x2pWLZFa?-(ZR$eGUdILJrD1UOI);=sIw$-iTNQ;J~|9gTTwF_fj0% zP3LiVEz_%eSa8Fxv#Y*@cV57;2S;N;<59uSKRmz6Vd>{pQ-~g2n*l-y^SCqBdP#OW zZ1QaNqYefc81L95$4=qK6imk{H2kvf2ahI zGc|PCrBEEU^87d`<%e#xDLH8vJPrBG>cvDhYX{}-WugR~LC{u|V>l~3{4kIM)+NXO zE;+oi`xd{xlv>`*uzW0m6-=+HWWXzhbV`alN{n9 z6=-bvw5eD~;jc>AJs&n`7b+-AEASm8G;Tt;F1mdDMA;iP87AMW!A31>F}K`6JO|YuM$0A!JakixEQ_%t^zCSa zp~85{MJ;aA)=P8ThNhK?uioCyNhqD+Cw@!}d~OEZ*t_?+mBseju3K-tPT5`8?J5!- z6Tcq$6=}^3s+Z6)pQphQ4wQyMj9QJsxa)@y%!M1w(lKj32E>-g0Bnc+>_`>P3JS+2 zW#&Pfg@FMiN~nL`pf9N>1Cx>GGDZ;FWLa9_kkMswqi4$FAUGsa3C6r8Ld@}MpEYEz z0g^~QE8yfC5m=)c%bE}?GTr&ZajC`zO9VAy#XW}sj^jT7hDnY=pimL>?_^E91Sy+0 z_H@zLzX9aXP6xr!)0)fTlQmhZbZj4x(Ibai3ETR5Y7ky#5~miuIflG43Wwm}9Y}#g z7gdH>Y;_}?x3{!{p=;m&Qb0zOJpy5ISAx|fdn^afHyWvdjg8#kWCf@>K#m)zN7;XH zZq%>9!De5Ljx`MqiB(#i2yB6~4buciLA;7mz%QLj65^UZn%7rX;a}OK4$6+DVJYaq z+@>KhyA*EKu+j+D_{3b@-g!>7ww=MKB!A!z$iR^3c)AF$fl~m^?sDWLT&ZNJ8@=#C zr4*nyJf&`2h+}1d)Y;$+>Li`z4fAtZoLR!?t(q?NEuVRj!^L#tXgnT zI5tK`4lznjx>&tb$>*|u;4cxU11a4U0AbLG`OWRD@(Og~S&!+riZTNyjG_z3iJ49t znzxfl>McvS2Wbw-Ic~oB+j~FhfP=q2rd*C2r+*f3aEec0otYNYbnF0fz0ieONe5ts-G9n6}eH$zw}%H*GF=MondiEYX~Vh?j<<%?&R*d zL$fn85C6{EF``K`A2MuY$Izob4zSdm&4B~t06WkpizA@zgwL^^+mgMgpmaTs)8vK* z2ezy{u6Ve>9yJG#CuD9}h=Ox|8{}=K98lm;D*7REj0Y8`vQb)p6GH}LWB`-L9nW!^ zuLIJbCv4CG*n^s>@hbdmyR<4gk7p!};I;*A2rG1nGWlsLVM0(1UgWAFwi zdnKp{u2<5i0bm#%;}_J}Ii*Ji7*z2L&Vs;nd(K7jZRrq1dX7PYLJmEgLl-do&I&59 z=-XZdH+Efo@kL^!2VIXx9$CgRsKW1XAv{R)i5La93UVW?&CjtcPEEAv2d8(ZZeuf`y4%R$QAbe6jF;^9{qUF-vBpSpau&Ttz%Me^LyBkESUmaZY3G$ z!%CIqB{-5&Dl&@5K06gei@#3pes^*9VXSQnvEd2Dbkix9ifO(xMH_j_(k4Cwb|Cr{ zIVKN5w2od~A;+Y|Fai#3bU6G3hjuws1eW340)nF?!Zl0+0l;Ogj*LykSV|m;>bP#E zaZ=)G#;_wgFL9g@TT-uNGM_VUafHwR&61CzX9eV7zuWzU!&zO9BB%iyXVV&n00m-$ zQlTs})k?=qHY6aLCV`3oaHfVi&T$AMimc)^IWEux+VJgUAPDXljjU)nw&pSUarbA3 z{cX2hdo9V)bG8HNrK2<0K2L1ide>b*#-)E=gnur*^dg0h<$QyAikt@$BQ#ObjCz{j z7!8c#3r`nIlK6Cpfuf>~&2BFJjR_jIc+h?Fn+R_Lex^XURuO>QBsl~eEUmov#6wp} zhV-E~KK*nWPDY={p}XYa`LEmi!Lk4!TFsJUf#)&_EH?m;gGE2X}TO5 z8wgZ1H@K;jMOqS2nFKC4r^)RTlW2*@RgR8Ws>j9v z4(fbqPL+CI{%;$bkOPK+PK!bgZg$&$P(F9OkbjJMdC_=bT7$q?G+@)?m$2lH9Su7=+UJm#SO+M0EZah zLB9j!c!vTa1I+VtGNd?}#z@xmYr{#AE=5JJp_-;*A8j%I3Q#Bx8%T{woYy3#t&Y5=nL$LUhi_;^VA2c#}{l?{mN-){Z5rMr$r<52x9wW z+Vlb(CU%0Chrw<57YkTMtvn7qRjRR&ad04zUA7!26_@xN2eC!NRVH%;1spnD`BD2D zB*)qXW25t<^YiTvN`Msw4hAbNTz{h0N=3xS$a8kyIJIfx@a3feD!|i5*p0pVtP=fx z^xZ?VJ9jX&Kv9e*n|K5P=wi(nW^6G8*w{p~2CdcKP90Cp|ImFh+$PL1YE{D@@ zN#7gi2HIr+i(56=3TuTec-A0Dov*f$9SiuF*C6p+s^{aZya_l!s&|jF#W0C`bb3fi z`99f_t>#Uh>)>Fkn@0^jj|n*-K7ZOxZt$k3#p9?%DciZwqh@7sH3Icv5qxjkoVLg_ z(VHH@&+&XEjquKhraV*-BgN&GOGCIV*olD284MhC&ikErV9Vry_IxjW4y6P$JBX{) zB}#ec9dHAvxC`LW=(zMECOme%TnbSgfYFt10msm=dFnPQZi3flGVSKZBO$#WXb8ze zw~3eCNN1mu*yvh}3uJ3k-sR?pbRyr7`R=Z7bKDSgC~{yN7^4-jr=GeI^EbebesFw2 za7-UxI2>dTUZbh3(fN10$(c&_An((K`1mk)wl(6|?sGmMvF{tqIX=O?k)bBXr3R7n zIJG*B90!6$=x3^U3!?7UxxwBAl0#8Lql4hkrP$W#3&vn`xPucK+L+-Ga7+aeo?zDI z{be{06#Mu*0V~eZYXWNq;Bo|z*C>h}c8Hq(M2O&6oIwrvrYm=1l?p$Sl&~JR9yIk) z&bhhIKom88A8h+>A%|>}#j^MV4*VDT7GlfJn4dNVaD+uY%I(*!DZG$q(XpIjMr0f# zNy|z_DFw<^ShTOF9bh`7CV^Rw>Dtn*#n5te?$8pGYaqGlxETo&KCBvM%y1xT2kcT+A^=)p{eA)j5sDw-UNlhs%y z&9V1(4omDuSe}hxBsVKh6?6pVBII^}y&VL{)}xiZxRfPYdG4#-N;u)fYNd7x*mwu! z6mmmBOhRm)sUp-0`6}v;l@e0kDx_F(ZSmTpUO{(mwjS z{8baLqd$w81-lT%lldh01MX(4LS79HM8D9Ui_d{cf=@lAHpBuQJ#ah*aD4E=^vT1A z4JMI#TVwZh41};7`vzszw5$f&qklisT3dcqz=3(i80EpxWzWy6)6=JzE|nrUy(AMxSx z)ts9u)04$q!&o_$7&icphi4I2U_$v#v-`hCdNdf#r;;%2pyi)I|7kPk>6lN(&q6&f zy}p<)(ssg~B**gCZDu%NY%PQ#Jt~j4cuq%}7|WtmKZRyvI8L{j>KC^w8xaz;qPjvh*LKUeAN@Rr?BK816753O)hKkTx z?=LBUBPPw}TI{RNzMzuPW+IU&`N2ncn+Oe;O36e<+9&vI#4aNvBh?VkS23I$z1!W^ z$?@59d#@!pKn}Hw8nZNtsceGflwC+@XjX{hp^PraBVYFEA>z4Wjn#?XLSsph7mA7T zpnNB*kAFRS1ilTy@$2WGH#$;4tu>9LwsI&;kZN+!ZeNcM1r8)R4s$nicT2JQwY!!6 zH(e;Z`OM=?)O^E8oqzZPrrOXgu*;~e^h)duZ2 zKPIj=w{1huy4}>d->r)C8bHGw(yi#bQBwkrF22jWj3UJ8dFIJgRYez8@Ea>D5&eqg z7r7GD8pv94xuQg%d_;`DDWlGlVeUoXE{BIRa?CK&(emitl>bSj;`M9fLVok;WdaUZ zt(a8r>>20F2b>&cyO-qB*BUlbn`TH$;S5s`+SRJoi=t*t$>YZt*Gc5BC^E_?CCkb}Kq>j~g{j8{Hh zezpc#R~HRuj84wY9azHfklC5v zLYgP=aC78_VB>$`aKISk;cde|7pA4Qf>v4d%f=Fxl$+0l4#YJvSY_N64g?Lzlnb~M ziWzqF{rBIG*n?_n7MC|;kwn(#s%PWhP~>VBeyh<;NWcMsSYqHmYilV!M<1-n&ylrc zv+U^P+YaremtTJQ5!x==JG;abJ}!nm`|39fwvFrQn-d?ta1F*(d{|w`h}rf~vzALF zDby_2s!;p=g3%ijS11EF-iC+kxcNp@o?U3x%4s=zJUHjwYA7ZCYt?H$3vM}-w{1zX>Y~wdDB8YpdUpRRJnGPCuwcu~z43M8bEK$ve z-P-1VKaav7$AKA=g9dQG>0l{#joeTdT)R8E6Go3iZ?Zq8t~riI%^0MIL{FIrwlWuN zLpR>UE456A9GTz14LAlf`wzZ5H|Z3)0*8NuM3uuLA>71ha8LIOyN0CH!IUe9A<~zb zT|97lQDmb|4;&=m=v=#nChPtCXTQ%tyvqCG_s!vEBxHy!Of4_3EVmxtSFpilJ5tLP z^YVMhKCK)8HWp`hA`Lq;gALgso9%^}rfYfBlIe48bKBq4oqcGVRTRe8H)j1y!X{%1 zew3w9>eLi$tE$&@Fhy=^oc^ z+EG(hQ-=M~*iEF`Cw!S+% zd$?Mwyo&7|xIBxhDv!6GNzr*ygMLH)2QDj>SD=AYwZEdA6&;dozVc|LOOu1_P^QBI zj*&ku9(nui?~WcjHli;hrcMp)?s=xCfA_$^(mzpOpXh#!D#%&HP%SIGA*t)$n7*9`3Ci3;>h758ekeZv31`UaLaQZwuikzRyOl0P;NW1C6 zg}0l}%$}Kr&R~W#vJlj{2Im9RG&a}xfw@RBu1n2OgYqNW*GlOi)>@F`SVe|IA8bq0 z!SiscYf*j9{v4fBMOuW8rLF<)X5yfsk=gs(cQoRI#^!BpH*7%F?8&^gY}xQg?Kb&3 zbYR`MW@EWKN-E*uYjON!ZJFO&L2X`VI>wcKCwX#!8&V5)j^yx>98K#VdI;;29)Bjo zL$m!^FUE1V`zg`!S?ciNGe`Hm_V{aC4{x1)5os`y?oo&Ld_ErDG#uOYw8;(n4X2aD zHB~~Bc7-x<5_R_pkb~f`lH(->j`33xs~kD<`97fnxPXgj;E?REr9cD_Mr)imC%T0k zY?}T@7q}#P^d5lYSU0A$Vc7JmO3%TqvL-orXxsTTc7o`d%Y|msa9A7%Z#pLDRBJvk zWCM0_peO#8O`9HU+m2H!+aAHpY|9W4W2tl^h%TkAJ7pm@jLtj21{0U-5SB_sGk%%= z6B|_VXL*9Rf!+Izw_4?}6{0VgNE`$>BIJe{tDu5pI;<3@G6^U%F@e|xoAB{%Ae}lp zn_a+PE0*Fpo!sTfeZ;;%ATpM2X=k~JaAPWsr;pEyw-?7DhU3~VHs@T`kTY;)MYVsB zt*U7TIZj&D8HzSE5FB1MQ7_rQxt>tsK$4>xXU;m5 z@L%hF=g`VL2Ma8)=@szcc`H11sm@i3x*Z1);K@vhGD`Z~{cL?T{xURv`st3hpf}(8 z^vu!YuNww@h6t5ixcKT&EQ%xwsYGr_EJSR8xmI$lr1+0VtvuX~9dA82->s)0)4?Q% zfMeiWfCIhG`R=Jh;Dr{U!7s-ZW&#?|>l8~-k>z-pM^1L_7H-H_?H=Gc`5GK+Y!S(k zLYlzz9PK|%rviC!RAslDp^7!f!C?xuRbN5d;Zqy@9(3LEWM*b&woQ#8H2`v?4~Wet zHrm7Kv49V!PXQd5eO6u3A0l>Mb2PaM$j&llC0zW%-C|fiyi5RA!RH1c&PU z<8Usi4&Q>*=0gAnvncYjal-Utig7qGuC?aG|Ck+>TRvwgKi}^jx=(oyuwz?;Tcbl~ zpeRd z5ZAG*H#Yn}T?bxtCe6W(UN-Xy10J!d_*6WmNiqYW{oZ!?r?&0QXX-bv)7&V?_*ucI#|hp_1JsHPk{pHA9&>Sx8ID9 zo5-N_*UAtP9*P?Z9Mmh9zLruEg^k@KY^)A&+-4IvD8zI=RDy#XTIb_QOn3#3#Rd%y ztK-mDQ;HlfXZ#h}>|&&C@vMvq^Z`@3~ zQPXH42OI~$QK$#YhVYpv`$?5o4^@U>Hui|;81$lBSn(6F0~~eeO$=WEu^6m(7iM^! z*88#Hm{Po#Bi0*^QBzu|F>P$V{q`O8*qJTZXu=SB_*`}6A5BU61mb!xy$oDP2}RrC z(7JML=w_oTMXJHEgbouNSdjhvw47Q$F7Qyyu*5dF$?K369Npb#N5~CI>Qm2x8xA|c zLAjckV2lTMYH$RDNHGRLZbm8{9nH%)jzV%ESfS;}f*V=SVin?(g$%i#jEp7YEwcCh z4kSifTN0_gh(g&d)z!3|;kaDKVL)(S4!q!SP2qMFk>-1hn+oax79638XSJ(=#&@=upTS3tVXPU@l{;;pEW7x3{kiR50N%fyD+ z?N_^v)sY?*>Gu6>p|f$+T^7RXN{Cau^1j-}YR`@Ev*8#LCww?$Ph=I=sik{wt~|ACmTi(8Xzinpc(WjZ%PQjp z$7u{$4txM?6oEqzC4PGR5WsP^`|R0EBVGMSgXrwW$O-8fB-k*_=WS=ABm4rES0qi) z{sK&S1bM+ZgdCPg4r+>ZYj7h`4;5)xANg6fF>-= zbA`lY1Io}zj#C2{PhU8V!>3P~pDMB(g&N0Q zE?pA00lT5FfvhO)2C<>-hTY_73UK+OT^-BHG+=xpolMOXHWthsEY|Z190*qCvZ95> zfFxTfQbV~7taYR#$XAv!fOZ&8GzPE%en#)iiX5EoV1nf+rUQB7VNr~OCnqqWN;D8aLXG?3HZ(SXAZA5P+4rnXksJ(Hjw#b& zMF&WNWtEE;E?)e#%fgK!&ru+RGpO5Op(wE-9Y1I{gc}MQ_RFbF2d9dv;O2}9zA&xP z(P27{eAAu;DLdUMXgw4oz6sXXBDTca|IB$CBkGMlN(5z2sQ$!YZh)OLRgN@ zaH{L-avbKfb?^uX|6nC0wWO>F>kL(UXcTlXzs}7*V0o1-3l2$Y5E*hq3`PA|gx$YR z`whcxZ0KOs0dg?Yu@tZD!aGm%$m!qY=vWMq@Plg6?3h>JfKat@j?|C^nmm7EMc8oK z3Ow^&IpYoJ;B?pI=;2_2*RL0$(;xO9>RApO%jh0F2p&dZqlb2b*tlxkpbVEn2O0`0 zn}9`%AZ8Ubt@Yfr#c>qHELhW=oXBR=*;I(zuw7=bs_ICI4w8fTkm)~9c7Ps~M#y}j?!SMS84+uQg8M3Ld~{B#QL)Y2 zc5)bnA4#SI@e1M#NXXIC!<&AzF~)|K)1RQe^uG; zrrl1eueHyLj@+`zQSl*j=cjWMPy(fysy~pPbxaOKWZEfi&~q3>hb`DP%)zdgO`QBm zL5Hx5&j7ao2d|7ybfm&tMYnb&bbbmZ129TV>L6YV!-7b`7sM}+9WZJ&h1h_s?!sxA zf%#8dn};J56m(cx_&enMzx@?ugCQ{;NNhrt$aoCZS8N`YB@y(Aar>cqs=69U&>;&r zIw@J!RY8g*Mg8TdAC+W@`7+t>ISeJ2lkI(Cy^t)>CR}&i|7waS?Cy05+nIoxE1&?Xc!|FH+-A4{}+__vlmy=SmJ4AhA@+Ua$ zm>!@fmeLJCgO6B!(qJt^KYW=UZ@gd4o#gQ0pa&g%C79PSFp4S6Ad zbg-SVjq{9;Sa!HDo&}C|_+f5B?dR#WlqS&(#9EI>$!pHn?0uDtEzPZbC z2e=JJDS{1lO@LH_!)ZS?M6z9zn^%aAF0ccXz~J;p%A&vj4-}GK4mb!LNp3JH%HlSz z0A5vcl(;ZE>X_SPsb?SkKESb)amV_>O!u+bjMj}nSL}|aF z0Ks?jF^S<+$wteWMysJ5?C)AP>Zu+I=}ohghQfWdA(UuD>$UuT0mo1!KEsXep^7^9N)fu$l2Une;3D|ISW=!77HUwS2C$))Qp{Q}zGt^p z!dNP_GaTz_I)IK7BLiJsM2Be|ASOHiw`ev7j(wqWqSBLw%&_tKFTc;8E0NlDa1ucZ z9T-}<<QV7J`C^c1nfR!3TAP_b-!Nx>L6Tgn z$^7=*J9p+yUAX>WSzG*w0J_@IG5gQa%r#N&NIa(Nfp%|>(VzD^l z70(V&2m^_i$A(JCFqKkbLsQaHnskSB99fWU|6h>64xr=n##gZ8iDQS!p#g0lS&V|* z2BMVdvyhSkY&x{3f(wLL`@~EEO*_7uNe-|>UP6}QK;1;cH##cJH!ZarH9HgrFhY zQPP5P$hh`G!WHj1Y#_2hooJ~7^4w_2a-fxkcyt{&na|H1m2hd2MeI0&_%HDz%!sn` zG(z^L3iUz}E!{o(Fv}Y!%XNH~?QrZMDWD@zLuLapim>sp4hC&u?*CFkX3`x}Z!lF}i840>m>!$iP2htrwjLUBR6U+Eq;+A{JYK#^#_;-U*hu8C@{Le88ehttwy7ipwQXB4hn%hCoLzy*7He2G&ptIs_)^f5TKfzqHR>t}ugSvm>m zm|r71wgC@WkE{wbD5N#EWi><%!-jVpS{xxyIaMP2OR zt{j5LHmkeDh&wFwu!e__3Eke^QN>0>jaML#q>-nsJUfIXe_v@*`3@Klzyo0m>G4{w z!VCt)yNG690UER#Tu%S!5wKC`25Av7;&{V)+bVj>l%k{;#=}NlIq+_pRGcX-Xw;e2 z(4Z4GYT%Hf%0^pp9MLW=|1J9}=d$MFF(C{ONrLNgdTj=m$g{A zOn8oCpn_`z;5;tR-KX~eCe-eU9|%t_-Fkp?87(eOkukVRSq=AjLAY+&akx+121Yb- zz3ZCrQPSf#`5~JjX8c?OG#aTPmE1@ftkF%Ygr>P6>u5xW*ipiR>*V(~u;=KLq*u06 zF*BiizsL+g11jytLl$mi8&h!{p>=L}c4**XNX7joJFkkB9!bf)b52q$h|R(p1)w_sts*KZ~jf zO`0_^1)7*K43&;euCT+?gL>wrNAS#aRUzP)Ll&xA8@KZ3KW{^g^5f0K+a$iv1XMJZ zQ58Y9dFka#z5JJuy#w26i^kmn+WyN5!hTFqRHhNOJHHKiEp?zDQA* zIWF|9==vC>+*9WFSv1Fsx>Dc}x|1!8RK~|FZ~-}+G_*hV84h%@V*=LIpQ_sm=rv7e z5f;g3gl24@>(CLuaRjV{$X?)}eID>YH|rXrZEUT6wik=q(?i77nG+{A$8oCTSKy^d z(+(p0Wig^1V!kPL6d^i`JY`qWjiz|=LjGLWp%yBY=kX-wI8JrEs#kmH5wCo&`71n; z!(n7>vC6?Ex5YpcwUgez%2k2sQr-wB_Whn}j0$B-GMen87;w>5?AmI}!u2E%39=)& zmFJT%kYXGzeVgHExlzCPt)s5+RDan!m)wS77>Ggy=Kfc%A~3=r3pWmmmQ>%P?8M$_ zp)r4!yA$tsGv!#PigC&?m&3-xR*IuXl|lEbK073N+SbY*LJ9rIH(M_8)M&_W&rwi7 zOf$rQ(&e@jQjBVN9Utm$S8uiAg^VcLJm8`q!zO3>>c zxV?LY_tnPr{}5zCh#zLy;#J25LHqIi3GuxTy_C}XaqEJU|26!nV=O>Bs6OKYPqg;GSJ0K3x{@y$Z{?5p)H=r7vLGQ-sfs+?AJ1{$CJVpV!x6m zv=5eET$uTMSHp<{m-y5k3}xGnUk45!fJ^)iG+cn%I>8n99@4P2K{FeX(j4k;Bb+cwWJ#Uo(#+qxxH`hBBC9;bPnzd&02#%i7R&AFue6TKdVn3*e( zFWa1SE+0tyh@@yJdAgtu)+%D>#WdXN+OC?{LD$(ed_Dj7Mnt2o zXMQ@v6yGYHtX^{Tv(%+pqr(riei}kr6Jqq-N*z~@D!yM@ar;MQt6_nBVm3`fH>T8jKmc&zd{}PD3_jr7mUL0P>87cVsSrhH z;Odzl(aJ}3-x=R<2r$b`?%{4+8x%uY#sivJ)Dr-APB#yksSlGW6 z4~?gkS}^1X+__eD&8~e0rJEhMm%amVyCJxc6to9aW4jfE3yj-i7#2T+? zBZ$TEJloRCSA~GTN#o|mTTn%3r%E@&FzWDw;=lw(n+`bf%jS-w&9N4EI@(`8#~k!v zn;BA1pIQ5BD* zQY&VOMzVM}Js%B2ugibZzLhd{T}|$1#j$(Z4q_#XTnlMKXhVqFPekVZk3LSeu z-=d;ZI5lf9(kslBSz}Q`hMNFW7*dvyTmVfcFh(CHUz+-Bn051}#J_}rl#1E5C^uj| zFpeYco0E`OYH0)|d`oL3CSofPgYk$#23wxmEH<)MqD`j%D1bqj;ZKWIlcC4!$u?$^ z@$x%p6o&2QK8d4nB*wMJl5-{eamsAnZYcMwrnZi*R<8D`h~`!_4|T(@+)JDp)Hm^x zI07`n0m09uVaH##rbxu0>q)#QLna|d^scB@ywAj;U0x7JkpCct9UbEm#B3^gCa7Oc z(Z@0*nr$-nin@y*S~+%qZ*<}sk+u;L$s|p%TCaVfU<(AEKUYf>1JyQe=N^zgMG zVjx102dG=7E&-+F9hpzfwFnSqj639vy2bDf&$d*QN|$=pJv!+w5(81Q{t?%r4I~D8 z(0CFFcNs#K0mk{pZL}IbPPDhb+J(+-^>F#Zl?e6qvEEUutJqs#Y>59LoB$> zNc}aE=viOI6q2#B1NmLi>n5X5weFd5d%|5!btTX<|A!u~u))ap_c3~f-X<;tP<%&Q z8$gAZVYpB1E^VMr3^gJ*CB1Yfj&lOfN|PpnCe6t$W(v($I+SVks6OE#>f07;L*Fkn z0&;0WlmaUC&5+s96WTtEzS@S2=!gL4ibhASWs2E~!#K7?x)TRUGuVG34)zKse{D$1 zdVS#G1xc@W{Q(LQSBgqtZzd;pW#-7T`i^W2cN+fHn)UURB-AUBl(%})Fb(h4br~Lq zd*P50EDq~wG!PWGHlQotLj?Eao_6%eu0>xP5`$8}v8h~DNrOFF@IiL$(smpta9Gsa zT|Q9n@9|8=c-~mLYz33YKmO%0H!+yL?l_5nU0>*?oXC*228VEu8AgJOgc+hToBY)< zbycCufQG^-5kJNu0+5A;n}WTy@7__6S2N~m44uGA+npT*?rcmTkFda2T|La z_73S&k0$*aT&@$3ROL_d{LjR3?1n@`d2D&+w{6R4a^;BH-Qv62U|?WnjeIvr=4G4G zqm{xpjvxp2r>1P48V|Q$Bn~Wa`=Z9N!j(ll-bCSEJ2a(a!;#_#4`DR-m)2jz5%9zyTX+32i=DkD6T1#DPZ~Lbki_4jvr%(;7!Y zAC~KmK+-FZF1U@`Xi$>_i&~Ixh2gD15R*hvG47`%)9GZz#4s*C72#T`?_jr=m8e(* zlO6o7YH9ZnOy9Ri9I4cV=Iag4alWEkkHU~;!23hX_;=eq7DVq z;ut!}V4fZBa*!;^)?5KAoYz%SGqSc?Ujb${I#Ctd*$905&@@(I7Zf60FV+^ z?gm4xIl?5gcL#97J|mJ$Ex-l*3?EJ`rbWxI`Lv^n)ruiC&s|+8qE_><(&?-?fQ*DS zNltlB)~>8H*b=6~pO|r@mYse_Y2#%!^|b5VV=<;No%s7Rt{eHvG#yB|36I<07$>i0 zP1~TkUNR9qXxVo+cK?SX%2r$j@jaSsr)8Ns@kBAwXKwE0J_cag>~sJx+3Ru#s`LK|#V;)F?nUY^SwNceN3*^O%W?B4koLf z6OZKwLpm`Uv!?qbK=t{Yo;5K6Vsety+n&ELU_qD=jDt9$PVE%5=OMQOJUOwpuRTSE z-NJ-;V;JB@bKg`o+8YY(_#PZ?|FEl~Ee0u^c1IUTkZ1xyL`5?{pcq&X*!z@+0+WvhbccEo`yF8Kd8aY{)ccDcjEtKl= zjXA8^;cw{AP8=49WAE`kqQ=qrgF@!-iQ|663ES*!E3!AACN+oNk9~#nf*zS=Ga)%9 zP)f>i2oiY10!@^XlFvv3+ffW}UFVSt4JG-g^o9t;0U(tsuxvJi!y}vdnZnFw0 z1&&uASbLj^!_I}~_BIxIQ)N}55pnQbKZzst)t(^kdNOMTB^OPi*x@}NaZ4yyDrja) zgT#+_!uLq~B?y%BDff&*Dts>aB9%;?q%`Y zvfL6|krF&#V17e!MBT8^B&(aB$1|-8_0oH^9n1V-$|MvP5yzESyvwgz)GP|;4`Ka6 zakvu*>`9JB-7WGU4z>Zr!P|IOE2qV|R74ydb45jbWw#BGf~LOIriz=Rf3qYbD3Up! z({JM^#WB$-J5Z*v@AKJPqRsTbs1oRv7)e2U@>~x-h@%-15A;gW9TNvpHRQ(qwe)N! zAY=vuK^%BLW+APV?ryV%!p*Y6;Vm0>tRaWH{B@?nf<&~08 zo^!~T;Tc}^%w7%}0a#!UE+u9~Kb3gsGbrPUiNh-wg^!J4;~1Q(;4mA)bHmm=A8nEo$aL(Sw%fXf?ii1ZOvCV8r~H}Ws6mDxRU~jHc{ZJUaJ_eT~E2czX|c z?nW5%sDCDoJzr*j>_@H$s>)4CM03bsm1E+dJZ>G6&6DizlI!}(v?OytE|J9`ju7#! zjE7U)-GKkyoqu`ml>Z8@qGVPt-7Z5Szm_EK!f+%m2)fk(*51V&a+IO{x!VkKE068{ zKpa;P2j%C9BJoCCqjBkpX^d5jxe@~g8Qb%Wqk6lHzaa2 zd9}PlNy%E`fh@@dElWOJOY$(`foV@U+cFGx#x{%#(B-(Rfxpd@Dx9&^{rE+YCK#?UU&;UUDAZ{g@ z9;I}~jQB#?nS${Kx_Mx3tP2x|!Fh3bpKCgIhy?woqmWbzD~?V|8YK8FaaNk}SQSe~ za+EXf&O)UBqR1!bc72rjR=$mPMv03v9+0i=&^dm@@^{l&F8wz`i<;3 zoQ84V}Lk8n@J=(q)sMVqpua-WbzUr!-e3l)A<*LN9w5#%vaq4TLvFPZ?g^x*qUnNdR z@0+Z3iWf;D#{!PT>1%+u=Ok~J?}J0uO*-YTEOrxMTPCfs^k5)EPT@7P)RYv>6@KBI z%aB#*oO7N&7tAVfEHR4`UEUE%^i=e)HgGscWhE37F&A)|oOU<7S>kw+qt@0Q-&{o= zFPYT74PmaqF*d9EOe_u|QN&Cv2c$113BNX(V!od@aQHojm=P&AT0%3&10C7K-N2E& zz^9A34Dil5%gv>-*ZOGT7UvRTTO2=`)_gHX@kHl49&+p2LG=zhTUh1qv?k;?-vh_U ziJ|kwNs(xr!*GyS(c-wIdvMqYx9?SFY#af{m)be37}hAeeL+U9E5p<)^7*&vST0h6 z9+Hem2AyG&q8t0lHK%MGZE+AB4y+1@#gr}`G{+LhS%Kr=vHY1s!^Pw}xF!!1a7jae zCR#0ycUO}diK`kgT*MdIoNEDxCl&{iv8+_JAJs#7NVN3p&gwE#%C=XLRtg+f2ieLf zWAuuJ@j)tEa9K@;Yp#fdHn15Wu4E}rq`(n*SNv@46H8h50XR4~^ChcSWwz>)o0e29 zuujXece#8ky!U7P;w8;#?vI%a^`14?k@vJX=&F!GNPAroBy70;{uLbQOalP^8{bUb zD_3P=@CfgaRqStY!_w`5_@M@e>0Y%K*A#FV0**NYD|F;`FL}YQVfd}!U?nWZOyhG* zLS_)bBgR)0Y~V<%{#U9-koG14ZC|FlKK)?CrPWp3?`(F(U4-^0Da2cxqoiW*wKmq> zg7Xp|DaYd#9E${hRG0}Z#aW1a4hfE-*nz`d%C2p@O=yhaOS&q>=$_|UZ1K*p@Dy=k zC;g49Vo;p4AEpEX1_=~XYrAPSPP8r5V$^TQ4sUQK&nCA<%)EbD@ z;=olm>=|=&Uos!`gS*V~ZYiBMFkjSyMhX2Ug`{JnVp;FYX~=0(f1sSVVJCAR<+voO zy(U%Ag@LXzDoZp1GMi`&Uy~Ia=LokqlA`U3tG;Zve7Y*AtIR z@tv}o9CVQBU`jMNUa(MEj^x0iu45JrI+@CL7s>QqUlG7fp5W*KjzdFRBw&&{8)JG; z8XTdxD!#ebGgilePt}d_L8!@bV40$pKaHj!U`?%(f_lJjcP#X(lVn!$+lijX{lt}Q zkwxE*!(|T+Zs^ha=bh`nL)m1rilt^m4aNv?h zL_E?hPT}OU6dbB;GTsHpv>$DAcrU}nUwiG6Um3xQkKCHYIS^km9F_Y~T;(A#Lk0O0 z9CHj#G#MvrCvIzA6;9j{f9uXDf)ihHAVttRRp3aSWh=0%3X47BedWWx^XobwEq_jR zkbcW`E9Y*GCXSDNt@1TrU6uM4hSlVAEQe0XNCtZ81`b1mqsa)P)m9TdK}2w{@xk#w zjdA3zoE0;cFW}H;*9({}aYHMi#jso-%j5c>76$5(eq^qD1BX4Oo~68iAa@!cFBDj@ zr@?U-aO{55_F%&Yui*o5kft5IgBz@xDy({#Gx(?CKX@X~+I)3KoV2Q0!&P!92{_P& zv}Dw1aIiBKcY@>6Se1RkMbNaZ=n_4=KaFvi=9hC-q#bn?gy)qt6Y2vJ5#I+#+;fNl z6~i+wF_8&-a4=c~QciN4;+R#aLCHbOexSf1t54ZcgNgAmS$$=m5gHp2-BjTCAUbMH zJ8s~Ymu)HWhk%zgbOXlHAhBCsLOLn8m4hT`+rZpg%yp{4!C?0YNmga{@-aj2VB*c- z%1N{2HM0UzSp}#R_dJ5bkGv_QL0;+fvt1K)waL5EEC>4*M+Jj3Zu^p~Vd?)48=Q7Uk#Q`a#P z^|cnY2so0Ds!=Sp&Of%|kl|a@hOlQ&R6p_A$|1v|?~0B*d2~D~_5?TiUW}uzIGf2@ zqQ>J&0UuVm<@7(kfaCl6iWUb`e1>!5C52WS$c*)fkOO}O2X#zbnO+luW7Wuey$1(# zDK(u1f+Il?i-720wcnWnt+lI?3OK&m5*kwzgsQUD;Lttw4LN9uUFo48pcG^#IQkC5 z{ZjcX!E|&OH6hP*RXp8TlaIMfN#y5GaF`^3%w-GG^tL!tv^NQkJ1tX)bDgY+7%c^k zu^X=oHQ)Ms%VNKG82^*;jY!Dv8p-;(b>`sq~XR^RM zll_d&Xa;1@1RNh&90nsQtOYK-{J~N@h=17>pP1QQZ3(vqgwF2m9!fBRk*%tvWRZMQ ztl%&u08BauNxTs6ZUdKX`V}|^p6hq?C0!LU9IcQoRCTjjsEteeJ~*7o<~7fS0*60m zoFN(m7rqCF>UHdW_PHq@?7F7`^KWT>!|2O5a8Ued9j(;w)2_|d?T4^8LHDkK7`ZbySfa%lwuU-2a`e262v&GxMfxbv7aKt6MDUue5&4|T; zW@I5>v8ed~p#$$Kr(ds4Oc@hts@cN^G+2$ponD|{DMcl;%-d$Ub z{iFs9C&w`IR*T~#V@|-)TLqFZlKD}~#{Gx^I>;*@%kW#B&ll#Z*j~na=~%r5$POHS z$ZCDl?FujAYE_-&=FvPDg^;f$YjbUr;AkCyWe1MU{Hb%VR59QNPGq3hFBl3OS211g z1jp^lYaSvmP7_)1aQUF(94)IBxm|H*&oYp8$J`z(2WiaqB4CuCCl71UBRy(x@SW17 zaPH11M(Pk5-h*v){S`Q}nH#&!%ceQg;5dFUSB3OPAv4CdT9LUV2XD5&3yvhC^%<0` zQHJX9EEUfC<-V=GD+vKO&`U{|6EiQR{SA%22Zv;0P4U(B-}n}AycZm0*H;}(#)qzg zGBr5Dc=-k(uoE@vQrojG*uEvBt-xWQ%XT<{4pExN#bE`9mVje+thBPZ2_Is)B|Wgx zQs6i?JPOV?KClx!CBMXGkoQd;z1jRj;gIJPly}mRPu&W08qy$lj7bA(oWJ_1P6pS!&P_{Ux_$91Og6OsQ1&m;IOCMAvnyeZDbH%mR^ca z%(`>dx|O?Lck(K6RSaVThn`-fCtB$wL8U3Sx|*Y`!;zGgd%m}r^dIa!W0`U7eT;)d zf=JZre{OMH3$?aUs)8<9i#uHKH;+ef7!K2C-!Hn@jAu77R&WS4)wTyHW077uh9+i9AC`hAUL{=C3z;n=ysS$zY7j@3i=vchE~i9{G+X!n%TO& z#j%Ew&L1kbHo!Idc6&|w`2fg!1xC_^2KDY%N>Wt(9u%h@Mp|lYGaHLd}%Y8 zFehAtP6T5RDqEROvF7H zn2nF%e9zk%uRS8nv1#$NIP8Ja;9vk;R?o)@9ERv%q=EG8OZfs04W66#nX9rFLBQH` z?OWsfO8n(;N<_*U;zD1hJ-R{6^G=KfZ%fBEcot<69CcXf#Rjym0**i`+Hxp?)AOn* z5=-4=du=g}&nylBM^O^#2%j(km6eXf)Z{St+TKe;#Lt$lu#62HEQp%VZlJ+oIH7N% z2DOSyf+K6U71_ER$!2-CH}c69(M{Ttr*m<5Uxa9GRA{j$jL-279Of-b;}raXhmzgX zG*7wicC$F%A_tTDj#sjq3OM?41BYZ&mAiX@a5)H2;C~c2U}GWwXJ&yfERk)ng3)8j zQx$-r6D75PLlV&^Sq2gA!NCe1S!XtQUQ%kcEx#VN>J#tk80SgLiB0Ktq0u+=p;p;M zQOri@eGLtrKlLy3#f3GkqRK8c+${}`g>Q*#0S7D&E)p@3trc)^3zb7W_?#}jqq_9+ zi-3L?gXe&uc z39Ho>T2d2NFtWzZz)Kw;k`NN*VmK$N*)97>EMh-Ad>~+0Z8LH6yw1g$O=Do2I?)}B z^9GJSdyz_Y)iqJ?Aa=w9E$3IKX3ykHRT~$~V0JB9J zfh7bbmQLwrL1~nbmQWCo5R{OmyStWJ8brFArE4W6MH&`ZSfo3bzWo2o{qF0Xr7P3qpvqCb2EM3C zr@>yWMZIP_?{IzIw-5iU5IleA#th%1l#N_@3X1F^`yEGQgjFT^V)u~x5;h?Hil^P6 z`xqdJn_ zCzDbBVvr_3rKJ4JyVWiQdiqKH)B+PgC_VJsf4vs5Qh}<%ARd)=$&uZce%^q$$>mue z1FEe)Ez1r^U|Drd4YL{glhJxKr^znk4KU5~DkkXyoy=1w;0 zSIgXY$!gt!tHTQQ6w#`P>xqp$D__JrP;UbPvFdEDDJ4jQABm(YuewA;xFc)Uv`?~c zPq)mp99l=*BiGKyl&&ht(`OE&1s*j$tJQEIUV97R+J6~1@QRnp}Z67}Z-)&A@g~y<(oS=HNHZuFTw{Gn>Fb6`fTN)t9#4+c& z;%&(nu~I@|9@W?{KB5MNgGxsHL~Z%({PN<%pY80CgS~5vSI>Q9a?>&1kutU~(xB%WCw2jKu zXqKYm%GJ$3GE7hYY4hGOQ2cyOJ&m$_i4M^B8E;@}TXa3`t=*7#3XGd_$`XmUF&#MR z<#FLOe9T6zQD#4=3)p8u4PQt=ss1_;cLt?im#9pU7E_AyKOyK z7ug&~mIU;Cr2q|%1*dvLmhD~+PLyl~eUb``59=MeO_Z~B)X{yN*=EwoUp$y^;)C5m z^e75Sxvmm!QtPaTk30CMb+)`Veu;+4o#J2?7eE+i_;DJc%=IT)>WbnCCo2fZI`9{0 zr8*X8`BfEWuQsa~t3xmBbRODm#)b<0T(~EF0d@s)@y66l*`_4nDoI+A>$x~5UTg`H z2Xd@Ci|lsLSKShk2eFi#%q2qGq*#^u2nOUPupBCJ2Ly9Iy{wuS`#JZr{B;njV~exf z5z!471==k<)<2s&Rzj)av+Q{u(p>wPn>)RKu}ZxtDMqKsf!5FGan>Wjfj^US@f0u^ zv^KQq{*lpD}{uck@dReyCi>EiyUKgazPFAmed3$RYL>%pG`Y zS!W_g4rLJ3g2I>1e$WnH&k6NM1g%=eDGd=xt;Bwrhy7gmoyl}ZIG*+@GRyw`p8{Mp(+Gw`J1}7&}uj~~V=m=1wE;-U| z`x-J_VJ6cWQ0}DhSA@8p7>K9fwv;p9P^ciZ{QYFx$MD64^26(q%&=Up&cwb1 zqQ(qL#siyiLxDwh?3T%8HLwVkGx1?W$kF@*5v(W$iIlIYZ#7Fr=pYB5vsS4>X~}>` z73amTgx`)56@KbO!S2qycV7KHUn1dOZs8(a$f&b^d<$D8$($ldD+$lEB8OGXXhON> z>S{7}1YYQWGr&2A%mJ6!K)3*Z(VZBdIs!ly4xgEWGJUGvmeT}ZzTE9To$Q(5SV z?>BHWQ`3lPC_#k6*c1|pM~mCIFwop#Dl8VsQ#ee(MN6qf%31oiwvAk?-aNF1X$UVJ z`~AXa`OxemjqzNbVh^;4pw!2j^D$=e@9AR*{uhtRVLRqiv0n~q1qEBpRH>Ax2acO3 z=hp)=yli?eb^C8z>;ghUI)W4q*Pf!U>L{XE(4_noXW7iZdfUyNq(6M+(0kIxinDS8 zxKum}v||omVc%ap9(tG8e2p%G*UI+QD}Ky~9tG#XCl@Bnocc-zOSwCP@M{SDaAK zn&$yh+4OBfzztu?l2pGjyQuMhnwoymg0(Qiz=y^bU5) z<_@O%dq*?Z1r`=I6wR#;UZXLT{FMgHwDgg4Qt&5mTbuH}bJv33T@ZwumdimtCWgBR zXlwYo2p118;hzNgJHd|>A;E%_&4dQqBlGOk-+9|nJqFs>@xwIN=`U7EE)7$_n{~Vo zR29#|n+D0H(z1-9#VrMfUDMlUEO_F~$o4#LrK?fY4>PD@nNydKa_L|!9p2>)eHr6* zd9Mb2ap%jgQ+}3?bH?T`DycpF+BC*Ti$A}#vm%jpo&~+IvO`&Ce{T4bFd~mAHyE?!dp88T}(c%QC7qMYaMyg^A(J+hVJ`mCR%1vM8N3l|t z8@;`MdO!0;FaFI4VO7E9RyVPMZ)R6s&Tx|h+u;TT2kiysIe#-}{d0|#=n|RRn9^Jm zDvOTQq^BwYm@meD@TrSI5HR0BekT%yC$){_B?sS=xk0mW92zpD*2rA9nMV@Up{N=u zmu_ys$b-cfVRpqW-e^<_{0XZCh32IFMUx1cjcDD%I0d_%CP+?o=2okFH9VQyW1EH5 z2`5Wi4qPGz-JvH2ur97Yn`VtS+>G%GeT|t#Ho$rPE@zU(IcjI<%i89`#v+;A0|oH) zb}GGsJ@O~Llbd9)8dyNRb}%S*jZ+94NOhqJ*42lLefnaZ?V>=f_4vOU9g>faSAqc% zw*czzsFuu`T3D@U)Ab+W+W7Ogs2LO6DoEeki-0bebc6DtIe#(>tdkbHX&lX-UK4PvHZWk50TD)vpzETdYfPfK^&dWLu(=oL zZpHO2*L(yP=?1yO#_6JAL4N~hWeq4tGc3Ont*zK&1FiO*qZ;ITEww`tOydp|;&<&M zr_WKG+kfURR1#_0!Tl>f43mxjx{tn_FBLR(hfhrl3Zex;XW&BOI2UGDXjEbABxImk5I;uDC+nS1@O z%AXUPa1Qk}t>5YTkzDf(Kcg-R+pk{a;eG)@C>;Ks?pWbut2Rk{cfsSjv5krPHQo@N zxd#aKUJ>V8H!%!o!EC`IGBgJ4k_{8z41kL!4%I2^+wNy5iBvT-$Mf)jo^Q7fMs%@< zZiu=+Y&E1YL&8#b%jP(Ms6~9F!00^t{j)<^@X?e}^Nn5+9Dd7Gs$dRCSk7=af!fv( z4#xNdyCrQ5UIT%Nrh{&Eqf($PMomO}Ma*QxO(rgxslx7W!(6iq}^dx%IqSo zm255^$z(aAM&6--AX`(Ws+e!bFCAVpGZau|8VR55T`;%qk(Gtv_YWJC!Q+50o}v z8|Z^0{MdpObZ)}diD(R*R0J>xKUH4TO-YjE1+pkQO|)*Q*qitAJ0=~ghin;-k=RN; zr*o*&i_G7X`Qg2=_9uF9e`PX$EzKbi#c^K<4-Aj2CeNo9;#L@jwuV!KgBTo@`OPCO zXAh7il7N+|SNpjSqo7RMZ-w2*5hMtU93;4zdpRWsJPZqWB@m`^!ZOfnmnq{>6uY?r z0`EU1+X{*?oC)770)XLUUnP`?O!!@;Sl3i~+bW(C$bxGkiHL%6hFPt8&D)*!QHZF(dg_sgc@bZTl@lwPIz6-nz`R{~*LXN7P_*=oa3I6G1{rH{D> zCW#q&L+@bDd`%b%5+kD@?Y6|nqb&m9(<=$M*4E1yPX$})b|42DB~0ZKObfl`Ijs4j z`T`F``_0B-+y{$a_M(0XQ$6K;)H^%)$mM-qWSHU)RmMNTs@+{Bb1M~Y%{ClPG^pnZ zIC>kCfph#F5I-W$|BpP&vo2A;F=IEYW(?kn*X(pH7T&iFG}&35Jm zxtgyoJq^)+XduY*URe=jJ2b85;jrl`9m(Er>hq+ZB>k+#cFuzzrmwp^P<9N-z^w62 zNchc*YnO}x;2l_PgPk!$&ua~OOO?fnFQmC7G4mF=3=Y9jBDt9jWjn=-n64r$ywzx7 zYAtO*4WsH2ZQtnRNr}@Z4*H0AZaA9?2v4Pgl^3+k^ip;F*VLclkS6JCbhGicw~o>B z8?5*SmroJYw(2mR2j~OCao0Iatw7MkrK|A=kMZescT;`llR~Cl5qwDa6BZ&BBr>TJ4=vtPjRwUu5Xkjcm5A$aS93X7_^9j8@ARDPB1Q@wA{ zVw}b2X-Hoseu|VXw&kHXT@iJTKwPbtd`FpS%bMa2YH{a59IPajK+oTU@0_j0%_K|n zciJr*Bz!i@*PU{u9`~*$j;Fd=E~TfGET(jjHNd`DR1pJorFQ5iPr|)Y*J|Na+|F(r z-MT$I_dLe5>&W&P*XD4}LjjveAY93kZl&scU@0F%z`sw}b2Csd!yPYVtkLbVMxDg_ zA8!=ydslW9g0CKotMAUx$_HCks}TX_OO>21BBs7)IOwuTMB=5y(iRm^o)x2}?bZig!hvXDR-%?=Hv))YGj$C@&A ziHKLem3JU8pTh;^QrpF+f0+*)ND%!>#zb7EKDb?3{KS8hIBTKR=wO#1frf@&Mx`Ox z^yCchiUIW{40Id9KVL*9(_tL{8f|NN51{Bg>Af=ke#=Vce?DXqw$+40#`;2H=s-sv z54iOD=tVB+;k>Km z9(DNajTgw$ZiLjQCb&$yratg8!lTG|qQrDr1Z~|Q2({C?)cP=t$BYvH-oH_{=ddoP zC{MW5)XTDRH1IrJa`}|ebY=}5n*C})ofJP0V}a@T!N0|kY9^4}b89$nla%&rAlFCh zN7{A9kq1WZz%@{p5|!h#zUPve3ACrbbKg$OGia%ZOJ?xO0CKnKe~Hu{hZ~py!MH#s{<5q;EEuIOWdp_q5hh^5_d(5%a zxDr~?54-;w!DyO=t72)I9J#ir5j6FRp?Hd4cH?qo;s-bHy99f)w+##VRVG9E zr0CXTdI~D*mGfZL*Zf12G{Qs(g8ceLI=-`8iG{MfUJNp~@Q##VYyFLH`32k{Ja6 literal 0 HcmV?d00001 diff --git a/miniprogram/images/user-active.png b/miniprogram/images/user-active.png new file mode 100644 index 0000000000000000000000000000000000000000..cf1b6d0d4cde717fe421ddad7054f975f6d49724 GIT binary patch literal 2135 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 ic>n+a21!IgR09C)M<~6wlW#Bp0000 \ No newline at end of file diff --git a/miniprogram/pages/ai-test/camera.wxss b/miniprogram/pages/ai-test/camera.wxss new file mode 100644 index 0000000..0834804 --- /dev/null +++ b/miniprogram/pages/ai-test/camera.wxss @@ -0,0 +1 @@ +/* placeholder */ \ No newline at end of file diff --git a/miniprogram/pages/ai-test/index.js b/miniprogram/pages/ai-test/index.js new file mode 100644 index 0000000..9555c82 --- /dev/null +++ b/miniprogram/pages/ai-test/index.js @@ -0,0 +1 @@ +Page({}) \ No newline at end of file diff --git a/miniprogram/pages/ai-test/index.json b/miniprogram/pages/ai-test/index.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/miniprogram/pages/ai-test/index.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/miniprogram/pages/ai-test/index.wxml b/miniprogram/pages/ai-test/index.wxml new file mode 100644 index 0000000..f966f72 --- /dev/null +++ b/miniprogram/pages/ai-test/index.wxml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/miniprogram/pages/ai-test/index.wxss b/miniprogram/pages/ai-test/index.wxss new file mode 100644 index 0000000..0834804 --- /dev/null +++ b/miniprogram/pages/ai-test/index.wxss @@ -0,0 +1 @@ +/* placeholder */ \ No newline at end of file diff --git a/miniprogram/pages/ai-test/result.js b/miniprogram/pages/ai-test/result.js new file mode 100644 index 0000000..9555c82 --- /dev/null +++ b/miniprogram/pages/ai-test/result.js @@ -0,0 +1 @@ +Page({}) \ No newline at end of file diff --git a/miniprogram/pages/ai-test/result.json b/miniprogram/pages/ai-test/result.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/miniprogram/pages/ai-test/result.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/miniprogram/pages/ai-test/result.wxml b/miniprogram/pages/ai-test/result.wxml new file mode 100644 index 0000000..f966f72 --- /dev/null +++ b/miniprogram/pages/ai-test/result.wxml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/miniprogram/pages/ai-test/result.wxss b/miniprogram/pages/ai-test/result.wxss new file mode 100644 index 0000000..0834804 --- /dev/null +++ b/miniprogram/pages/ai-test/result.wxss @@ -0,0 +1 @@ +/* placeholder */ \ No newline at end of file diff --git a/miniprogram/pages/enterprise/index.js b/miniprogram/pages/enterprise/index.js new file mode 100644 index 0000000..724b0f2 --- /dev/null +++ b/miniprogram/pages/enterprise/index.js @@ -0,0 +1,214 @@ +// pages/enterprise/index.js - 企业版首页 +const app = getApp() +const { request } = require('../../utils/request') + +Page({ + data: { + statusBarHeight: 0, + navbarHeight: 88, + siteTitle: '神仙团队AI性格测试', + startButtonEnterprise: '开始面部测试', + aiAnalysisText: '智能分析' + }, + + onLoad(options) { + // 企业版首页:固定 scope=enterprise + const app = getApp() + try { app.globalData.appScope = 'enterprise' } catch (e) {} + + // ── 解析入参(兼容两种来源:扫码 scene / 分享链接 options)── + const rawScene = (options && options.scene) ? decodeURIComponent(options.scene) : '' + // 解析 scene 中的 key=value 对(如 uid=1&eid=6) + const sceneParams = {} + if (rawScene) { + rawScene.split('&').forEach(pair => { + const [k, v] = pair.split('=') + if (k) sceneParams[k] = v || '' + }) + } + + // 合并所有来源:scene > options(分享链接) + const uid = parseInt(sceneParams.uid || options.uid || 0, 10) + let eid = parseInt(sceneParams.eid || options.eid || 0, 10) + + // 兼容旧格式 scene: e_企业ID + if (!eid && rawScene && rawScene.indexOf('e_') === 0) { + eid = parseInt(rawScene.slice(2), 10) || 0 + } + + console.log('[enterprise/onLoad] 解码参数 =>', { + rawScene, + sceneParams, + options, + uid, + eid + }) + + if (eid > 0) app.globalData.enterpriseIdFromScene = eid + + // 企业版分销绑定:uid > 0 且 eid > 0 时触发 + if (uid > 0 && eid > 0) { + app.globalData._pendingInviterId = uid + app.globalData._pendingInviterScope = 'enterprise' + app.globalData._pendingInviterEid = eid + } + // 获取状态栏高度和屏幕信息 + const systemInfo = wx.getSystemInfoSync() + const statusBarHeight = systemInfo.statusBarHeight || 0 + const screenWidth = systemInfo.screenWidth || 375 + const statusBarHeightRpx = (statusBarHeight * 750) / screenWidth + const navbarHeightRpx = statusBarHeightRpx + 88 + const gd = app.globalData + this.setData({ + statusBarHeight: statusBarHeightRpx, + navbarHeight: navbarHeightRpx, + siteTitle: gd.siteTitle || '神仙团队AI性格测试', + startButtonEnterprise: (gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试', + aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析' + }) + + // 未绑定企业的用户:若从邀请码扫码进入(有 enterpriseIdFromScene)也允许使用企业版 + const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} + const fromInvite = !!app.globalData.enterpriseIdFromScene + const redirectBack = () => { + wx.showToast({ title: '您尚未绑定任何企业,无法使用企业版', icon: 'none', duration: 2500 }) + setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 600) + } + // 如果不是通过企业邀请码进入:沿用原有 hasEnterprise 判定 + if (!fromInvite) { + if (userInfo.hasEnterprise === true) { + app.getRuntimeConfig().then((cfg) => { + if (cfg && cfg.siteTitle) { + app.globalData.siteTitle = cfg.siteTitle + this.setData({ siteTitle: cfg.siteTitle }) + } + }).catch(() => {}) + return + } + if (userInfo.hasEnterprise === false) { + redirectBack() + return + } + } + app.ensureLogin() + .then(() => app.getRuntimeConfig()) + .then((cfg) => { + if (cfg) { + if (cfg.siteTitle) { + app.globalData.siteTitle = cfg.siteTitle + this.setData({ siteTitle: cfg.siteTitle }) + } + if (cfg.textConfig) { + app.globalData.textConfig = cfg.textConfig + this.setData({ + startButtonEnterprise: cfg.textConfig.startButtonEnterprise || '开始面部测试', + aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' + }) + } + } + if ((cfg && cfg.pricingType) !== 'enterprise' && !app.globalData.enterpriseIdFromScene) { + redirectBack() + return + } + // 若通过企业邀请码进入(带 enterpriseIdFromScene),登录后绑定到 wechat_users.enterpriseId + const eid = app.globalData.enterpriseIdFromScene + if (eid) { + request({ + url: '/api/enterprise/bind', + method: 'POST', + data: { enterpriseId: eid }, + success(res) { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const data = res.data.data || {} + const merged = { ...(app.globalData.userInfo || {}), ...data } + app.globalData.userInfo = merged + wx.setStorageSync('userInfo', merged) + } + } + }) + } + }) + .catch(() => { + wx.showToast({ title: '请先登录', icon: 'none' }) + setTimeout(() => wx.switchTab({ url: '/pages/index/index' }), 600) + }) + }, + + onShow() { + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 0 }) + } + try { getApp().globalData.appScope = 'enterprise' } catch (e) {} + const gd = getApp().globalData + this.setData({ + siteTitle: gd.siteTitle || '神仙团队AI性格测试', + startButtonEnterprise: (gd.textConfig && gd.textConfig.startButtonEnterprise) || '开始面部测试', + aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析' + }) + }, + + // 切换到个人版 + switchToPersonal() { + wx.switchTab({ + url: '/pages/index/index' + }) + }, + + // 开始AI面部测试(先校验是否已上传简历,再跳转相机) + startAITest() { + const eid = (app.globalData && app.globalData.enterpriseIdFromScene) || (app.globalData && app.globalData.userInfo && app.globalData.userInfo.enterpriseId) || (wx.getStorageSync('userInfo') || {}).enterpriseId || null + const query = eid ? `?enterpriseId=${eid}&pageSize=1` : '?pageSize=1' + request({ + url: '/api/enterprise/resume-uploads' + query, + method: 'GET', + needAuth: true, + success: (res) => { + const list = (res.data && res.data.code === 200 && res.data.data && res.data.data.list) ? res.data.data.list : [] + if (!list.length) { + wx.showModal({ + title: '提示', + content: '需要先上传简历后再开始面部测试,请到「我的」-「我的简历」中上传', + showCancel: true, + confirmText: '去上传', + success: (r) => { + if (r.confirm) { + wx.navigateTo({ url: '/pages/enterprise/resume-history' }) + } + } + }) + return + } + wx.switchTab({ url: '/pages/index/camera' }) + }, + fail: () => { + wx.showModal({ + title: '提示', + content: '需要先上传简历后再开始面部测试,请到「我的」-「我的简历」中上传', + showCancel: true, + confirmText: '去上传', + success: (r) => { + if (r.confirm) { + wx.navigateTo({ url: '/pages/enterprise/resume-history' }) + } + } + }) + } + }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 (企业版) - 团队分析与优化', + path: getSharePath('/pages/enterprise/index') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 (企业版) - 团队分析与优化', + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/enterprise/index.json b/miniprogram/pages/enterprise/index.json new file mode 100644 index 0000000..c0684f0 --- /dev/null +++ b/miniprogram/pages/enterprise/index.json @@ -0,0 +1,7 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "custom-tab-bar": "/custom-tab-bar/index" + }, + "enablePullDownRefresh": false +} diff --git a/miniprogram/pages/enterprise/index.wxml b/miniprogram/pages/enterprise/index.wxml new file mode 100644 index 0000000..033a726 --- /dev/null +++ b/miniprogram/pages/enterprise/index.wxml @@ -0,0 +1,59 @@ + + + + + + + 👤 + 个人版 + + {{siteTitle || '神仙团队AI性格测试'}} + + + + + + + + + + + + + + 面相分析 + 骨相分析 + 性格测评 + + + + + 测试流程 + + + 1 + STEP1 + 拍摄照片 + + + + 2 + STEP2 + {{aiAnalysisText || '智能分析'}} + + + + 3 + STEP3 + 生成报告 + + + + + + {{startButtonEnterprise || '开始面部测试'}} + + + + + diff --git a/miniprogram/pages/enterprise/index.wxss b/miniprogram/pages/enterprise/index.wxss new file mode 100644 index 0000000..1617328 --- /dev/null +++ b/miniprogram/pages/enterprise/index.wxss @@ -0,0 +1,283 @@ +/* pages/enterprise/index.wxss - 企业版首页样式(与个人版相同,仅背景不同) */ +.container { + min-height: 100vh; + width: 100vw; + overflow-x: hidden; + overflow-y: auto; + background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%); + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; + padding-bottom: calc(100rpx + env(safe-area-inset-bottom) + 40rpx); +} + +/* 背景装饰圆形 */ +.bg-decoration { + position: absolute; + width: 600rpx; + height: 600rpx; + border-radius: 50%; + pointer-events: none; + z-index: 0; +} + +.bg-top-right { + top: -200rpx; + right: -200rpx; + background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%); +} + +.bg-bottom-left { + /* bottom: -200rpx; + left: -200rpx; */ + bottom: 0; + left: 0; + background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%); +} + +/* 自定义导航栏 */ +.custom-navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + background: #ffffff; + z-index: 10000; + width: 100%; +} + +.navbar-content { + height: 88rpx; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 40rpx; + position: relative; + background: #ffffff; + min-height: 88rpx; +} + +.navbar-title { + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: 36rpx; + font-weight: 700; + color: #333; + text-align: center; + flex: 1; +} + +.navbar-placeholder { + width: 140rpx; + flex-shrink: 0; +} + +.switch-personal-btn { + display: flex; + align-items: center; + gap: 8rpx; + padding: 12rpx 20rpx; + background: rgba(99, 102, 241, 0.1); + border-radius: 30rpx; + border: 1rpx solid rgba(99, 102, 241, 0.2); + flex-shrink: 0; + z-index: 10; +} + +.personal-icon { + font-size: 28rpx; +} + +.personal-text { + font-size: 24rpx; + color: #6366f1; + font-weight: 600; +} + +.top-image-section { + width: 100%; + padding: 15rpx 40rpx 20rpx; + flex-shrink: 0; + box-sizing: border-box; + position: relative; + z-index: 1; + margin-top: 0; +} + +.image-container { + position: relative; + width: 100%; +} + +.image-wrapper { + position: relative; + width: 100%; + padding-top: 100%; + border-radius: 50%; + overflow: hidden; + background: #ffffff; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12); +} + +.main-image { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 85%; + height: 85%; + display: block; +} + +.float-tag { + position: absolute; + padding: 12rpx 24rpx; + border-radius: 30rpx; + font-size: 24rpx; + font-weight: 600; + color: #e63946; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2); + white-space: nowrap; + z-index: 10; +} + +.tag-1 { + top: 15%; + right: 10rpx; +} + +.tag-2 { + bottom: 25%; + left: 0; +} + +.tag-3 { + bottom: 25%; + right: 0; +} + +.process-section { + padding: 15rpx 40rpx 20rpx; + flex-shrink: 0; + position: relative; + z-index: 1; +} + +.section-title { + font-size: 32rpx; + font-weight: 700; + color: #e63946; + text-align: center; + margin-bottom: 25rpx; +} + +.process-steps { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10rpx; +} + +.step-item { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; +} + +.step-circle { + width: 70rpx; + height: 70rpx; + border-radius: 50%; + background: #b8b9bc; + display: flex; + align-items: center; + justify-content: center; + font-size: 36rpx; + font-weight: 700; + color: #fff; + margin-bottom: 12rpx; +} + +.step-circle.active { + background: #e63946; +} + +.step-label { + font-size: 20rpx; + color: #666; + margin-bottom: 4rpx; +} + +.step-text { + font-size: 24rpx; + color: #333; + font-weight: 600; +} + +.step-line { + width: 50rpx; + height: 4rpx; + background: #e0e0e0; + margin: 0 6rpx 50rpx; +} + +.start-button { + /* 顶部与中间按钮的间距:上 20,下 10 */ + margin: 20rpx 40rpx 0; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + border-radius: 50rpx; + padding: 24rpx; + text-align: center; + box-shadow: 0 8rpx 30rpx rgba(99, 102, 241, 0.3); + position: relative; + z-index: 1; +} + +.button-text { + font-size: 30rpx; + color: #fff; + font-weight: 700; +} + +.upload-button { + margin: 10rpx 40rpx 0; + border: 2rpx solid #8b5cf6; + border-radius: 50rpx; + padding: 20rpx; + text-align: center; + background: #fff; + position: relative; + z-index: 1; +} + +.upload-button-primary { + margin: 20rpx 40rpx 0; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + border: none; +} + +.upload-button-primary .upload-text { + color: #fff; +} + +.upload-text { + font-size: 26rpx; + color: #8b5cf6; + font-weight: 600; +} + +/* 确保底部导航显示 */ +custom-tab-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 9999; +} diff --git a/miniprogram/pages/enterprise/resume-history.js b/miniprogram/pages/enterprise/resume-history.js new file mode 100644 index 0000000..cfdc18c --- /dev/null +++ b/miniprogram/pages/enterprise/resume-history.js @@ -0,0 +1,209 @@ +// pages/enterprise/resume-history.js - 简历上传记录(历史) +const app = getApp() +const { request } = require('../../utils/request') + +Page({ + data: { + list: [], + loading: true, + empty: false + }, + + onLoad() { + this.loadList() + }, + + onShow() { + if (this.data.list.length > 0 || !this.data.loading) { + this.loadList() + } + }, + + loadList() { + this.setData({ loading: true }) + const eid = (app.globalData && app.globalData.enterpriseIdFromScene) || (app.globalData && app.globalData.userInfo && app.globalData.userInfo.enterpriseId) || (wx.getStorageSync('userInfo') || {}).enterpriseId || null + const query = eid ? `?enterpriseId=${eid}&pageSize=100` : '?pageSize=100' + request({ + url: '/api/enterprise/resume-uploads' + query, + method: 'GET', + needAuth: true, + success: (res) => { + this.setData({ loading: false }) + if (res.statusCode === 200 && res.data && res.data.code === 200 && Array.isArray(res.data.data && res.data.data.list)) { + const list = (res.data.data.list || []).map((item) => ({ + id: item.id, + url: item.url || '', + fileName: item.fileName || '', + uploadedAt: item.uploadedAt || 0, + uploadedAtStr: item.uploadedAtStr || this._formatTime(item.uploadedAt), + isDefault: !!item.isDefault + })) + this.setData({ list, empty: list.length === 0 }) + } else { + this.setData({ list: [], empty: true }) + } + }, + fail: () => this.setData({ loading: false, list: [], empty: true }) + }) + }, + + uploadResume() { + const eid = (app.globalData && app.globalData.enterpriseIdFromScene) || (app.globalData && app.globalData.userInfo && app.globalData.userInfo.enterpriseId) || (wx.getStorageSync('userInfo') || {}).enterpriseId || null + wx.chooseMessageFile({ + count: 1, + type: 'file', + extension: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx'], + success: (fileRes) => { + const files = fileRes.tempFiles || [] + if (!files.length) { + wx.showToast({ title: '请选择文件', icon: 'none' }) + return + } + const file = files[0] + const filePath = file.path || file.tempFilePath + const fileName = file.name || '简历文件' + const apiBase = (app.globalData && app.globalData.apiBase) ? app.globalData.apiBase.replace(/\/$/, '') : '' + const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' + const uploadUrl = apiBase + '/api/upload/file' + wx.showLoading({ title: '上传中...', mask: true }) + wx.uploadFile({ + url: uploadUrl, + filePath, + name: 'file', + header: token ? { Authorization: 'Bearer ' + token } : {}, + success: (res) => { + wx.hideLoading() + try { + const data = JSON.parse(res.data) + if (data.code === 200 && data.data && data.data.url) { + const url = data.data.url + request({ + url: '/api/enterprise/resume-uploads', + method: 'POST', + needAuth: true, + data: { url, fileName, enterpriseId: eid }, + success: (r) => { + if (r.statusCode === 200 && r.data && r.data.code === 200) { + wx.showToast({ title: '已上传并记录', icon: 'success' }) + this.loadList() + } else { + wx.showToast({ title: r.data && r.data.message || '记录失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '记录失败,请重试', icon: 'none' }) + }) + } else { + wx.showToast({ title: data.message || '上传失败', icon: 'none' }) + } + } catch (e) { + wx.showToast({ title: '解析上传结果失败', icon: 'none' }) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '上传失败,请稍后重试', icon: 'none' }) + } + }) + }, + fail: () => wx.showToast({ title: '已取消选择', icon: 'none' }) + }) + }, + + _formatTime(ts) { + if (!ts) return '' + const d = new Date(ts) + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + const h = String(d.getHours()).padStart(2, '0') + const min = String(d.getMinutes()).padStart(2, '0') + return `${y}-${m}-${day} ${h}:${min}` + }, + + previewResume(e) { + const url = e.currentTarget.dataset.url + const fileName = e.currentTarget.dataset.fileName || '' + if (!url) return + const fullUrl = url.startsWith('http') ? url : ((app.globalData && app.globalData.apiBase) || '').replace(/\/$/, '') + url + const ext = (fileName.split('.').pop() || '').toLowerCase() + const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'] + const isImage = imageExts.indexOf(ext) !== -1 + + wx.downloadFile({ + url: fullUrl, + success: (res) => { + if (res.statusCode !== 200 || !res.tempFilePath) { + wx.showToast({ title: '打开失败', icon: 'none' }) + return + } + const filePath = res.tempFilePath + if (isImage) { + wx.previewImage({ current: filePath, urls: [filePath] }) + } else { + const docTypes = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'] + const fileType = docTypes.indexOf(ext) !== -1 ? ext : 'pdf' + wx.openDocument({ + filePath, + fileType, + showMenu: true, + fail: () => wx.showToast({ title: '该格式暂不支持预览', icon: 'none' }) + }) + } + }, + fail: () => wx.showToast({ title: '打开失败', icon: 'none' }) + }) + }, + + setDefault(e) { + const id = e.currentTarget.dataset.id + if (!id) return + request({ + url: '/api/enterprise/resume-uploads/set-default', + method: 'POST', + needAuth: true, + data: { id }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + wx.showToast({ title: '已设为默认简历', icon: 'success' }) + this.loadList() + } else { + wx.showToast({ title: res.data && res.data.message || '设置失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '设置失败', icon: 'none' }) + }) + }, + + deleteResume(e) { + const id = e.currentTarget.dataset.id + if (!id) return + wx.showModal({ + title: '确认删除', + content: '删除后不可恢复,确定删除该简历记录?', + confirmText: '删除', + confirmColor: '#f43f5e', + success: (res) => { + if (!res.confirm) return + request({ + url: '/api/enterprise/resume-uploads/delete', + method: 'POST', + needAuth: true, + data: { id }, + success: (r) => { + if (r.statusCode === 200 && r.data && r.data.code === 200) { + wx.showToast({ title: '已删除', icon: 'success' }) + this.loadList() + } else { + wx.showToast({ title: r.data && r.data.message || '删除失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '删除失败', icon: 'none' }) + }) + } + }) + }, + + goBack() { + wx.navigateBack({ fail: () => wx.switchTab({ url: '/pages/profile/index' }) }) + } +}) diff --git a/miniprogram/pages/enterprise/resume-history.json b/miniprogram/pages/enterprise/resume-history.json new file mode 100644 index 0000000..9481ceb --- /dev/null +++ b/miniprogram/pages/enterprise/resume-history.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "我的简历", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/enterprise/resume-history.wxml b/miniprogram/pages/enterprise/resume-history.wxml new file mode 100644 index 0000000..dff647e --- /dev/null +++ b/miniprogram/pages/enterprise/resume-history.wxml @@ -0,0 +1,66 @@ + + + + + + + + + + 仅展示当前绑定企业的简历上传记录 + + + + + + + + 📄 + + + {{item.fileName}} + {{item.uploadedAtStr}} + + + 默认 + 设为默认 + + + + + + 删除 + + + + 预览查看 + + + + + + + + + + 📁 + + 暂无上传记录 + 点击下方按钮上传简历,或返回企业版首页上传 + 上传简历 + 返回首页 + + + + + 加载中... + + + + + + + 上传简历 + + + diff --git a/miniprogram/pages/enterprise/resume-history.wxss b/miniprogram/pages/enterprise/resume-history.wxss new file mode 100644 index 0000000..81b1085 --- /dev/null +++ b/miniprogram/pages/enterprise/resume-history.wxss @@ -0,0 +1,342 @@ +/* pages/enterprise/resume-history.wxss */ +.page { + min-height: 100vh; + background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%); + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +/* 背景装饰圆形 */ +.bg-decoration { + position: absolute; + width: 600rpx; + height: 600rpx; + border-radius: 50%; + pointer-events: none; + z-index: 0; +} + +.bg-top-right { + top: -200rpx; + right: -200rpx; + background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%); +} + +.bg-bottom-left { + bottom: 0; + left: 0; + background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%); +} + +.container { + position: relative; + z-index: 1; + flex: 1; + padding: 24rpx 32rpx 60rpx; + padding-bottom: calc(60rpx + 120rpx); +} + +/* 底部悬浮上传按钮(有数据时显示) */ +.float-upload-wrap { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 100; + padding: 24rpx 32rpx; + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + background: linear-gradient(to top, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.95) 70%, transparent); +} + +.float-upload-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 12rpx; + height: 96rpx; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: #fff; + font-size: 30rpx; + font-weight: 700; + border-radius: 48rpx; + box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.35); +} + +.float-upload-btn:active { + opacity: 0.92; +} + +.float-upload-icon { + font-size: 36rpx; + line-height: 1; +} + +.float-upload-text { + letter-spacing: 1rpx; +} + +.tip-section { + margin-bottom: 24rpx; + padding: 0 8rpx; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 16rpx; +} + +.tip-text { + font-size: 24rpx; + color: #999; +} + +.upload-entry { + display: flex; + align-items: center; + gap: 8rpx; + padding: 14rpx 28rpx; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: #fff; + font-size: 26rpx; + font-weight: 600; + border-radius: 32rpx; + box-shadow: 0 6rpx 16rpx rgba(99, 102, 241, 0.25); +} + +.upload-entry:active { + opacity: 0.9; + transform: scale(0.98); +} + +.upload-entry-icon { + font-size: 28rpx; + line-height: 1; +} + +/* 简历卡片 */ +.resume-card { + background: #fff; + border-radius: 24rpx; + padding: 28rpx; + margin-bottom: 24rpx; + box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04); + border: 1rpx solid rgba(255, 255, 255, 0.8); +} + +.file-info { + display: flex; + align-items: flex-start; + gap: 20rpx; + margin-bottom: 28rpx; + position: relative; + overflow: hidden; + min-height: 0; +} + +.file-icon-wrap { + width: 80rpx; + height: 80rpx; + background: #f0f2ff; + border-radius: 16rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.file-icon { + font-size: 40rpx; +} + +.file-meta { + width: 80%; + min-width: 0; + overflow: hidden; + padding-right: 16rpx; +} + +.top-right-actions { + flex-shrink: 0; + align-self: flex-start; +} + +.set-default-text { + font-size: 24rpx; + color: #6366f1; + padding: 4rpx 0; +} + +.set-default-text:active { + opacity: 0.8; +} + +.name { + display: block; + font-size: 30rpx; + font-weight: 600; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: 6rpx; + width: 85%; +} + +.time { + display: block; + font-size: 24rpx; + color: #999; +} + +.default-badge { + position: absolute; + top: 0; + right: 0; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: #fff; + font-size: 20rpx; + font-weight: 600; + padding: 4rpx 16rpx; + border-radius: 10rpx; +} + +/* 卡片操作区 */ +.card-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding-top: 24rpx; + border-top: 1rpx solid #f5f6fa; +} + +.secondary-actions { + display: flex; + align-items: center; + gap: 20rpx; +} + +.action-btn { + display: flex; + align-items: center; + justify-content: center; + height: 64rpx; + border-radius: 32rpx; + font-size: 24rpx; + font-weight: 600; + transition: all 0.2s ease; +} + +.action-btn:active { + opacity: 0.7; + transform: scale(0.98); +} + +.text-btn { + padding: 0 16rpx; + background: transparent; +} + +.action-icon { + margin-right: 6rpx; + font-size: 26rpx; +} + +.delete-btn { + color: #f43f5e; + font-size: 26rpx; +} + + +.primary-btn { + padding: 0 32rpx; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: #fff; + box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.2); +} + +/* 空状态 */ +.empty-state { + padding: 120rpx 60rpx; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.empty-icon-wrap { + width: 160rpx; + height: 160rpx; + background: #fff; + border-radius: 80rpx; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 40rpx; + box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05); +} + +.empty-icon { + font-size: 80rpx; +} + +.empty-text { + font-size: 34rpx; + font-weight: 700; + color: #333; + margin-bottom: 16rpx; +} + +.empty-tip { + font-size: 26rpx; + color: #999; + line-height: 1.6; + margin-bottom: 48rpx; +} + +.go-upload-btn { + padding: 24rpx 64rpx; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); + color: #fff; + font-size: 28rpx; + font-weight: 700; + border-radius: 50rpx; + box-shadow: 0 10rpx 20rpx rgba(99, 102, 241, 0.2); + margin-bottom: 20rpx; +} + +.back-home-btn { + padding: 20rpx 48rpx; + font-size: 26rpx; + color: #6366f1; + font-weight: 600; + border: 2rpx solid #6366f1; + border-radius: 40rpx; +} + +/* 加载中 */ +.loading-wrap { + padding: 100rpx 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 20rpx; +} + +.loading-spinner { + width: 40rpx; + height: 40rpx; + border: 4rpx solid rgba(99, 102, 241, 0.1); + border-top-color: #6366f1; + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-text { + font-size: 26rpx; + color: #999; +} diff --git a/miniprogram/pages/history/index.js b/miniprogram/pages/history/index.js new file mode 100644 index 0000000..fb8e497 --- /dev/null +++ b/miniprogram/pages/history/index.js @@ -0,0 +1,161 @@ +// pages/history/index.js - 测试历史记录(一次拉取全部,时间行右侧展示企业名) +const app = getApp() + +Page({ + data: { + activeTab: 'all', + tabName: '', + list: [], + total: 0, + loading: false, + isEnterprise: false + }, + + _checkIsEnterprise() { + const gd = app.globalData || {} + const storedUser = wx.getStorageSync('userInfo') || null + const scope = gd.appScope || 'personal' + const enterpriseId = gd.enterpriseIdFromScene + || (gd.userInfo && gd.userInfo.enterpriseId) + || (storedUser && storedUser.enterpriseId) + || null + return scope === 'enterprise' || !!enterpriseId + }, + + onLoad() { + this.setData({ isEnterprise: this._checkIsEnterprise() }) + this.loadAll() + }, + + onShow() { + this.setData({ isEnterprise: this._checkIsEnterprise() }) + this.loadAll() + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 1 }) + } + }, + + // 一次拉取全部历史(pageSize=500) + loadAll() { + if (this.data.loading) return + this.setData({ loading: true, list: [] }) + + const token = app.globalData.token || wx.getStorageSync('token') + const apiBase = app.globalData.apiBase + const { activeTab } = this.data + const typeParam = activeTab === 'all' ? '' : `&type=${activeTab}` + + if (!token || !apiBase) { + this.setData({ loading: false }) + this.loadFromStorage() + return + } + + wx.request({ + url: `${apiBase}/api/test/history?page=1&pageSize=500${typeParam}&scope=all`, + method: 'GET', + header: { Authorization: `Bearer ${token}` }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.data) { + const payload = res.data.data + const rawList = Array.isArray(payload) ? payload : (payload.list || []) + const total = Array.isArray(payload) ? rawList.length : (payload.total || 0) + const formatted = this.formatList(rawList) + this.setData({ list: formatted, total, loading: false }) + } else { + this.setData({ loading: false }) + this.loadFromStorage() + } + }, + fail: () => { + this.setData({ loading: false }) + this.loadFromStorage() + } + }) + }, + + formatList(rawList) { + const typeNames = { mbti: 'MBTI性格测试', disc: 'DISC性格测试', pdp: 'PDP行为偏好测试', ai: '面相分析', resume: '简历综合分析' } + const emojis = { mbti: '🧠', disc: '📊', pdp: '🦁', ai: '👁️', resume: '📋' } + + return rawList.map((item, idx) => { + if (item.typeName) { + return { ...item, enterpriseName: item.enterpriseName || '' } + } + const testType = (item.testType || item.type || 'mbti').toLowerCase() + const ts = item.createdAt || item.testTime || item.timestamp + return { + ...item, + type: testType, + key: testType + '_' + (item.id || idx), + emoji: emojis[testType] || '📋', + typeName: typeNames[testType] || '测试', + testTime: ts ? this.formatTime(typeof ts === 'number' ? ts * 1000 : ts) : '', + enterpriseName: item.enterpriseName || '' + } + }) + }, + + // 本地缓存回退 + loadFromStorage() { + const mbtiResult = wx.getStorageSync('mbtiResult') + const discResult = wx.getStorageSync('discResult') + const pdpResult = wx.getStorageSync('pdpResult') + const aiResult = wx.getStorageSync('aiResult') + const list = [] + if (mbtiResult) list.push({ type: 'mbti', key: 'mbti', emoji: '🧠', typeName: 'MBTI性格测试', resultText: mbtiResult.mbtiType || '未知', testTime: this.formatTime(mbtiResult.timestamp), data: mbtiResult }) + if (pdpResult) list.push({ type: 'pdp', key: 'pdp', emoji: pdpResult.description?.emoji || '🦁', typeName: 'PDP行为偏好测试', resultText: pdpResult.description?.type || '未知', testTime: this.formatTime(pdpResult.timestamp || pdpResult.completedAt), data: pdpResult }) + if (discResult) list.push({ type: 'disc', key: 'disc', emoji: '📊', typeName: 'DISC性格测试', resultText: (discResult.dominantType || '未知') + '型', testTime: this.formatTime(discResult.timestamp || discResult.completedAt), data: discResult }) + if (aiResult) list.push({ type: 'ai', key: 'ai', emoji: '👁️', typeName: '面相分析', resultText: aiResult.mbti || '未知', testTime: this.formatTime(aiResult.timestamp || aiResult.completedAt), data: aiResult }) + this.setData({ list, total: list.length, loading: false }) + }, + + changeTab(e) { + const tab = e.currentTarget.dataset.tab + const names = { all: '', mbti: 'MBTI', pdp: 'PDP', disc: 'DISC', ai: '面相', resume: '简历' } + this.setData({ activeTab: tab, tabName: names[tab] || '' }) + this.loadAll() + }, + + formatTime(timestamp) { + if (!timestamp) return '未知时间' + const date = new Date(timestamp) + const y = date.getFullYear() + const m = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + const h = String(date.getHours()).padStart(2, '0') + const min = String(date.getMinutes()).padStart(2, '0') + return `${y}-${m}-${d} ${h}:${min}` + }, + + viewDetail(e) { + const type = e.currentTarget.dataset.type + const id = e.currentTarget.dataset.id + const routes = { + mbti: '/pages/result/mbti', + disc: '/pages/result/disc', + pdp: '/pages/result/pdp', + ai: '/pages/index/result', + resume: '/pages/result/resume' + } + const base = routes[type] + if (!base) return + if ((type === 'ai' || type === 'resume') && !id) return + const query = id ? `?id=${id}&type=${type}` : '' + wx.navigateTo({ url: query ? base + query : base }) + }, + + goToTest() { + const routes = { + mbti: '/pages/test/mbti', + disc: '/pages/test/disc', + pdp: '/pages/test/pdp', + ai: '/pages/index/camera', + resume: '/pages/enterprise/index' + } + const url = routes[this.data.activeTab] + if (url) wx.navigateTo({ url }) + else wx.switchTab({ url: '/pages/index/index' }) + }, + +}) diff --git a/miniprogram/pages/history/index.json b/miniprogram/pages/history/index.json new file mode 100644 index 0000000..30f7c36 --- /dev/null +++ b/miniprogram/pages/history/index.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "测试记录", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/history/index.wxml b/miniprogram/pages/history/index.wxml new file mode 100644 index 0000000..552ba42 --- /dev/null +++ b/miniprogram/pages/history/index.wxml @@ -0,0 +1,74 @@ + + + + + + + + 全部 + + + MBTI + + + PDP + + + DISC + + + 面相 + + + 简历 + + + + + + + + 📋 + 暂无{{activeTab === 'all' ? '' : tabName}}历史记录 + + 开始测试 + + + + + + + + + {{item.emoji}} + + + + {{item.typeName}} + + {{item.resultText}} + + + + {{item.testTime}} + {{item.enterpriseName}} + + + + + + + + 加载中... + + + + 共 {{total}} 条 + + + + + + 加载中... + + diff --git a/miniprogram/pages/history/index.wxss b/miniprogram/pages/history/index.wxss new file mode 100644 index 0000000..a4f44e7 --- /dev/null +++ b/miniprogram/pages/history/index.wxss @@ -0,0 +1,208 @@ +/* pages/history/index.wxss - 测试历史记录(按旧版模板重构) */ +.container { + min-height: 100vh; + background-color: #f5f5f5; +} + +/* Tabs */ +.tabs-container { + background-color: #fff; + border-bottom: 1rpx solid #f0f0f0; + padding: 20rpx 0; +} + +.tabs-scroll { + white-space: nowrap; +} + +.tabs { + display: flex; + padding: 0 32rpx; +} + +.tab-item { + padding: 12rpx 32rpx; + margin-right: 16rpx; + border-radius: 40rpx; + background-color: #f5f5f5; + flex-shrink: 0; +} + +.tab-item.active { + background-color: #e63946; +} + +.tab-text { + font-size: 28rpx; + color: #666; +} + +.tab-item.active .tab-text { + color: #fff; + font-weight: 500; +} + +/* 空状态 */ +.empty-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding-top: 200rpx; +} + +.empty-icon { + font-size: 100rpx; + margin-bottom: 24rpx; +} + +.empty-text { + font-size: 28rpx; + color: #999; + margin-bottom: 32rpx; +} + +.primary-button { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + border-radius: 40rpx; + padding: 20rpx 60rpx; + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.button-text { + font-size: 28rpx; + color: #fff; + font-weight: 500; +} + +/* 测试记录列表 */ +.test-list { + display: flex; + flex-direction: column; + gap: 20rpx; + padding: 20rpx; +} + +.test-card { + background-color: #fff; + border-radius: 16rpx; + padding: 24rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05); +} + +.test-card:active { + transform: scale(0.98); + opacity: 0.9; +} + +.card-content { + display: flex; + align-items: center; +} + +.icon-circle { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 20rpx; + flex-shrink: 0; +} + +.icon-mbti { background-color: #f3e8ff; } +.icon-pdp { background-color: #dbeafe; } +.icon-disc { background-color: #dcfce7; } +.icon-ai { background-color: #fce7f3; } + +.icon-emoji { + font-size: 32rpx; +} + +.test-details { + flex: 1; + min-width: 0; +} + +.test-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8rpx; + gap: 12rpx; +} + +.test-title { + font-size: 30rpx; + font-weight: 500; + color: #333; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.result-badge { + padding: 4rpx 16rpx; + border-radius: 20rpx; + flex-shrink: 0; +} + +.badge-mbti { background-color: #f3e8ff; } +.badge-mbti .badge-text { color: #7c3aed; } + +.badge-pdp { background-color: #dbeafe; } +.badge-pdp .badge-text { color: #2563eb; } + +.badge-disc { background-color: #dcfce7; } +.badge-disc .badge-text { color: #16a34a; } + +.badge-ai { background-color: #fce7f3; } +.badge-ai .badge-text { color: #db2777; } + +.badge-text { + font-size: 22rpx; + font-weight: 500; + white-space: nowrap; +} + +.test-time-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16rpx; + margin-top: 4rpx; +} + +.test-time { + font-size: 24rpx; + color: #999; + flex-shrink: 0; +} + +.test-enterprise { + font-size: 24rpx; + color: #666; + flex-shrink: 0; + max-width: 280rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.load-more-row { + display: flex; + justify-content: center; + align-items: center; + padding: 32rpx 0 20rpx; +} + +.load-more-text { + font-size: 26rpx; + color: #7c3aed; +} + +.no-more { + color: #bbb; +} diff --git a/miniprogram/pages/index/camera.js b/miniprogram/pages/index/camera.js new file mode 100644 index 0000000..5420d82 --- /dev/null +++ b/miniprogram/pages/index/camera.js @@ -0,0 +1,196 @@ +// pages/index/camera.js - 面相分析拍照页,拍完后上传到服务器再跳转结果页 +const app = getApp() +const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js') + +Page({ + data: { + photos: [], + photoIndex: 0, + guideTexts: ['请正对镜头', '请向左转45°', '请向右转45°'], + guideText: '请正对镜头', + uploading: false, + needPhoneAuth: false, + aiAnalysisText: '智能分析' + }, + + onLoad() { + this.cameraContext = wx.createCameraContext() + const tc = app.globalData.textConfig + if (tc && tc.aiAnalysisText) { + this.setData({ aiAnalysisText: tc.aiAnalysisText }) + } else { + app.getRuntimeConfig().then((cfg) => { + if (cfg && cfg.textConfig) { + app.globalData.textConfig = cfg.textConfig + this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' }) + } + }).catch(() => {}) + } + }, + + onShow() { + if (!ensureProfileCompleteAndRedirect()) return + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 1 }) + } + this.setData({ needPhoneAuth: !hasPhone() }) + const tc = app.globalData.textConfig + if (tc && tc.aiAnalysisText) { + this.setData({ aiAnalysisText: tc.aiAnalysisText }) + } + }, + + // 本页就地授权手机号 + onGetPhoneNumber(e) { + const { code, errMsg } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + if (!code && !hasPhone()) { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + return + } + if (!code && hasPhone()) { + // 已有手机号,无需重复请求 + this.setData({ needPhoneAuth: false }) + return + } + bindPhoneByCode(code).then(() => { + this.setData({ needPhoneAuth: false }) + }).catch(() => { + // 失败时保持 needPhoneAuth 为 true,等待用户重新授权 + this.setData({ needPhoneAuth: !hasPhone() }) + }) + }, + + // 拍照 + takePhoto() { + if (this.data.photos.length >= 3) { + wx.showToast({ title: '已拍满3张', icon: 'none' }) + return + } + + this.cameraContext.takePhoto({ + quality: 'high', + success: (res) => { + const photos = [...this.data.photos, res.tempImagePath] + const photoIndex = photos.length + const guideText = this.data.guideTexts[photoIndex] || '拍摄完成' + + this.setData({ + photos, + photoIndex, + guideText + }) + + if (photos.length === 3) { + wx.showToast({ title: '拍摄完成', icon: 'success' }) + } + }, + fail: (err) => { + wx.showToast({ title: '拍照失败', icon: 'none' }) + console.error('拍照失败:', err) + } + }) + }, + + // 重新拍摄全部照片 + retakeAll() { + wx.showModal({ + title: '重新拍摄', + content: '确定要重新拍摄所有照片吗?', + confirmText: '确定', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + this.setData({ + photos: [], + photoIndex: 0, + guideText: '请正对镜头' + }) + wx.showToast({ title: '已清空,请重新拍摄', icon: 'success' }) + } + } + }) + }, + + // 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页 + completeCapture() { + if (!ensureProfileCompleteAndRedirect()) return + if (!hasPhone()) { + wx.showToast({ title: '请先授权手机号', icon: 'none' }) + this.setData({ needPhoneAuth: true }) + return + } + const photos = this.data.photos + if (!photos || photos.length === 0) { + wx.showToast({ title: '请先拍摄照片', icon: 'none' }) + return + } + this.setData({ uploading: true }) + wx.showLoading({ title: '上传中...', mask: true }) + const apiBase = (app.globalData && app.globalData.apiBase) ? app.globalData.apiBase.replace(/\/$/, '') : '' + const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' + const uploadUrl = apiBase + '/api/upload/image' + + const uploadOne = (path) => { + return new Promise((resolve, reject) => { + wx.uploadFile({ + url: uploadUrl, + filePath: path, + name: 'file', + header: token ? { Authorization: 'Bearer ' + token } : {}, + success: (res) => { + try { + const data = JSON.parse(res.data) + if (data.code === 200 && data.data && data.data.url) { + resolve(data.data.url) + } else { + reject(new Error(data.message || '上传失败')) + } + } catch (e) { + reject(new Error('解析上传结果失败')) + } + }, + fail: (err) => reject(err) + }) + }) + } + + Promise.all(photos.map(uploadOne)) + .then((urls) => { + wx.hideLoading() + wx.setStorageSync('aiPhotos', urls) + this.setData({ uploading: false }) + wx.navigateTo({ url: '/pages/index/result' }) + }) + .catch((err) => { + wx.hideLoading() + this.setData({ uploading: false }) + wx.showToast({ title: err.message || '上传失败', icon: 'none' }) + }) + }, + + // 从相册选择:跳转到上传页(三角度上传),不改变当前 scope/企业上下文 + goToUpload() { + wx.navigateTo({ + url: '/pages/index/upload' + }) + }, + + // 相机错误 + onCameraError(e) { + console.error('相机错误:', e) + wx.showModal({ + title: '相机权限', + content: '请允许使用相机权限以进行AI人脸分析', + confirmText: '去设置', + success: (res) => { + if (res.confirm) { + wx.openSetting() + } + } + }) + } +}) diff --git a/miniprogram/pages/index/camera.json b/miniprogram/pages/index/camera.json new file mode 100644 index 0000000..988934d --- /dev/null +++ b/miniprogram/pages/index/camera.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "拍照", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/index/camera.wxml b/miniprogram/pages/index/camera.wxml new file mode 100644 index 0000000..259ce7c --- /dev/null +++ b/miniprogram/pages/index/camera.wxml @@ -0,0 +1,69 @@ + + + + + 步骤 {{photoIndex + 1}}/3 + {{photos.length}}/3 张照片已完成 + + + + + + + + + {{photoIndex + 1}} + + {{guideText}} + 请保持自然表情,确保光线充足 + + + + + + + + + + + + + + {{index === 0 ? '正面' : (index === 1 ? '左侧' : '右侧')}} + + + + + + + + 拍摄{{guideText}}照片 + + + 从相册选择 + + + + + + 重新拍摄 + + + 立即{{aiAnalysisText || '智能分析'}} + + + + + + + 为保障服务与联系,请先授权手机号。 + + + diff --git a/miniprogram/pages/index/camera.wxss b/miniprogram/pages/index/camera.wxss new file mode 100644 index 0000000..60525fb --- /dev/null +++ b/miniprogram/pages/index/camera.wxss @@ -0,0 +1,312 @@ +/* pages/index/camera.wxss - 一屏内展示,为底部自定义 tabBar(含中间浮起圆钮)预留空间 */ +.container { + width: 100%; + height: 100vh; + height: 100dvh; + box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: #fff; + /* 预留底部空间,避免与自定义 tabBar(约 100rpx 高 + 中间圆钮上浮约 56rpx)重叠 */ + padding-bottom: calc(160rpx + env(safe-area-inset-bottom)); +} + +.progress-section { + flex-shrink: 0; + margin: 16rpx 24rpx 0; + padding: 20rpx 32rpx 16rpx; + border-radius: 10rpx; + background: linear-gradient(to right, #fff5f5, #ffe5e8); +} + +.progress-info { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10rpx; +} + +.step-text { + font-size: 26rpx; + font-weight: 600; + color: #e63946; +} + +.photo-count { + font-size: 22rpx; + color: #999; +} + +.progress-bars { + display: flex; + gap: 12rpx; + margin-bottom: 12rpx; +} + +.progress-bar { + flex: 1; + height: 6rpx; + border-radius: 6rpx; +} + +.progress-bar.completed { + background-color: #52c41a; +} + +.progress-bar.pending { + background-color: #e5e5e5; +} + +.instruction-card { + background: rgba(255, 255, 255, 0.9); + border-radius: 12rpx; + padding: 14rpx 18rpx; + border: 1rpx solid rgba(230, 57, 70, 0.1); +} + +.instruction-content { + display: flex; + align-items: center; + gap: 12rpx; +} + +.step-number { + width: 40rpx; + height: 40rpx; + flex-shrink: 0; + border-radius: 50%; + background-color: #e63946; + color: #fff; + font-size: 24rpx; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; +} + +.instruction-text { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2rpx; +} + +.angle-text { + font-size: 26rpx; + font-weight: 600; + color: #c41d2a; +} + +.tip-text { + font-size: 22rpx; + color: #666; + line-height: 1.3; +} + +.camera-container { + flex: 1; + min-height: 0; + padding: 16rpx 24rpx; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; +} + +.camera-preview { + width: 100%; + max-width: 100%; + max-height: 100%; + aspect-ratio: 1; + border-radius: 32rpx; + overflow: hidden; + border: 6rpx solid #e5e5e5; + background-color: #000; + position: relative; +} + +.camera { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.photos-preview { + width: 100%; + height: 100%; + min-height: 0; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 12rpx; +} + +.photo-item { + position: relative; + flex: 1; + min-width: 0; + height: 100%; + max-height: 100%; + border-radius: 16rpx; + overflow: hidden; + border: 4rpx solid #e5e5e5; +} + +.photo-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.photo-label { + position: absolute; + top: 8rpx; + left: 8rpx; + background: rgba(230, 57, 70, 0.9); + color: #fff; + font-size: 20rpx; + font-weight: 600; + padding: 4rpx 12rpx; + border-radius: 12rpx; +} + +.button-container { + flex-shrink: 0; + padding: 16rpx 24rpx 0; + display: flex; + justify-content: center; + align-items: center; +} + +.capture-actions { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 20rpx; +} + +.capture-actions .capture-button { + width: 100%; +} + +.album-button { + width: 100%; + padding: 20rpx; + border-radius: 50rpx; + text-align: center; + background: #fff; + border: 2rpx solid #e63946; + box-sizing: border-box; +} + +.album-button:active { + background: #fff5f5; +} + +.album-button-text { + font-size: 28rpx; + font-weight: 600; + color: #e63946; + letter-spacing: 1rpx; +} + +.phone-auth-section { + padding: 24rpx 24rpx 0; + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.phone-auth-tip { + font-size: 26rpx; + color: #4b5563; +} + +.phone-auth-btn { + height: 80rpx; + line-height: 80rpx; + border-radius: 40rpx; + background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); + color: #ffffff; + font-size: 28rpx; +} + +.phone-auth-btn::after { + border: none; +} + +.capture-button { + width: 100%; + max-width: 100%; + padding: 22rpx; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + border-radius: 50rpx; + text-align: center; + box-sizing: border-box; + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.button-text { + font-size: 30rpx; + font-weight: 700; + color: #fff; + letter-spacing: 1rpx; +} + +.action-buttons { + display: flex; + gap: 20rpx; + width: 100%; +} + +.action-button { + flex: 1; + padding: 28rpx 24rpx; + border-radius: 50rpx; + text-align: center; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; +} + +.action-button.secondary { + background: #fff; + border: 2rpx solid #e63946; +} + +.action-button.secondary:active { + background: #fff5f5; + transform: scale(0.98); +} + +.action-button.primary { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.4); +} + +.action-button.primary:active { + transform: scale(0.98); + box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.3); +} + +.action-button-text { + font-size: 28rpx; + font-weight: 700; + letter-spacing: 1rpx; +} + +.action-button.secondary .action-button-text { + color: #e63946; +} + +.action-button.primary .action-button-text { + color: #fff; +} diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js new file mode 100644 index 0000000..c89d965 --- /dev/null +++ b/miniprogram/pages/index/index.js @@ -0,0 +1,177 @@ +// pages/index/index.js +const app = getApp() +const { request } = require('../../utils/request.js') + +Page({ + data: { + statusBarHeight: 0, + navbarHeight: 88, + showEnterpriseEntry: false, + siteTitle: '神仙团队AI性格测试', + startButtonText: '开始面相测试', + aiAnalysisText: '智能分析' + }, + + onLoad(options) { + // 获取状态栏高度和屏幕信息 + const systemInfo = wx.getSystemInfoSync() + const statusBarHeight = systemInfo.statusBarHeight || 0 + const screenWidth = systemInfo.screenWidth || 375 + // 将状态栏高度从px转换为rpx + const statusBarHeightRpx = (statusBarHeight * 750) / screenWidth + const navbarHeightRpx = statusBarHeightRpx + 88// 状态栏 + 导航栏内容 + + const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {} + const gd = getApp().globalData + this.setData({ + statusBarHeight: statusBarHeightRpx, + navbarHeight: navbarHeightRpx, + showEnterpriseEntry: userInfo.hasEnterprise === true, + siteTitle: gd.siteTitle || '神仙团队AI性格测试', + startButtonText: (gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试', + aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析' + }) + // 预加载站点名称与文案配置 + app.getRuntimeConfig().then((cfg) => { + if (cfg) { + if (cfg.siteTitle) { + getApp().globalData.siteTitle = cfg.siteTitle + this.setData({ siteTitle: cfg.siteTitle }) + } + if (cfg.textConfig) { + getApp().globalData.textConfig = cfg.textConfig + this.setData({ + startButtonText: cfg.textConfig.startButtonText || '开始面相测试', + aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' + }) + } + } + }).catch(() => {}) + // ── 解析入参(兼容扫码 scene / 分享链接 options)── + const rawScene = (options && options.scene) ? decodeURIComponent(options.scene) : '' + const sceneParams = {} + if (rawScene) { + rawScene.split('&').forEach(pair => { + const [k, v] = pair.split('=') + if (k) sceneParams[k] = v || '' + }) + } + + const uid = parseInt(sceneParams.uid || (options && options.uid) || 0, 10) + const eid = parseInt(sceneParams.eid || (options && options.eid) || 0, 10) + + console.log('[index/onLoad] 解码参数 =>', { + rawScene, + sceneParams, + options, + uid, + eid + }) + + // 携带 eid:跳转企业版首页 + if (eid > 0) { + getApp().globalData.enterpriseIdFromScene = eid + wx.navigateTo({ url: '/pages/enterprise/index?uid=' + uid + '&eid=' + eid }) + return + } + + // 个人版分销绑定 + if (uid > 0) { + app.globalData._pendingInviterId = uid + app.globalData._pendingInviterScope = 'personal' + } + }, + + onShow() { + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 0 }) + } + // 个人版首页:固定 scope=personal,并清除企业来源上下文 + try { + getApp().globalData.appScope = 'personal' + getApp().globalData.enterpriseIdFromScene = null + } catch (e) {} + const gd = getApp().globalData + this.setData({ + siteTitle: gd.siteTitle || '神仙团队AI性格测试', + startButtonText: (gd.textConfig && gd.textConfig.startButtonText) || '开始面相测试', + aiAnalysisText: (gd.textConfig && gd.textConfig.aiAnalysisText) || '智能分析' + }) + const userInfo = getApp().globalData.userInfo || wx.getStorageSync('userInfo') || {} + this.setData({ showEnterpriseEntry: userInfo.hasEnterprise === true }) + // 有 token 时拉取最新用户信息(含 hasEnterprise),绑定企业后无需重新登录即可展示企业版入口 + const token = getApp().globalData.token || wx.getStorageSync('token') + if (token) { + request({ + url: '/api/auth/me', + method: 'GET', + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) { + const user = res.data.data + getApp().globalData.userInfo = user + wx.setStorageSync('userInfo', user) + this.setData({ showEnterpriseEntry: user.hasEnterprise === true }) + } + } + }) + } + }, + + // 开始拍照(个人版入口:强制本次链路为个人定价) + startCamera() { + try { getApp().globalData.appScope = 'personal' } catch (e) {} + wx.switchTab({ + url: '/pages/index/camera' + }) + }, + + // 上传照片(个人版入口:强制本次链路为个人定价) + uploadPhoto() { + try { getApp().globalData.appScope = 'personal' } catch (e) {} + // 这里仅负责跳转到全新的「拍摄或上传照片」页面,具体拍摄/上传逻辑在新页面实现 + wx.navigateTo({ + url: '/pages/index/upload' + }) + }, + + // 切换到企业版(仅已绑定企业的用户可进入,优先用登录返回的 hasEnterprise,避免多请求) + switchToEnterprise() { + const app = getApp() + const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} + if (userInfo.hasEnterprise === true) { + wx.navigateTo({ url: '/pages/enterprise/index' }) + return + } + if (userInfo.hasEnterprise === false) { + wx.showToast({ title: '您尚未绑定任何企业,无法使用企业版', icon: 'none', duration: 2500 }) + return + } + // 缓存里没有 hasEnterprise 时:临时用 enterprise scope 获取配置判定 + try { app.globalData.appScope = 'enterprise' } catch (e) {} + app.ensureLogin().then(() => app.getRuntimeConfig()).then((cfg) => { + if ((cfg && cfg.pricingType) === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.showToast({ title: '您尚未绑定任何企业,无法使用企业版', icon: 'none', duration: 2500 }) + } + }).catch(() => { + wx.showToast({ title: '无法获取配置,请稍后重试', icon: 'none' }) + }) + }, + + onShareAppMessage() { + const { getSharePathByScope } = require('../../utils/share') + return { + title: 'AI人脸性格分析 - 看看你的面相透露了什么性格密码', + path: getSharePathByScope('/pages/index/index') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: 'AI人脸性格分析 - 看看你的面相透露了什么性格密码', + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json new file mode 100644 index 0000000..3d61cfc --- /dev/null +++ b/miniprogram/pages/index/index.json @@ -0,0 +1,6 @@ +{ + "navigationStyle": "custom", + "usingComponents": { + "custom-tab-bar": "/custom-tab-bar/index" + } +} diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml new file mode 100644 index 0000000..58dfd1f --- /dev/null +++ b/miniprogram/pages/index/index.wxml @@ -0,0 +1,61 @@ + + + + + + + 🏢 + 企业版 + + {{siteTitle || '神仙团队AI性格测试'}} + + + + + + + + + + + + + + 面相分析 + 骨相分析 + 性格测评 + + + + + 测试流程 + + + 1 + STEP1 + 拍摄照片 + + + + 2 + STEP2 + {{aiAnalysisText || '智能分析'}} + + + + 3 + STEP3 + 生成报告 + + + + + + {{startButtonText || '开始面相测试'}} + + + 人工智能生成 + + + + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss new file mode 100644 index 0000000..76bb48e --- /dev/null +++ b/miniprogram/pages/index/index.wxss @@ -0,0 +1,278 @@ +/* pages/index/index.wxss - 个人版首页,样式与企业版一致 */ +.container { + min-height: 100vh; + width: 100vw; + overflow-x: hidden; + overflow-y: auto; + background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 50%, #ffffff 100%); + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; + padding-bottom: calc(100rpx + env(safe-area-inset-bottom) + 40rpx); +} + +/* 背景装饰圆形(与企业版一致) */ +.bg-decoration { + position: absolute; + width: 600rpx; + height: 600rpx; + border-radius: 50%; + pointer-events: none; + z-index: 0; +} + +.bg-top-right { + top: -200rpx; + right: -200rpx; + background: radial-gradient(circle, rgba(139, 92, 246, 0.08) 0%, transparent 70%); +} + +.bg-bottom-left { + bottom: 0; + left: 0; + background: radial-gradient(circle, rgba(99, 102, 241, 0.06) 0%, transparent 70%); +} + +/* 自定义导航栏 */ +.custom-navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + background: #ffffff; + z-index: 10000; + width: 100%; +} + +.navbar-content { + height: 88rpx; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 40rpx; + position: relative; + background: #ffffff; + min-height: 88rpx; +} + +.navbar-title { + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: 36rpx; + font-weight: 700; + color: #e63946; + text-align: center; + flex: 1; +} + +.navbar-placeholder { + width: 140rpx; + flex-shrink: 0; +} + +/* 与企业版切换按钮同款样式(红主色) */ +.switch-enterprise-btn { + display: flex; + align-items: center; + gap: 8rpx; + padding: 12rpx 20rpx; + background: rgba(230, 57, 70, 0.1); + border-radius: 30rpx; + border: 1rpx solid rgba(230, 57, 70, 0.2); + flex-shrink: 0; + z-index: 10; +} + +.enterprise-icon { + font-size: 28rpx; +} + +.enterprise-text { + font-size: 24rpx; + color: #e63946; + font-weight: 600; +} + +.top-image-section { + width: 100%; + padding: 15rpx 40rpx 20rpx; + flex-shrink: 0; + box-sizing: border-box; + position: relative; + z-index: 1; + margin-top: 0; +} + +.image-container { + position: relative; + width: 100%; +} + +.image-wrapper { + position: relative; + width: 100%; + padding-top: 100%; + border-radius: 50%; + overflow: hidden; + background: #ffffff; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12); +} + +.main-image { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 85%; + height: 85%; + display: block; +} + +.float-tag { + position: absolute; + padding: 12rpx 24rpx; + border-radius: 30rpx; + font-size: 24rpx; + font-weight: 600; + color: #e63946; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4rpx 16rpx rgba(230, 57, 70, 0.2); + white-space: nowrap; + z-index: 10; +} + +.tag-1 { + top: 15%; + right: 10rpx; +} + +.tag-2 { + bottom: 25%; + left: 0; +} + +.tag-3 { + bottom: 25%; + right: 0; +} + +.process-section { + padding: 15rpx 40rpx 20rpx; + flex-shrink: 0; + position: relative; + z-index: 1; +} + +.section-title { + font-size: 32rpx; + font-weight: 700; + color: #e63946; + text-align: center; + margin-bottom: 25rpx; +} + +.process-steps { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10rpx; +} + +.step-item { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; +} + +.step-circle { + width: 70rpx; + height: 70rpx; + border-radius: 50%; + background: #b8b9bc; + display: flex; + align-items: center; + justify-content: center; + font-size: 36rpx; + font-weight: 700; + color: #fff; + margin-bottom: 12rpx; +} + +.step-circle.active { + background: #e63946; +} + +.step-label { + font-size: 20rpx; + color: #666; + margin-bottom: 4rpx; +} + +.step-text { + font-size: 24rpx; + color: #333; + font-weight: 600; +} + +.step-line { + width: 50rpx; + height: 4rpx; + background: #e0e0e0; + margin: 0 6rpx 50rpx; +} + +.start-button { + margin: 20rpx 40rpx 0; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + border-radius: 50rpx; + padding: 24rpx; + text-align: center; + box-shadow: 0 8rpx 30rpx rgba(230, 57, 70, 0.3); + position: relative; + z-index: 1; +} + +.button-text { + font-size: 30rpx; + color: #fff; + font-weight: 700; +} + +.upload-button { + margin: 0 40rpx 16rpx; + border: 2rpx solid #8b5cf6; + border-radius: 50rpx; + padding: 18rpx; + text-align: center; + background: #fff; + flex-shrink: 0; +} + +.upload-text { + font-size: 26rpx; + color: #8b5cf6; + font-weight: 600; +} + +.ai-generated-tip { + text-align: center; + font-size: 24rpx; + color: #9CA3AF; + margin-top: 24rpx; + margin-bottom: 16rpx; +} + +/* 确保底部导航显示 */ +custom-tab-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 9999; +} diff --git a/miniprogram/pages/index/result.js b/miniprogram/pages/index/result.js new file mode 100644 index 0000000..419239c --- /dev/null +++ b/miniprogram/pages/index/result.js @@ -0,0 +1,539 @@ +// pages/index/result.js - AI分析结果页(按旧版模板重构) +const app = getApp() +const payment = require('../../utils/payment') +const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js') + +Page({ + data: { + isAnalyzing: true, + showResult: false, + hasError: false, + errorMessage: '', + noFaceError: false, + noFaceMessage: '', + progress: 0, + analyzingTip: '正在识别面部特征...', + activeTab: 'face', // 'face' | 'bone' + // 报告付费信息 + payInfo: { + requiresPayment: false, // 是否需要付费才解锁完整报告 + isPaid: false, // 当前这次是否已解锁 + amountYuan: 0 // 人脸报告价格(元) + }, + result: { + mbti: '', + title: '', + summary: '', + pdp: '', + pdpAux: '', + pdpEmoji: '', + disc: '', + discAux: '', + traits: [], + faceAnalysisText: '', + boneAnalysisText: '', + careers: [], + relationship: '', + gallupTop3: [], + // 完整面相/骨相(object 才用,字符串走 *Text 字段) + faceAnalysis: null, + boneAnalysis: null, + // 企业版:职业画像、HR视角、老板视角、简历亮点 + portrait: null, + hrView: null, + bossView: null, + resumeHighlights: '' + }, + // 当前这次AI分析对应的测试记录ID(mbti_test_results.id) + testResultId: null, + // 支付后是否已经触发过一次“刷新完整报告”,避免重复刷新 + hasReloadedAfterPay: false, + // 是否已在本地拥有手机号(决定是否还需要弹出微信手机号授权) + hasPhone: false, + analyzingTitle: '正在分析中', + reportTitle: '分析报告', + aiAnalysisText: '智能分析' + }, + + onLoad(options) { + const id = options && options.id + const type = options && options.type + + // 加载文案配置(分析中提示、报告标题等) + const tc = app.globalData.textConfig + if (tc) { + this.setData({ + analyzingTitle: tc.analyzingTitle || '正在分析中', + reportTitle: tc.reportTitle || '分析报告', + aiAnalysisText: tc.aiAnalysisText || '智能分析' + }) + if (tc.reportTitle) wx.setNavigationBarTitle({ title: tc.reportTitle }) + } else { + app.getRuntimeConfig().then((cfg) => { + if (cfg && cfg.textConfig) { + app.globalData.textConfig = cfg.textConfig + const t = cfg.textConfig + this.setData({ + analyzingTitle: t.analyzingTitle || '正在分析中', + reportTitle: t.reportTitle || '分析报告', + aiAnalysisText: t.aiAnalysisText || '智能分析' + }) + if (t.reportTitle) wx.setNavigationBarTitle({ title: t.reportTitle }) + } + }).catch(() => {}) + } + + // 从历史记录进入:根据ID从后端读取数据库中的结果 + if (id && type === 'ai') { + const token = app.globalData.token || wx.getStorageSync('token') + const apiBase = app.globalData.apiBase + if (!token || !apiBase) { + wx.showToast({ title: '未登录,无法读取历史记录', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + return + } + + wx.showLoading({ title: '加载历史记录...' }) + wx.request({ + url: `${apiBase}/api/test/detail`, + method: 'GET', + header: { + 'Authorization': `Bearer ${token}` + }, + data: { id }, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200 && res.data && res.data.data) { + const payload = res.data.data + const apiData = payload.data || payload + // 历史详情场景下,记录当前测试记录ID + this.setData({ testResultId: id }) + this.processResult(apiData) + // 从历史进入:金额与是否需付款以 test_results 的 paidAmount/needPaymentToUnlock 为准 + this.initPayInfoFromRuntime( + !!payload.requiresPayment, + !!payload.isPaid, + payload + ) + } else { + wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '网络错误,加载失败', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }) + return + } + + // 正常从拍照流程进入:调用 /api/analyze + this.startAnalysis() + }, + + onShow() { + if (!ensureProfileCompleteAndRedirect()) return + this.setData({ hasPhone: hasPhone() }) + const tc = app.globalData.textConfig + if (tc) { + this.setData({ + analyzingTitle: tc.analyzingTitle || '正在分析中', + reportTitle: tc.reportTitle || '分析报告', + aiAnalysisText: tc.aiAnalysisText || '智能分析' + }) + } + }, + + // 面相/骨相 Tab 切换 + switchAnalysisTab(e) { + this.setData({ activeTab: e.currentTarget.dataset.tab }) + }, + + // 调用后端AI分析API(aiPhotos 应为上传后的 URL 数组,由拍照页上传后写入) + startAnalysis() { + let photos = wx.getStorageSync('aiPhotos') || [] + if (!Array.isArray(photos)) photos = [] + const isUrls = photos.length > 0 && photos.every(p => typeof p === 'string' && (p.startsWith('http://') || p.startsWith('https://'))) + if (!isUrls || photos.length === 0) { + this.setData({ progress: 100 }) + setTimeout(() => { + wx.showToast({ title: '请先完成拍照并上传', icon: 'none' }) + wx.navigateBack({ delta: 1 }) + }, 300) + return + } + const tips = [ + '正在识别面部特征...', + '分析眉眼特征...', + '结合《冰鉴》分析骨形...', + '匹配MBTI/PDP/DISC...', + '生成综合报告...' + ] + let progress = 0 + let tipIndex = 0 + + // 进度动画 + const timer = setInterval(() => { + progress += 3 + if (progress > 95) progress = 95 + if (progress > (tipIndex + 1) * 18 && tipIndex < tips.length - 1) tipIndex++ + this.setData({ progress: Math.floor(progress), analyzingTip: tips[tipIndex] }) + }, 200) + + // 调用后端API:appScope='enterprise' 时才传 enterpriseId,个人版不传 + const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} + const scope = (app.globalData && app.globalData.appScope) || 'personal' + const enterpriseId = scope === 'enterprise' + ? (app.globalData.enterpriseIdFromScene || userInfo.enterpriseId || null) + : null + wx.request({ + url: `${app.globalData.apiBase}/api/analyze`, + method: 'POST', + header: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${wx.getStorageSync('token') || ''}` + }, + data: { + photoUrls: photos, + userId: app.globalData.openId || '', + ...(enterpriseId ? { enterpriseId: Number(enterpriseId) } : {}) + }, + success: (res) => { + clearInterval(timer) + + if (res.statusCode === 200 && res.data) { + const bodyCode = res.data.code + // 后端 error() 始终返回 HTTP 200,业务错误码放在 data.code 中 + if (bodyCode === 422) { + // AI 未检测到人脸 + const msg = res.data.message || '图片中未检测到人脸,请确保拍摄时面部清晰可见' + this.setData({ progress: 100 }) + setTimeout(() => this.showNoFaceError(msg), 300) + } else if (bodyCode === 200) { + const apiData = res.data.data || res.data + this.setData({ progress: 100, analyzingTip: '分析完成!' }) + setTimeout(() => { + this.processResult(apiData) + }, 500) + } else { + console.error('API返回错误:', res) + this.setData({ progress: 100 }) + const msg = (res.data && (res.data.message || res.data.msg)) || '分析失败,请稍后重试' + setTimeout(() => this.showAnalyzeError(msg), 300) + } + } else { + console.error('API返回错误:', res) + this.setData({ progress: 100 }) + setTimeout(() => this.showAnalyzeError('分析失败,请稍后重试'), 300) + } + }, + fail: (err) => { + clearInterval(timer) + console.error('API调用失败:', err) + this.setData({ progress: 100 }) + setTimeout(() => this.showAnalyzeError('网络异常,请检查网络后重试'), 300) + } + }) + }, + + // 处理API返回结果 + processResult(apiData) { + const result = { + mbti: apiData.mbti?.type || '', + title: apiData.mbti?.title || '', + summary: apiData.personalitySummary || apiData.overview || '', + pdp: apiData.pdp?.primary || '', + pdpAux: apiData.pdp?.secondary || '', + pdpEmoji: this.getPDPEmoji(apiData.pdp?.primary), + disc: apiData.disc?.primary || '', + discAux: apiData.disc?.secondary || '', + traits: Array.isArray(apiData.advantages) ? apiData.advantages : [], + faceAnalysisText: typeof apiData.faceAnalysis === 'string' ? apiData.faceAnalysis : '', + boneAnalysisText: typeof apiData.boneAnalysis === 'string' ? apiData.boneAnalysis : '', + careers: Array.isArray(apiData.careers) ? apiData.careers : [], + relationship: apiData.relationship || '', + gallupTop3: Array.isArray(apiData.gallupTop3) ? apiData.gallupTop3 : [], + faceAnalysis: null, + boneAnalysis: null, + portrait: apiData.portrait || null, + hrView: apiData.hrView || null, + bossView: apiData.bossView || null, + resumeHighlights: apiData.resumeHighlights || '', + timestamp: Date.now() + } + + wx.setStorageSync('aiResult', result) + if (app.globalData) app.globalData.aiResult = result + // 测试记录已由 /api/analyze 顺带写入,无需再调 /api/test/submit + + const updates = { + isAnalyzing: false, + showResult: true, + hasError: false, + result + } + + // 记录本次测试记录ID(由 /api/analyze 返回) + if (apiData && apiData._testResultId) { + updates.testResultId = apiData._testResultId + } + + this.setData(updates) + + // 优先使用后端 /api/analyze 返回的价格信息,避免二次请求 + if (apiData._payment) { + const p = apiData._payment || {} + this.setData({ + payInfo: { + requiresPayment: !!p.requiresPayment, + isPaid: false, + amountYuan: typeof p.amountYuan === 'number' + ? p.amountYuan + : (p.amountFen ? (p.amountFen / 100) : 0) + } + }) + } + // 无 _payment 时(如从历史详情进入)不在这里调 initPayInfoFromRuntime,避免异步 getRuntimeConfig 后覆盖详情接口返回的 needPaymentToUnlock;由调用方用 detail 的 payload 单独设置 payInfo + }, + + + // PDP类型对应emoji + getPDPEmoji(type) { + const map = { + '老虎': '🐅', '孔雀': '🦚', '无尾熊': '🐨', '考拉': '🐨', + '猫头鹰': '🦉', '变色龙': '🦎' + } + return map[type] || '🐅' + }, + + /** + * 初始化付费信息:结合运行配置 + 可选的数据库记录状态 + * @param {boolean|null} recordRequires 是否从记录中读到需要付费(可为空) + * @param {boolean} recordIsPaid 记录是否已付费 + */ + initPayInfoFromRuntime(recordRequires = null, recordIsPaid = false, detailPayload = null) { + // 从历史/详情进入:优先用 test_results 的 paidAmount,需付款但金额为0 则直接可查看 + if (detailPayload && (detailPayload.paidAmount != null || detailPayload.amountYuan != null || detailPayload.needPaymentToUnlock != null)) { + const paidAmount = Number(detailPayload.paidAmount ?? 0) + const amountYuan = detailPayload.amountYuan != null ? Number(detailPayload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPay = detailPayload.needPaymentToUnlock === true || (!!detailPayload.requiresPayment && !detailPayload.isPaid && paidAmount > 0) + this.setData({ + payInfo: { + requiresPayment: needPay, + isPaid: !!detailPayload.isPaid, + amountYuan: needPay ? amountYuan : 0 + } + }) + return + } + app.getRuntimeConfig() + .then((cfg) => { + const pricing = cfg.pricing || {} + const reportRequires = cfg.reportRequiresPayment || {} + const facePriceRaw = pricing.face + const facePrice = typeof facePriceRaw === 'number' + ? facePriceRaw + : Number(facePriceRaw || 0) + + const requiresByConfig = !!(reportRequires && reportRequires.face) + const requiresPayment = + typeof recordRequires === 'boolean' ? recordRequires : requiresByConfig + // 系统设置需付款但金额为0 则直接可查看,不展示付费墙 + const needPay = requiresPayment && facePrice > 0 + const amountYuan = facePrice > 0 ? facePrice : 0 + + this.setData({ + payInfo: { + requiresPayment: needPay, + isPaid: !!recordIsPaid, + amountYuan + } + }) + }) + .catch(() => { + this.setData({ + payInfo: { + requiresPayment: false, + isPaid: !!recordIsPaid, + amountYuan: 0 + } + }) + }) + }, + + // 解锁完整报告:发起人脸测试付费 + unlockFullReport() { + const { payInfo, testResultId, hasReloadedAfterPay } = this.data + if (!payInfo.requiresPayment || payInfo.isPaid) { + return + } + + app.ensureLogin().then((logged) => { + if (!logged) { + wx.showToast({ title: '请先登录后再解锁', icon: 'none' }) + return + } + payment.purchaseFaceTest({ + testResultId, + success: () => { + wx.showToast({ title: '已解锁完整报告', icon: 'success' }) + + // 本地先标记已付费,避免按钮仍然提示“需要解锁” + this.setData({ + 'payInfo.isPaid': true + }) + + // 避免重复触发刷新:只在还没刷新的情况下,延迟 0.5s 拉一次详情 + if (testResultId && !hasReloadedAfterPay) { + this.setData({ hasReloadedAfterPay: true }) + setTimeout(() => { + this.reloadFullDetail(testResultId) + }, 500) + } + }, + fail: () => { + // 支付失败或取消,这里不做额外处理 + } + }) + }) + }, + + // AI结果页付费按钮:就地触发微信手机号授权,然后调用 unlockFullReport + onGetPhoneNumberForFacePay(e) { + if (!ensureProfileCompleteAndRedirect()) return + const { code, errMsg } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + if (!hasPhone()) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + this.unlockFullReport() + return + } + if (!code) { + if (hasPhone()) { + this.unlockFullReport() + } else { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + } + return + } + bindPhoneByCode(code) + .then(() => { + this.setData({ hasPhone: true }) + this.unlockFullReport() + }) + .catch(() => { + // 保持在当前页,等待用户重新点击 + }) + }, + + // 解锁成功后,根据测试记录ID重新拉取完整详情 + reloadFullDetail(id) { + if (!id) return + + const app = getApp() + const apiBase = app.globalData?.apiBase || '' + + wx.showLoading({ title: '加载完整报告...' }) + wx.request({ + url: `${apiBase}/api/test/detail`, + method: 'GET', + header: { + 'Authorization': app.globalData?.token ? `Bearer ${app.globalData.token}` : '' + }, + data: { id }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.data) { + const payload = res.data.data + const apiData = payload.data || payload + + // 详情接口在后台会根据 isPaid 返回完整结构,这里直接复用已有处理逻辑 + this.processResult(apiData) + + // 同步数据库中的付费状态与金额(test_results.paidAmount) + const recordRequires = + typeof payload.requiresPayment === 'boolean' || typeof payload.requiresPayment === 'number' + ? !!payload.requiresPayment + : null + const recordIsPaid = !!payload.isPaid + this.initPayInfoFromRuntime(recordRequires, recordIsPaid, payload) + } else { + console.error('刷新完整报告失败', res) + } + }, + fail: (err) => { + console.error('请求完整报告失败', err) + }, + complete: () => { + wx.hideLoading() + } + }) + }, + + // 无人脸错误:显示提示并提供重新拍摄入口 + showNoFaceError(message) { + this.setData({ + isAnalyzing: false, + showResult: false, + noFaceError: true, + noFaceMessage: message || '图片中未检测到人脸,请重新拍摄清晰的正面照片' + }) + }, + + // 分析失败:不使用任何本地模拟数据 + showAnalyzeError(message) { + this.setData({ + isAnalyzing: false, + showResult: true, + hasError: true, + errorMessage: message || '分析失败,请稍后重试' + }) + }, + + // 跳转到详情性格测试选择页(MBTI / PDP / DISC 三选一) + goToMBTI() { + wx.navigateTo({ url: '/pages/test-select/index' }) + }, + + shareResult() { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + }, + + retake() { + // camera 是 tabBar 页面,必须用 switchTab + wx.switchTab({ url: '/pages/index/camera' }) + }, + + goHome() { + // 根据当前 scope 跳对应首页 + const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal' + if (scope === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + }, + + onShareAppMessage() { + const r = this.data.result + const t = this.data.aiAnalysisText || '智能分析' + const { getSharePathByScope } = require('../../utils/share') + return { + title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`, + path: getSharePathByScope('/pages/index/index') + } + }, + + onShareTimeline() { + const r = this.data.result + const t = this.data.aiAnalysisText || '智能分析' + const { buildShareQuery } = require('../../utils/share') + return { + title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`, + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/index/result.json b/miniprogram/pages/index/result.json new file mode 100644 index 0000000..2c985f3 --- /dev/null +++ b/miniprogram/pages/index/result.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "分析报告", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/index/result.wxml b/miniprogram/pages/index/result.wxml new file mode 100644 index 0000000..a81af27 --- /dev/null +++ b/miniprogram/pages/index/result.wxml @@ -0,0 +1,304 @@ + + + + + + + + + {{analyzingTitle || '正在分析中'}} + {{analyzingTip}} + + + + 这个过程可能需要30秒到1分钟,请耐心等待... + + + + + + + + 🚫 + + 未检测到人脸 + {{noFaceMessage}} + + 拍摄建议: + • 确保面部完整出现在画面中 + • 保持光线充足,避免逆光 + • 正对镜头,不要遮挡面部 + • 保持适当距离,不要过近或过远 + + + 重新拍摄 + + + + + + + + + + {{hasError ? '分析出现异常' : '分析完成,以下是您的个性化报告'}} + + + + + 错误信息 + {{errorMessage}} + + 重新分析 + + + + + + 性格类型分析 + + + + {{result.mbti}} + {{result.title}} + {{result.summary}} + + + + + + PDP主性格 + {{result.pdp || '--'}} + 辅助: {{result.pdpAux}} + + + DISC类型 + {{result.disc || '--'}}型 + 辅助: {{result.discAux}} + + + + + + 主要优势 + + + + {{item}} + + + + + + 下一步:详细性格测试 → + + + + + + 性格概述 + {{result.summary}} + + + + + 盖洛普前三大优势 + + + {{index + 1}} + {{item}} + + + + + + + + 完整版性格深度解析 + 解锁后将展示完整报告内容 + + + + + 一次性解锁本次{{reportTitle || '分析报告'}},永久保存在「历史记录」中 + + + + + + + + + + + 面部特征分析 + + + {{item.label}}: + {{item.description}} + + + + + + + 面相分析 + {{result.faceAnalysisText}} + + + + + + + 骨相分析(《冰鉴》八骨) + + + 五行形相: + {{result.boneAnalysis.boneFormSummary.elementType}} + + + 骨肉关系: + {{result.boneAnalysis.boneFormSummary.boneFleshRelation}} + + + + + + + 骨相分析(《冰鉴》八骨) + {{result.boneAnalysisText}} + + + + + 人际关系与团队合作 + {{result.relationship}} + + + + + 简历亮点 + {{result.resumeHighlights}} + + + + + 职业画像 + + 核心优势 + + {{item}} + + + + 潜在风险 + + {{item}} + + + + 工作风格 + {{result.portrait.workStyle}} + + + + + + HR视角 + + 岗位推荐 + + 最适合 + + {{item}} + + + + 不适合 + + {{item}} + + + + + 成长周期 + + + 入职适应 + {{result.hrView.lifecycle.onboarding}} + + + 试用期 + {{result.hrView.lifecycle.probation}} + + + 成长期 + {{result.hrView.lifecycle.growth}} + + + 留人因素 + {{result.hrView.lifecycle.retention}} + + + + + 绩效潜力 + 潜力:{{result.hrView.performance.potential}} + + 驱动因子: + {{result.hrView.performance.drivers.join('、')}} + + + + 团队适配 + {{result.hrView.teamFit.bestTeam || ''}} + 管理建议:{{result.hrView.teamFit.manageAdvice}} + + + + + + 老板视角 + {{result.bossView.headline}} + + + {{item.label}} + {{item.value}} + + + 用人成本预判:{{result.bossView.costInsight}} + + + + + + + + + 重新分析 + + + 返回首页 + + + + 人工智能生成 + + diff --git a/miniprogram/pages/index/result.wxss b/miniprogram/pages/index/result.wxss new file mode 100644 index 0000000..21131fc --- /dev/null +++ b/miniprogram/pages/index/result.wxss @@ -0,0 +1,984 @@ +/* pages/index/result.wxss - AI分析结果(按旧版模板重构) */ +.container { + min-height: 100vh; + background-color: #f5f5f5; + padding: 0 24rpx 60rpx; +} + +/* ========== 分析中模态框 ========== */ +.analyzing-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.analyzing-content { + background-color: #fff; + border-radius: 24rpx; + padding: 80rpx 60rpx; + width: 560rpx; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15); +} + +.analyzing-icon { + width: 120rpx; + height: 120rpx; + margin-bottom: 40rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.spinner { + width: 120rpx; + height: 120rpx; + border: 8rpx solid #e0e0e0; + border-top-color: #e63946; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.analyzing-title { + font-size: 36rpx; + font-weight: 600; + color: #333; + margin-bottom: 16rpx; +} + +.analyzing-desc { + font-size: 28rpx; + color: #e63946; + margin-bottom: 24rpx; + text-align: center; + font-weight: 500; +} + +.analyzing-bar { + width: 400rpx; + height: 12rpx; + background: #e5e5e5; + border-radius: 6rpx; + overflow: hidden; + margin-bottom: 24rpx; +} + +.analyzing-bar-fill { + height: 100%; + background: linear-gradient(90deg, #e63946, #ff6b9d); + border-radius: 6rpx; + transition: width 0.3s ease; +} + +.analyzing-hint { + font-size: 24rpx; + color: #999; + text-align: center; + line-height: 1.6; +} + +/* ========== 状态横幅 ========== */ +.status-banner { + background: linear-gradient(135deg, #e8f5e9 0%, #f1f8e9 100%); + padding: 24rpx; + border-radius: 12rpx; + display: flex; + align-items: center; + margin: 24rpx 0; +} + +.status-dot { + width: 12rpx; + height: 12rpx; + background-color: #4caf50; + border-radius: 50%; + margin-right: 16rpx; +} + +.status-text { + font-size: 26rpx; + color: #2e7d32; +} + +.error-banner { + background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%); +} + +.error-dot { + background-color: #f44336; +} + +.error-banner .status-text { + color: #c62828; + font-weight: bold; +} + +.error-content { + background-color: #fff; + border-radius: 16rpx; + padding: 32rpx; + margin-bottom: 24rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); + border-left: 4rpx solid #f44336; +} + +.error-title { + font-size: 28rpx; + color: #c62828; + font-weight: bold; + display: block; + margin-bottom: 16rpx; +} + +.error-message { + font-size: 24rpx; + color: #666; + line-height: 1.8; + display: block; + white-space: pre-wrap; + word-break: break-all; + background-color: #f5f5f5; + padding: 16rpx; + border-radius: 8rpx; + margin-bottom: 24rpx; +} + +.retry-btn { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + padding: 20rpx; + border-radius: 12rpx; + text-align: center; +} + +.retry-text { + font-size: 28rpx; + color: #fff; + font-weight: 500; +} + +/* ========== 通用卡片 ========== */ +.card { + background-color: #fff; + border-radius: 16rpx; + padding: 32rpx; + margin-bottom: 24rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); + word-wrap: break-word; +} + +.card-title { + font-size: 32rpx; + font-weight: 600; + color: #333; + display: block; + margin-bottom: 24rpx; +} + +.card-text { + font-size: 28rpx; + color: #666; + line-height: 1.8; + display: block; +} + +/* ========== 付费解锁毛玻璃区域 ========== */ +.paywall-card { + position: relative; + overflow: hidden; +} + +.paywall-content { + position: relative; +} + +.paywall-blur { + padding: 32rpx 24rpx 140rpx; + border-radius: 24rpx; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(18rpx); +} + +.paywall-mask { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + border-radius: 24rpx; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(245, 245, 245, 0.98)); + pointer-events: none; +} + +.paywall-fake-title { + display: block; + font-size: 30rpx; + font-weight: 600; + color: #444; + margin-bottom: 16rpx; +} + +.paywall-fake-line { + display: block; + font-size: 26rpx; + color: #888; + line-height: 1.8; +} + +.paywall-btn { + position: absolute; + left: 5%; + right: 5%; + width: 90%; + bottom: 56rpx; + padding: 20rpx 0; + border-radius: 999rpx; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.35); + display: flex; + flex-direction: row; + align-items: baseline; + justify-content: center; + gap: 12rpx; +} + +.paywall-btn-main { + font-size: 30rpx; + color: #fff; + font-weight: 600; +} + +.paywall-btn-price { + font-size: 24rpx; + color: #ffe5f0; +} + +.paywall-tip { + display: block; + margin-top: 8rpx; + font-size: 22rpx; + color: #999; + text-align: center; +} + +/* ========== 性格类型卡片 ========== */ +.personality-card { + text-align: center; +} + +.personality-type { + margin-bottom: 32rpx; +} + +.type-code { + font-size: 72rpx; + font-weight: 700; + color: #e63946; + display: block; + margin-bottom: 8rpx; +} + +.type-name { + font-size: 28rpx; + color: #666; + display: block; + margin-bottom: 16rpx; +} + +.type-desc { + font-size: 26rpx; + color: #888; + line-height: 1.6; + display: block; + text-align: center; + padding: 0 20rpx; +} + +.type-details { + display: flex; + gap: 24rpx; + margin-bottom: 24rpx; +} + +.type-box { + flex: 1; + padding: 24rpx; + border-radius: 12rpx; + text-align: center; +} + +.pdp-box { + background: linear-gradient(135deg, #faf5fc 0%, #f3e5f5 100%); +} + +.disc-box { + background: linear-gradient(135deg, #f5f9fd 0%, #e3f2fd 100%); +} + +.type-label { + font-size: 24rpx; + color: #666; + display: block; + margin-bottom: 8rpx; +} + +.type-value { + font-size: 30rpx; + font-weight: 600; + display: block; + margin-bottom: 4rpx; +} + +.pdp-box .type-value { color: #7b1fa2; } +.disc-box .type-value { color: #1976d2; } + +.type-sub { + font-size: 22rpx; + color: #999; + display: block; +} + +/* 主要优势 */ +.advantages-box { + background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); + padding: 24rpx; + border-radius: 12rpx; + margin-bottom: 24rpx; +} + +.advantages-label { + font-size: 26rpx; + color: #666; + display: block; + text-align: center; + margin-bottom: 16rpx; +} + +.advantages-list { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + gap: 16rpx; +} + +.advantage-item { + display: flex; + flex-direction: column; + align-items: center; +} + +.advantage-dot { + width: 12rpx; + height: 12rpx; + background-color: #4caf50; + border-radius: 50%; + margin-bottom: 8rpx; +} + +.advantage-text { + font-size: 24rpx; + font-weight: 500; + color: #333; +} + +.next-btn { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + padding: 24rpx; + border-radius: 48rpx; + box-shadow: 0 8rpx 16rpx rgba(230, 57, 70, 0.3); +} + +.next-btn-text { + font-size: 28rpx; + font-weight: 600; + color: #fff; + display: block; + text-align: center; +} + +/* ========== Tab切换 ========== */ +.tabs { + display: flex; + border-bottom: 2rpx solid #f0f0f0; + margin-bottom: 24rpx; + background-color: #fff; + border-radius: 16rpx 16rpx 0 0; +} + +.tab { + flex: 1; + padding: 24rpx; + text-align: center; + position: relative; +} + +.tab.active::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 60rpx; + height: 4rpx; + background-color: #e63946; + border-radius: 2rpx; +} + +.tab-text { + font-size: 28rpx; + color: #999; +} + +.tab-text.active { + color: #e63946; + font-weight: 600; +} + +/* ========== 面相分析 ========== */ +.feature-list { + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.feature-item { + display: flex; + font-size: 26rpx; + line-height: 1.8; +} + +.feature-label { + font-weight: 600; + color: #333; + flex-shrink: 0; +} + +.feature-desc { + color: #666; + flex: 1; +} + +/* 职业建议 */ +.career-list { + display: flex; + flex-direction: column; + gap: 24rpx; +} + +.career-item { + display: flex; + flex-direction: column; + gap: 8rpx; +} + +.career-header { + display: flex; + align-items: center; +} + +.career-dot { + width: 12rpx; + height: 12rpx; + background-color: #e63946; + border-radius: 50%; + margin-right: 16rpx; + flex-shrink: 0; +} + +.career-name { + font-size: 28rpx; + font-weight: 600; + color: #333; +} + +.career-reason { + font-size: 24rpx; + color: #666; + line-height: 1.6; + padding-left: 28rpx; +} + +/* 人际关系 */ +.interpersonal-content { + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.interpersonal-item { + display: flex; + font-size: 26rpx; + line-height: 1.8; +} + +.interpersonal-label { + font-weight: 600; + color: #333; + flex-shrink: 0; +} + +.interpersonal-text { + color: #666; + flex: 1; +} + +/* 潜能 */ +.potential-list { + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.potential-item { + display: flex; + flex-direction: column; + gap: 8rpx; +} + +.potential-name { + font-size: 28rpx; + font-weight: 600; + color: #333; +} + +.potential-desc { + font-size: 24rpx; + color: #666; + line-height: 1.6; + padding-left: 20rpx; +} + +/* ========== 骨相分析 ========== */ +.bone-content { + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.bone-item { + display: flex; + font-size: 26rpx; + line-height: 1.8; +} + +.bone-label { + font-weight: 600; + color: #333; + flex-shrink: 0; + min-width: 120rpx; +} + +.bone-text { + color: #666; + flex: 1; +} + +/* 人生阶段 */ +.life-stage-list { + display: flex; + flex-direction: column; + gap: 24rpx; +} + +.life-stage-item { + background: linear-gradient(135deg, #fff9e6 0%, #fff3cd 100%); + padding: 20rpx; + border-radius: 12rpx; + border-left: 4rpx solid #ff9800; +} + +.stage-label { + font-size: 28rpx; + font-weight: 600; + color: #e65100; + display: block; + margin-bottom: 8rpx; +} + +.stage-desc { + font-size: 26rpx; + color: #666; + line-height: 1.8; + display: block; +} + +/* 骨相总评 */ +.bone-summary-content { + display: flex; + flex-direction: column; + gap: 24rpx; +} + +.summary-text { + font-size: 28rpx; + color: #333; + line-height: 1.8; + display: block; +} + +.suggestions-title { + font-size: 28rpx; + font-weight: 600; + color: #333; + margin-top: 8rpx; +} + +.suggestions-list { + display: flex; + flex-direction: column; + gap: 16rpx; + margin-top: 12rpx; +} + +.suggestion-item { + background: linear-gradient(135deg, #fce4ec 0%, #f8bbd0 100%); + padding: 16rpx 20rpx; + border-radius: 8rpx; +} + +.suggestion-text { + font-size: 26rpx; + color: #333; + line-height: 1.6; + display: block; +} + +/* ========== 简化版(无面相骨相数据) ========== */ +.simple-career-list { + display: flex; + flex-wrap: wrap; + gap: 16rpx; +} + +.simple-career-tag { + padding: 12rpx 24rpx; + background: #f3f4f6; + border-radius: 24rpx; + font-size: 26rpx; + color: #374151; +} + +.ai-generated-tip { + text-align: center; + font-size: 24rpx; + color: #9CA3AF; + margin-top: 24rpx; + padding-bottom: 40rpx; +} + +/* ========== 操作按钮 ========== */ +.action-section { + margin-top: 16rpx; + padding-bottom: 40rpx; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 100%!important; + height: 100rpx; + min-height: 100rpx; + border-radius: 28rpx; + margin-bottom: 20rpx; + padding: 0; + border: none; + line-height: 100rpx; + box-sizing: border-box; + overflow: hidden; +} + +.btn::after { + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.btn-outline { + background: #fff; + border: 2rpx solid #e63946; +} + +.btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +.btn-text-outline { + font-size: 30rpx; + color: #e63946; + font-weight: 500; +} + +/* ========== 无人脸错误状态 ========== */ +.no-face-wrap { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 40rpx 32rpx; + box-sizing: border-box; +} + +.no-face-card { + background: #fff; + border-radius: 24rpx; + padding: 60rpx 48rpx; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); +} + +.no-face-icon { + width: 120rpx; + height: 120rpx; + background: #fff3f3; + border-radius: 60rpx; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 32rpx; +} + +.no-face-emoji { + font-size: 56rpx; + line-height: 1; +} + +.no-face-title { + font-size: 38rpx; + font-weight: 700; + color: #1a1a2e; + margin-bottom: 16rpx; + text-align: center; +} + +.no-face-desc { + font-size: 28rpx; + color: #e63946; + text-align: center; + line-height: 1.6; + margin-bottom: 40rpx; +} + +.no-face-tips { + width: 100%; + background: #f8f9fa; + border-radius: 16rpx; + padding: 28rpx 32rpx; + margin-bottom: 48rpx; + box-sizing: border-box; +} + +.no-face-tips-title { + font-size: 28rpx; + font-weight: 600; + color: #333; + display: block; + margin-bottom: 16rpx; +} + +.no-face-tip-item { + font-size: 26rpx; + color: #666; + line-height: 1.8; + display: block; +} + +.no-face-btn { + width: 100%; + height: 96rpx; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + border-radius: 48rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.no-face-btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +/* ========== 盖洛普优势卡片 ========== */ +.gallup-list { + display: flex; + flex-direction: column; + gap: 20rpx; + margin-top: 12rpx; +} + +.gallup-item { + display: flex; + align-items: center; + gap: 24rpx; + padding: 18rpx 24rpx; + background: linear-gradient(135deg, rgba(230, 57, 70, 0.06) 0%, rgba(255, 107, 157, 0.04) 100%); + border-radius: 16rpx; + border-left: 6rpx solid #e63946; +} + +.gallup-rank { + width: 56rpx; + height: 56rpx; + border-radius: 50%; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + display: flex; + align-items: center; + justify-content: center; + font-size: 28rpx; + font-weight: 700; + color: #fff; + flex-shrink: 0; + text-align: center; + line-height: 56rpx; +} + +.gallup-text { + font-size: 30rpx; + font-weight: 600; + color: #1a1a2e; +} + +/* ========== 企业版:职业画像 / HR视角 / 老板视角 ========== */ +.enterprise-section { + margin-bottom: 24rpx; +} + +.enterprise-section:last-child { + margin-bottom: 0; +} + +.enterprise-label { + font-size: 26rpx; + font-weight: 600; + color: #333; + display: block; + margin-bottom: 12rpx; +} + +.enterprise-sublabel { + font-size: 24rpx; + color: #888; + display: block; + margin-bottom: 8rpx; +} + +.enterprise-tags { + display: flex; + flex-wrap: wrap; + gap: 12rpx; +} + +.enterprise-tag { + padding: 10rpx 20rpx; + background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); + border-radius: 24rpx; + font-size: 24rpx; + color: #2e7d32; +} + +.enterprise-tag.risk { + background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); + color: #e65100; +} + +.lifecycle-list { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.lifecycle-item { + padding: 16rpx 20rpx; + background: #f8f9fa; + border-radius: 12rpx; +} + +.lifecycle-key { + font-size: 24rpx; + color: #999; + display: block; + margin-bottom: 6rpx; +} + +.lifecycle-val { + font-size: 26rpx; + color: #333; + line-height: 1.6; + display: block; +} + +.boss-card { + border-left: 6rpx solid #1976d2; +} + +.boss-headline { + font-size: 30rpx; + font-weight: 600; + color: #1a1a2e; + line-height: 1.6; + display: block; + margin-bottom: 20rpx; +} + +.boss-metrics { + display: flex; + flex-wrap: wrap; + gap: 16rpx; +} + +.boss-metric { + flex: 1; + min-width: 140rpx; + padding: 16rpx; + background: #f5f5f5; + border-radius: 12rpx; + text-align: center; +} + +.boss-metric-label { + font-size: 22rpx; + color: #999; + display: block; + margin-bottom: 6rpx; +} + +.boss-metric-value { + font-size: 28rpx; + font-weight: 600; + color: #333; + display: block; +} + +.boss-metric-value.high { + color: #4caf50; +} + +.boss-metric-value.medium { + color: #ff9800; +} + +.boss-metric-value.low { + color: #f44336; +} diff --git a/miniprogram/pages/index/upload.js b/miniprogram/pages/index/upload.js new file mode 100644 index 0000000..6287649 --- /dev/null +++ b/miniprogram/pages/index/upload.js @@ -0,0 +1,193 @@ +// pages/index/camera.js - 面相分析拍照页,拍完后上传到服务器再跳转结果页 +const app = getApp() +const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js') + +Page({ + data: { + sampleImages: [ + 'https://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/mbti/20260312/img_69b22c5b77dd72.66364633.png', + 'https://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/mbti/20260312/img_69b22d71b312f8.14596541.png', + 'https://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/mbti/20260312/img_69b22d78d52b94.76297015.png' + ], + photos: ['', '', ''], + uploadedUrls: [], + photoIndex: 0, + guideTexts: ['请正对镜头', '请向左转45°', '请向右转45°'], + guideText: '请正对镜头', + uploading: false, + needPhoneAuth: false, + aiAnalysisText: '智能分析' + }, + + onLoad() { + const tc = app.globalData.textConfig + if (tc && tc.aiAnalysisText) { + this.setData({ aiAnalysisText: tc.aiAnalysisText }) + } else { + app.getRuntimeConfig().then((cfg) => { + if (cfg && cfg.textConfig) { + app.globalData.textConfig = cfg.textConfig + this.setData({ aiAnalysisText: cfg.textConfig.aiAnalysisText || '智能分析' }) + } + }).catch(() => {}) + } + }, + + onShow() { + if (!ensureProfileCompleteAndRedirect()) return + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 1 }) + } + this.setData({ needPhoneAuth: !hasPhone() }) + const tc = app.globalData.textConfig + if (tc && tc.aiAnalysisText) { + this.setData({ aiAnalysisText: tc.aiAnalysisText }) + } + }, + + // 本页就地授权手机号 + onGetPhoneNumber(e) { + const { code, errMsg } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + if (!code && !hasPhone()) { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + return + } + if (!code && hasPhone()) { + // 已有手机号,无需重复请求 + this.setData({ needPhoneAuth: false }) + return + } + bindPhoneByCode(code).then(() => { + this.setData({ needPhoneAuth: false }) + }).catch(() => { + // 失败时保持 needPhoneAuth 为 true,等待用户重新授权 + this.setData({ needPhoneAuth: !hasPhone() }) + }) + }, + + // 选择/拍摄照片通用方法(index: 0 正面 / 1 左侧 / 2 右侧) + choosePhoto(index) { + wx.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['camera', 'album'], + success: (res) => { + const tempPath = res.tempFilePaths[0] + const photos = this.data.photos.slice() + photos[index] = tempPath + const filledCount = photos.filter(Boolean).length + const guideText = this.data.guideTexts[filledCount] || '拍摄完成' + this.setData({ photos, photoIndex: filledCount, guideText }) + // 选完图后立即上传当前这张到服务器 + this.uploadSinglePhoto(index, tempPath) + }, + fail: () => {} + }) + }, + // 单张上传到服务器,成功后记录 URL + uploadSinglePhoto(index, localPath) { + const apiBase = (app.globalData && app.globalData.apiBase) ? app.globalData.apiBase.replace(/\/$/, '') : '' + if (!apiBase) { + wx.showToast({ title: '网络未就绪,请稍后再试', icon: 'none' }) + return + } + const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' + const uploadUrl = apiBase + '/api/upload/image' + + wx.showLoading({ title: '上传中...', mask: true }) + wx.uploadFile({ + url: uploadUrl, + filePath: localPath, + name: 'file', + header: token ? { Authorization: 'Bearer ' + token } : {}, + success: (res) => { + try { + const data = JSON.parse(res.data) + if (data.code === 200 && data.data && data.data.url) { + const url = data.data.url + const uploadedUrls = this.data.uploadedUrls.slice() + uploadedUrls[index] = url + const photos = this.data.photos.slice() + photos[index] = url + this.setData({ uploadedUrls, photos }) + } else { + wx.showToast({ title: data.message || '上传失败', icon: 'none' }) + } + } catch (e) { + wx.showToast({ title: '解析上传结果失败', icon: 'none' }) + } + }, + fail: (err) => { + console.error('[upload] 单张上传失败:', err) + wx.showToast({ title: '上传失败,请重试', icon: 'none' }) + }, + complete: () => { + wx.hideLoading() + } + }) + }, + + // 三个角度对应的上传按钮 + onUploadFront() { + this.choosePhoto(0) + }, + onUploadLeft() { + this.choosePhoto(1) + }, + onUploadRight() { + this.choosePhoto(2) + }, + + // 重新拍摄全部照片 + retakeAll() { + wx.showModal({ + title: '重新拍摄', + content: '确定要重新拍摄所有照片吗?', + confirmText: '确定', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + this.setData({ + photos: ['', '', ''], + uploadedUrls: [], + photoIndex: 0, + guideText: '请正对镜头' + }) + wx.showToast({ title: '已清空,请重新拍摄', icon: 'success' }) + } + } + }) + }, + + // 完成拍照:先上传 3 张图到服务器,拿到 URL 后再跳转结果页 + completeCapture() { + if (!ensureProfileCompleteAndRedirect()) return + const urls = (this.data.uploadedUrls || []).filter(Boolean) + if (!urls.length) { + wx.showToast({ title: '请先上传至少一张照片', icon: 'none' }) + return + } + // 此时 URL 已在选择时上传完成,这里只负责保存和跳转 + wx.setStorageSync('aiPhotos', urls) + wx.navigateTo({ url: '/pages/index/result' }) + }, + + // 相机错误 + onCameraError(e) { + console.error('相机错误:', e) + wx.showModal({ + title: '相机权限', + content: '请允许使用相机权限以进行AI人脸分析', + confirmText: '去设置', + success: (res) => { + if (res.confirm) { + wx.openSetting() + } + } + }) + } +}) diff --git a/miniprogram/pages/index/upload.json b/miniprogram/pages/index/upload.json new file mode 100644 index 0000000..19388ac --- /dev/null +++ b/miniprogram/pages/index/upload.json @@ -0,0 +1,8 @@ +{ + "navigationBarTitleText": "拍照", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": { + "custom-tab-bar": "/custom-tab-bar/index" + } +} diff --git a/miniprogram/pages/index/upload.wxml b/miniprogram/pages/index/upload.wxml new file mode 100644 index 0000000..4b81bce --- /dev/null +++ b/miniprogram/pages/index/upload.wxml @@ -0,0 +1,113 @@ + + + + + + + 多角度拍摄更精准 + + 为了更准确地通过骨相与面相分析您的性格, + 请上传以下三个维度的照片。 + + + + + + + + + 1 + + + 正面 + 目视前方,五官端正,不遮挡额头 + + + + + + 📷 + 点击上传 + + + + + + + + + + 2 + + + 左侧 45度 + 展示左侧面颊,用于分析面部轮廓 + + + + + + 📷 + 点击上传 + + + + + + + + + + 3 + + + 右侧 45度 + 展示右侧面颊,完整捕捉面部信息 + + + + + + 📷 + 点击上传 + + + + + + + + + + + + 温馨提示 + + + · 光线充足,背景整洁,避免过曝或过暗 + · 保持表情自然,无需刻意微笑 + · 如有佩戴眼镜,请确保镜片不反光 + + + + + + 为保障服务与联系,请先授权手机号。 + + + + + + + + + + + + diff --git a/miniprogram/pages/index/upload.wxss b/miniprogram/pages/index/upload.wxss new file mode 100644 index 0000000..01914ff --- /dev/null +++ b/miniprogram/pages/index/upload.wxss @@ -0,0 +1,243 @@ +/* pages/index/camera.wxss - 上传照片引导页(参照设计稿) */ + +.page { + min-height: 100vh; + min-height: 100dvh; + background-color: #ffffff; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +/* 主体内容 */ +.content { + flex: 1; + /* 底部按钮作为正常内容,在 tabBar 之上留出空间 */ + padding: 40rpx 48rpx 40rpx; + box-sizing: border-box; +} + +/* 顶部说明文案 */ +.intro { + text-align: center; + margin-bottom: 40rpx; +} + +.intro-title { + font-size: 34rpx; + font-weight: 700; + color: #111827; + margin-bottom: 12rpx; +} + +.intro-desc-wrap { + display: flex; + flex-direction: column; + align-items: center; + row-gap: 4rpx; +} + +.intro-desc { + font-size: 24rpx; + color: #6b7280; + line-height: 1.7; +} + +/* 三个步骤区域 */ +.steps { + display: flex; + flex-direction: column; + row-gap: 40rpx; +} + +.step-item { + display: flex; + flex-direction: row; +} + +.step-index { + width: 60rpx; + height: 60rpx; + border-radius: 9999rpx; + border-width: 4rpx; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; +} + +.step-index-primary { + background-color: #ffe5e9; + border-color: #ff5e7d; +} + +.step-index-secondary { + background-color: #f3f4f6; + border-color: #d1d5db; +} + +.step-index-text { + font-size: 26rpx; + font-weight: 700; +} + +.step-index-text-primary { + color: #ff5e7d; +} + +.step-index-text-secondary { + color: #6b7280; +} + +.step-body { + flex: 1; +} + +.step-title { + font-size: 30rpx; + font-weight: 700; + color: #111827; + margin-bottom: 4rpx; +} + +.step-subtitle { + font-size: 22rpx; + color: #9ca3af; + margin-bottom: 16rpx; +} + +.step-upload-row { + display: flex; + flex-direction: row; + column-gap: 24rpx; +} + +.sample-image { + width: 200rpx; + height: 200rpx; + border-radius: 24rpx; + border: 2rpx solid #f3f4f6; + box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.06); + flex-shrink: 0; +} + +.upload-card { + width: 200rpx; + height: 200rpx; + overflow: hidden; + border-radius: 24rpx; + border: 4rpx dashed #fecaca; + background-color: #fff5f5; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #f97373; +} + +.upload-icon { + font-size: 40rpx; + margin-bottom: 8rpx; +} + +.upload-text { + font-size: 24rpx; +} + +.upload-preview { + width: 100%; + height: 100%; + border-radius: 20rpx; + display: block; +} + +/* 温馨提示卡片 */ +.tips-card { + margin-top: 48rpx; + padding: 24rpx 28rpx; + border-radius: 24rpx; + background-color: #fff5f5; + border: 2rpx solid #ffe0e0; +} + +.tips-title-row { + display: flex; + align-items: center; + margin-bottom: 12rpx; +} + +.tips-icon { + width: 28rpx; + height: 28rpx; + border-radius: 9999rpx; + border: 4rpx solid #e63946; + margin-right: 10rpx; +} + +.tips-title { + font-size: 26rpx; + font-weight: 700; + color: #e63946; +} + +.tips-list { + margin-top: 4rpx; +} + +.tips-item { + font-size: 24rpx; + color: #b91c1c; + line-height: 1.7; +} + +/* 底部按钮区域 */ +.footer { + padding: 24rpx 0 0; + margin: 24rpx 48rpx calc(160rpx + env(safe-area-inset-bottom)); + background-color: #ffffff; +} + +.footer .primary-btn{ + width: 100%; +} + +.phone-auth-section { + padding: 24rpx 24rpx 0; + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.phone-auth-tip { + font-size: 26rpx; + color: #4b5563; +} + +.phone-auth-btn { + height: 80rpx; + line-height: 80rpx; + border-radius: 40rpx; + background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); + color: #ffffff; + font-size: 28rpx; +} + +.phone-auth-btn::after { + border: none; +} + +.primary-btn { + width: 100%; + padding: 24rpx 0; + border-radius: 9999rpx; + border: none; + background-image: linear-gradient(90deg, #ff5e7d 0%, #ff85a1 100%); + box-shadow: 0 10rpx 25rpx rgba(248, 113, 113, 0.45); + font-size: 32rpx; + font-weight: 700; + color: #ffffff; +} + +.primary-btn::after { + border: none; +} diff --git a/miniprogram/pages/phone-auth/index.js b/miniprogram/pages/phone-auth/index.js new file mode 100644 index 0000000..886de17 --- /dev/null +++ b/miniprogram/pages/phone-auth/index.js @@ -0,0 +1,74 @@ +// 手机号授权页:用户点击按钮授权后,用 code 换手机号并写回 userInfo,再返回或跳转 next +const app = getApp() + +Page({ + data: { + next: '', + }, + + onLoad(options) { + this.setData({ + next: options.next ? decodeURIComponent(options.next) : '', + }) + }, + + onGetPhoneNumber(e) { + const { code, errMsg } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + if (!code) { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + return + } + const token = app.globalData.token || wx.getStorageSync('token') + if (!token) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + wx.showLoading({ title: '处理中...', mask: true }) + wx.request({ + url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/auth/wechat/phone`, + method: 'POST', + header: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + }, + data: { code }, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const data = res.data.data || {} + const user = data.user || app.globalData.userInfo || {} + const phone = data.phone || user.phone || '' + const newUser = { ...user, phone } + app.globalData.userInfo = newUser + wx.setStorageSync('userInfo', newUser) + wx.showToast({ title: '授权成功', icon: 'success' }) + const nextPath = this.data.next && this.data.next.startsWith('/') ? this.data.next : '' + const tabBarPaths = ['/pages/index/index', '/pages/index/camera', '/pages/profile/index'] + const isTabBar = tabBarPaths.some(p => nextPath === p || nextPath.startsWith(p + '?')) + if (nextPath) { + setTimeout(() => { + if (isTabBar) { + const pathOnly = nextPath.split('?')[0] + wx.switchTab({ url: pathOnly, fail: () => wx.navigateBack() }) + } else { + wx.redirectTo({ url: nextPath, fail: () => wx.navigateBack() }) + } + }, 500) + } else { + setTimeout(() => wx.navigateBack(), 500) + } + } else { + wx.showToast({ title: res.data && res.data.message ? res.data.message : '获取手机号失败', icon: 'none' }) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '网络请求失败', icon: 'none' }) + }, + }) + }, +}) diff --git a/miniprogram/pages/phone-auth/index.json b/miniprogram/pages/phone-auth/index.json new file mode 100644 index 0000000..4d34557 --- /dev/null +++ b/miniprogram/pages/phone-auth/index.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "授权手机号", + "usingComponents": {} +} diff --git a/miniprogram/pages/phone-auth/index.wxml b/miniprogram/pages/phone-auth/index.wxml new file mode 100644 index 0000000..eecad2b --- /dev/null +++ b/miniprogram/pages/phone-auth/index.wxml @@ -0,0 +1,5 @@ + + 为保障服务与联系需要,请授权您的手机号。 + 授权后可在「查看报告」「支付」「企业咨询」等场景使用,仅需授权一次。 + + diff --git a/miniprogram/pages/phone-auth/index.wxss b/miniprogram/pages/phone-auth/index.wxss new file mode 100644 index 0000000..2e079d3 --- /dev/null +++ b/miniprogram/pages/phone-auth/index.wxss @@ -0,0 +1,29 @@ +.container { + padding: 48rpx 32rpx; + min-height: 100vh; + box-sizing: border-box; +} +.tip { + font-size: 30rpx; + color: #333; + line-height: 1.6; + margin-bottom: 24rpx; +} +.tip.sub { + color: #666; + font-size: 26rpx; + margin-bottom: 64rpx; +} +.auth-btn { + width: 100%; + height: 88rpx; + line-height: 88rpx; + background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); + color: #fff; + border-radius: 44rpx; + font-size: 32rpx; + border: none; +} +.auth-btn::after { + border: none; +} diff --git a/miniprogram/pages/profile/index.js b/miniprogram/pages/profile/index.js new file mode 100644 index 0000000..2608062 --- /dev/null +++ b/miniprogram/pages/profile/index.js @@ -0,0 +1,318 @@ +// pages/profile/index.js - 我的页面 +const app = getApp() +const { getTypeOnly } = require('../../utils/resultFormat') +const { request } = require('../../utils/request') + +Page({ + data: { + hasLogin: false, + userInfo: null, + balance: 0, + testCount: 0, + hasResults: false, + mbtiType: '', + discType: '', + pdpType: '', + aiType: '', + mbtiTime: '', + discTime: '', + pdpTime: '', + aiTime: '', + /** 最近记录的数据库 ID,用于跳转时传参 */ + mbtiResultId: null, + discResultId: null, + pdpResultId: null, + aiResultId: null, + loginLoading: false, + loginFailed: false, + nicknameDisplay: '', + displayNickname: '', + /** 默认头像(根据昵称):无头像时显示首字+背景色 */ + avatarLetter: '登', + avatarBgColor: '#6366f1', + // 推广中心统计与配置(来自 /api/distribution/stats) + promoDistributionEnabled: true, + promoCenterTitle: '推广中心', + promoTotalInvite: 0, + promoTotalEarned: '0.00', + promoWithdrawable: '0.00', + /** 是否有企业权限(绑定企业):有则显示「我的简历」 */ + hasEnterprise: false + }, + + onLoad() { + // 仅在 onLoad 执行一次,onShow 里的 runLoginThenLoad 会导致重复请求 + this.runLoginThenLoad() + }, + onShow() { + // 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程 + if (this.data.hasLogin) { + this.loadData() + } + + if (typeof this.getTabBar === 'function' && this.getTabBar()) { + this.getTabBar().setData({ selected: 2 }) + } + }, + + /** 先确保登录完成(静默登录),再刷新页面数据 */ + runLoginThenLoad() { + this.setData({ loginLoading: true, loginFailed: false }) + app.ensureLogin().then((ok) => { + this.setData({ loginLoading: false, loginFailed: !ok }) + this.loadData() + }).catch(() => { + this.setData({ loginLoading: false, loginFailed: true }) + this.loadData() + }) + }, + + /** 生成随机后缀:仅 26 英文字母(大小写)与数字,不含特殊字符,默认 4 位 */ + _randomWechatUserSuffix(len) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let s = '' + for (let i = 0; i < (len || 4); i++) { + s += chars[Math.floor(Math.random() * chars.length)] + } + return s + }, + + /** 根据昵称生成默认头像的首字与背景色(同一昵称同色) */ + _avatarFromNickname(name) { + const str = (name && String(name).trim()) || '登' + const letter = str.charAt(0).toUpperCase() || '登' + const palette = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#14b8a6', '#0ea5e9', '#3b82f6', '#eab308'] + let hash = 0 + for (let i = 0; i < str.length; i++) hash += str.charCodeAt(i) + const bgColor = palette[Math.abs(hash) % palette.length] + return { avatarLetter: letter, avatarBgColor: bgColor } + }, + + /** 获取或生成当前用户的「微信用户XXXX」后缀(同一用户固定,仅 26 字母+数字) */ + _getOrCreateWechatUserSuffix(userInfo) { + const uid = (userInfo && (userInfo.id || userInfo.userId)) ? String(userInfo.id || userInfo.userId) : '_default' + const storageKey = 'wechat_user_suffix' + const stored = wx.getStorageSync(storageKey) + if (stored && typeof stored === 'object' && stored[uid]) return stored[uid] + const suffix = this._randomWechatUserSuffix(4) + const next = { ...(stored && typeof stored === 'object' ? stored : {}), [uid]: suffix } + wx.setStorageSync(storageKey, next) + return suffix + }, + + loadData() { + const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') + const token = app.globalData.token || wx.getStorageSync('token') + + // 1. 先同步渲染用户基础信息(昵称/头像) + const nickname = (userInfo && (userInfo.nickname || userInfo.nickName)) + ? String(userInfo.nickname || userInfo.nickName).trim() : '' + let nicknameDisplay = nickname + let displayNickname = '' + if (!nickname && userInfo) { + const suffix = this._getOrCreateWechatUserSuffix(userInfo) + displayNickname = '微信用户' + suffix + nicknameDisplay = displayNickname + } + const avatarFromName = userInfo + ? this._avatarFromNickname(nicknameDisplay) + : this._avatarFromNickname('点击登录') + + const hasEnterprise = !!(userInfo && (userInfo.hasEnterprise === true || (userInfo.enterpriseId && Number(userInfo.enterpriseId) > 0))) + this.setData({ + hasLogin: !!token || !!userInfo, + userInfo: userInfo || null, + hasEnterprise, + nicknameDisplay, + displayNickname, + avatarLetter: avatarFromName.avatarLetter, + avatarBgColor: avatarFromName.avatarBgColor + }) + + // 2. 已登录则从服务端拉取最近记录;失败降级读 localStorage + if (token || userInfo) { + this._loadRecentFromAPI() + this._loadPromoStats() + } + }, + + /** 从 /api/test/recent 拉取各类型最新记录 */ + _loadRecentFromAPI() { + const scope = app.globalData.appScope || 'personal' + request({ + url: `/api/test/recent?scope=${scope}`, + method: 'GET', + success: (res) => { + // res 是 wx.request 原始响应:{ statusCode, data: { code, data, message } } + const payload = res && res.data + if (!payload || payload.code !== 200 || !payload.data) { + this._loadRecentFromStorage() + return + } + const { records = {}, totalCount = 0 } = payload.data + const r = records + + // DISC resultText 后端已含「型」,type badge 只显示字母,去掉「型」 + const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : '' + + this.setData({ + testCount: totalCount, + hasResults: !!(r.mbti || r.disc || r.pdp || r.ai), + mbtiType: r.mbti ? r.mbti.resultText : '', + discType, + pdpType: r.pdp ? r.pdp.resultText : '', + aiType: r.ai ? r.ai.resultText : '', + mbtiTime: r.mbti ? r.mbti.testTime : '', + discTime: r.disc ? r.disc.testTime : '', + pdpTime: r.pdp ? r.pdp.testTime : '', + aiTime: r.ai ? r.ai.testTime : '', + mbtiResultId: r.mbti ? r.mbti.id : null, + discResultId: r.disc ? r.disc.id : null, + pdpResultId: r.pdp ? r.pdp.id : null, + aiResultId: r.ai ? r.ai.id : null, + }) + }, + fail: () => { + this._loadRecentFromStorage() + } + }) + }, + + /** 从 /api/distribution/stats 拉取推广中心统计 */ + _loadPromoStats() { + request({ + url: '/api/distribution/stats', + method: 'GET', + success: (res) => { + const payload = res && res.data + if (payload && payload.code === 200 && payload.data) { + const d = payload.data + this.setData({ + promoDistributionEnabled: d.distributionEnabled !== false, + promoCenterTitle: d.promoCenterTitle || '推广中心', + promoTotalInvite: d.totalInvite || 0, + promoTotalEarned: d.totalEarned || '0.00', + promoWithdrawable: d.walletBalance || '0.00' + }) + } + } + }) + }, + + /** 降级:从 localStorage 读最近记录(兼容离线或 API 失败) */ + _loadRecentFromStorage() { + const mbtiResult = wx.getStorageSync('mbtiResult') + const discResult = wx.getStorageSync('discResult') + const pdpResult = wx.getStorageSync('pdpResult') + const aiResult = wx.getStorageSync('aiResult') + + let testCount = 0 + if (mbtiResult) testCount++ + if (discResult) testCount++ + if (pdpResult) testCount++ + if (aiResult) testCount++ + + const _fmt = (ts) => { + if (!ts) return '' + const d = new Date(typeof ts === 'number' && ts < 1e12 ? ts * 1000 : ts) + if (isNaN(d.getTime())) return '' + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` + } + + this.setData({ + testCount, + hasResults: testCount > 0, + mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '', + discType: discResult ? getTypeOnly(discResult, 'disc') : '', + pdpType: pdpResult ? getTypeOnly(pdpResult, 'pdp') : '', + aiType: aiResult ? (aiResult.mbtiType || aiResult.type || '') : '', + mbtiTime: _fmt(mbtiResult && (mbtiResult.createdAt || mbtiResult.timestamp || mbtiResult.testTime)), + discTime: _fmt(discResult && (discResult.createdAt || discResult.timestamp || discResult.testTime)), + pdpTime: _fmt(pdpResult && (pdpResult.createdAt || pdpResult.timestamp || pdpResult.testTime)), + aiTime: _fmt(aiResult && (aiResult.createdAt || aiResult.timestamp || aiResult.testTime)), + mbtiResultId: null, + discResultId: null, + pdpResultId: null, + aiResultId: null, + }) + }, + + /** 点击登录:先静默登录拿到 token */ + doLogin() { + this.setData({ loginLoading: true, loginFailed: false }) + app.ensureLogin().then((ok) => { + this.setData({ loginLoading: false }) + if (ok) { + this.loadData() + } else { + wx.showToast({ title: '登录失败,请检查网络或稍后重试', icon: 'none' }) + this.setData({ loginFailed: true }) + } + }).catch(() => { + this.setData({ loginLoading: false, loginFailed: true }) + wx.showToast({ title: '登录失败', icon: 'none' }) + }) + }, + + goToIndex() { wx.switchTab({ url: '/pages/index/index' }) }, + goToCamera() { wx.switchTab({ url: '/pages/index/camera' }) }, + goToHistory() { wx.navigateTo({ url: '/pages/history/index' }) }, + goToUserProfile() { wx.navigateTo({ url: '/pages/user-profile/index' }) }, + goToPurchase() { wx.navigateTo({ url: '/pages/purchase/index?tab=personal' }) }, + goToPurchasePersonal() { wx.navigateTo({ url: '/pages/purchase/index?tab=personal' }) }, + goToPurchaseEnterprise() { wx.navigateTo({ url: '/pages/purchase/index?tab=enterprise' }) }, + goToEnterprise() { wx.navigateTo({ url: '/pages/enterprise/index' }) }, + goToPromo() { wx.navigateTo({ url: '/pages/promo/index' }) }, + goToMyResume() { wx.navigateTo({ url: '/pages/enterprise/resume-history' }) }, + goToSettings() { + wx.showToast({ title: '开发中', icon: 'none' }) + }, + shareApp() { + // 触发分享 + }, + viewMBTI() { + const id = this.data.mbtiResultId + wx.navigateTo({ url: id ? `/pages/result/mbti?id=${id}&type=mbti` : '/pages/result/mbti' }) + }, + viewDISC() { + const id = this.data.discResultId + wx.navigateTo({ url: id ? `/pages/result/disc?id=${id}&type=disc` : '/pages/result/disc' }) + }, + viewPDP() { + const id = this.data.pdpResultId + wx.navigateTo({ url: id ? `/pages/result/pdp?id=${id}&type=pdp` : '/pages/result/pdp' }) + }, + viewAI() { + const id = this.data.aiResultId + wx.navigateTo({ url: id ? `/pages/index/result?id=${id}&type=ai` : '/pages/index/result' }) + }, + + logout() { + wx.showModal({ + title: '确认退出', + content: '退出后测试记录仍会保留', + success: (res) => { + if (res.confirm) { + app.logout() + this.setData({ hasLogin: false, userInfo: null, loginFailed: false }) + } + } + }) + }, + + onShareAppMessage() { + const { getSharePathByScope } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 - 发现你的MBTI类型', + path: getSharePathByScope('/pages/index/index') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 - 发现你的MBTI类型', + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/profile/index.json b/miniprogram/pages/profile/index.json new file mode 100644 index 0000000..0d7731d --- /dev/null +++ b/miniprogram/pages/profile/index.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "", + "navigationBarBackgroundColor": "#F9FAFB", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/profile/index.wxml b/miniprogram/pages/profile/index.wxml new file mode 100644 index 0000000..89ef911 --- /dev/null +++ b/miniprogram/pages/profile/index.wxml @@ -0,0 +1,212 @@ + + + + + + 我的 + + + + + + + + + + {{avatarLetter}} + + + + 登录中... + 登录失败,点击重试 + 点击登录 + 登录后查看你的测试结果 + + + + + + + + + + + + {{avatarLetter}} + + + + + + + + {{nicknameDisplay || '点击设置昵称'}} + + + + + {{mbtiType}} + + + {{discType}}型 + + + {{pdpType}} + + + 暂无测试记录 + + + + + + + + + + + + 最新测试 + + 查看全部 + + + + + + + + + 🧠 + + MBTI性格 + {{mbtiType}} + {{mbtiTime}} + + + + + + 📊 + + DISC测评 + {{discType}}型 + {{discTime}} + + + + + + 🦁 + + PDP行为 + {{pdpType}} + {{pdpTime}} + + + + + + 👁️ + + 面相分析 + {{aiType}} + {{aiTime}} + + + + + + + + + + + 📈 + {{promoCenterTitle}} + + + + + 邀请好友 + {{promoTotalInvite}} + + + + 累计收益 + ¥{{promoTotalEarned}} + + + + 可提现 + ¥{{promoWithdrawable}} + + + + + + + + 深度服务 + + + + + 📄 + + + 专业报告 + 解锁完整的深度性格解析 + + + + + + + + + 🏢 + + + 企业版服务 + 团队测评与人才管理方案 + + + + + + + + + 🕒 + + + 测试历史 + {{testCount > 0 ? testCount + '条记录' : '查看过往所有测试记录'}} + + + + + + + + 📋 + + + 我的简历 + 查看与设置默认简历 + + + + + + + + + + diff --git a/miniprogram/pages/profile/index.wxss b/miniprogram/pages/profile/index.wxss new file mode 100644 index 0000000..252f93b --- /dev/null +++ b/miniprogram/pages/profile/index.wxss @@ -0,0 +1,534 @@ +/* pages/profile/index.wxss */ +page { + background-color: #F9FAFB; +} + +.page { + min-height: 100vh; + background-color: #F9FAFB; + padding-bottom: 140rpx; +} + +/* ===== 顶部栏 ===== */ +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16rpx 40rpx 24rpx; + background: #F9FAFB; +} + +.topbar-title { + font-size: 48rpx; + font-weight: 700; + color: #111827; + letter-spacing: -0.5rpx; +} + +.topbar-actions { + display: flex; + gap: 12rpx; + align-items: center; +} + +.settings-btn { + width: 72rpx; + height: 72rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.settings-icon { + font-size: 44rpx; + color: #4B5563; +} + +.icon-btn { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + border: none; +} + +.icon-btn::after { + border: none; +} + +.icon-text { + font-size: 36rpx; +} + +/* ===== 用户卡片 ===== */ +.user-card { + margin: 0 32rpx 28rpx; + background: #FFFFFF; + border-radius: 28rpx; + padding: 36rpx 32rpx; + display: flex; + align-items: center; + gap: 20rpx; + box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05); + position: relative; +} + +.user-card:active { + opacity: 0.9; +} + +/* 头像区域(已登录,仅展示) */ +.avatar-wrap { + position: relative; + width: 144rpx; + height: 144rpx; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.avatar-ring { + width: 144rpx; + height: 144rpx; + border-radius: 50%; + padding: 4rpx; + background: linear-gradient(135deg, #FFDEE9 0%, #B5FFFC 100%); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + box-shadow: inset 0 2rpx 10rpx rgba(0,0,0,0.05); + box-sizing: border-box; +} + +.avatar-img { + width: 128rpx; + height: 128rpx; + border-radius: 50%; + border: 4rpx solid #FFFFFF; + display: block; +} + +.avatar-letter-wrap { + width: 128rpx; + height: 128rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4rpx solid #FFFFFF; +} + +.avatar-letter { + font-size: 64rpx; + font-weight: 700; + color: #FFFFFF; +} + +.online-dot { + position: absolute; + bottom: 8rpx; + right: 8rpx; + width: 28rpx; + height: 28rpx; + background: #22C55E; + border-radius: 50%; + border: 4rpx solid #FFFFFF; + z-index: 10; + pointer-events: none; +} + +/* 用户信息 */ +.user-meta { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; + gap: 8rpx; + padding-left: 12rpx; +} + +.user-name { + font-size: 36rpx; + font-weight: 700; + color: #111827; + display: block; +} + +.user-sub { + font-size: 24rpx; + color: #9CA3AF; +} + +.nickname-text { + font-size: 36rpx; + font-weight: 700; + color: #111827; + line-height: 1.3; + display: block; +} + +/* 类型标签 */ +.tags-scroll { + width: 100%; + white-space: nowrap; +} + +.tags-row { + display: flex; + gap: 10rpx; + flex-wrap: nowrap; +} + +.tag { + display: inline-flex; + align-items: center; + padding: 6rpx 16rpx; + border-radius: 10rpx; + flex-shrink: 0; +} + +.tag-text { + font-size: 20rpx; + font-weight: 600; + white-space: nowrap; +} + +.tag-purple { + background: #EDE9FE; +} +.tag-purple .tag-text { + color: #7C3AED; +} + +.tag-blue { + background: #DBEAFE; +} +.tag-blue .tag-text { + color: #2563EB; +} + +.tag-orange { + background: #FEF3C7; +} +.tag-orange .tag-text { + color: #D97706; +} + +.tag-gray { + background: #F3F4F6; +} +.tag-gray .tag-text { + color: #9CA3AF; +} + +.chevron { + font-size: 48rpx; + color: #D1D5DB; + flex-shrink: 0; +} + +/* ===== Section ===== */ +.section { + margin-bottom: 28rpx; +} + +.px-section { + padding: 0 32rpx; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 32rpx; + margin-bottom: 20rpx; +} + +.section-title { + font-size: 32rpx; + font-weight: 700; + color: #1F2937; + display: block; + margin-bottom: 20rpx; +} + +.section-header .section-title { + margin-bottom: 0; +} + +.section-link { + display: flex; + align-items: center; +} + +.section-link-text { + font-size: 24rpx; + color: #F43F5E; + font-weight: 500; +} + +/* ===== 测试结果卡片(横向滚动) ===== */ +.cards-scroll { + width: 100%; + white-space: nowrap; +} + +.cards-row { + display: flex; + gap: 24rpx; + padding: 8rpx 32rpx 16rpx; + width: max-content; +} + +.result-card { + width: 280rpx; + height: 240rpx; + border-radius: 24rpx; + padding: 32rpx 28rpx 24rpx; + display: flex; + flex-direction: column; + justify-content: space-between; + position: relative; + overflow: hidden; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06); + border: 1rpx solid rgba(0,0,0,0.04); + background: #FFFFFF; + flex-shrink: 0; +} + +.card-deco { + position: absolute; + top: -24rpx; + right: -24rpx; + width: 120rpx; + height: 120rpx; + border-radius: 50%; + opacity: 0.3; +} + +.card-purple .card-deco { background: #DDD6FE; } +.card-blue .card-deco { background: #BFDBFE; } +.card-orange .card-deco { background: #FDE68A; } +.card-rose .card-deco { background: #FECDD3; } + +.card-icon-wrap { + width: 64rpx; + height: 64rpx; + border-radius: 16rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.card-icon-purple { background: #EDE9FE; } +.card-icon-blue { background: #DBEAFE; } +.card-icon-orange { background: #FEF3C7; } +.card-icon-rose { background: #FFE4E6; } + +.card-icon { + font-size: 32rpx; +} + +.card-label { + font-size: 22rpx; + color: #9CA3AF; + font-weight: 500; +} + +.card-value { + font-size: 44rpx; + font-weight: 800; + letter-spacing: -1rpx; +} + +.card-purple .card-value { color: #7C3AED; } +.card-blue .card-value { color: #2563EB; } +.card-orange .card-value { color: #D97706; } +.card-rose .card-value { color: #E11D48; } + +.card-time { + font-size: 20rpx; + color: #D1D5DB; +} + +.card-time { + font-size: 20rpx; + color: #D1D5DB; +} + +/* ===== 推广中心 ===== */ +.promo-card { + background: #FFFFFF; + border-radius: 32rpx; + padding: 40rpx; + box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05); + border: 1rpx solid rgba(0,0,0,0.04); +} + +.promo-header { + display: flex; + align-items: center; + margin-bottom: 40rpx; +} + +.promo-title-wrap { + display: flex; + align-items: center; + gap: 12rpx; +} + +.promo-icon { + font-size: 32rpx; +} + +.promo-title { + font-size: 32rpx; + font-weight: 700; + color: #1F2937; +} + +.promo-stats { + display: flex; + justify-content: space-between; + align-items: center; +} + +.promo-stat-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 8rpx; +} + +.promo-stat-label { + font-size: 24rpx; + color: #9CA3AF; +} + +.promo-stat-value { + font-size: 36rpx; + font-weight: 700; + color: #111827; + font-family: "DIN Alternate", "Courier New", Courier, monospace; +} + +.promo-stat-divider { + width: 1rpx; + height: 60rpx; + background: #F3F4F6; +} + +.promo-highlight { + color: #F43F5E; +} + +/* ===== 服务菜单 ===== */ +.menu-card { + background: #FFFFFF; + border-radius: 24rpx; + overflow: hidden; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.05); + border: 1rpx solid rgba(0,0,0,0.04); +} + +.menu-item { + display: flex; + align-items: center; + padding: 32rpx 36rpx; + gap: 28rpx; + position: relative; +} + +.menu-item:active { + background: #F9FAFB; +} + +.menu-divider { + height: 1rpx; + background: #F3F4F6; + margin: 0 36rpx; +} + +.menu-icon-wrap { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.menu-icon-red { background: #FEF2F2; } +.menu-icon-indigo { background: #EEF2FF; } +.menu-icon-amber { background: #FFFBEB; } +.menu-icon-purple { background: #EDE9FE; } + +.menu-icon { + font-size: 36rpx; +} + +.menu-content { + flex: 1; + min-width: 0; +} + +.menu-title { + font-size: 30rpx; + font-weight: 600; + color: #111827; + display: block; + margin-bottom: 6rpx; +} + +.menu-sub { + font-size: 24rpx; + color: #9CA3AF; + display: block; +} + +.menu-chevron { + font-size: 48rpx; + color: #D1D5DB; + flex-shrink: 0; +} + +/* ===== 退出登录按钮 ===== */ +.logout-btn { + width: 100%; + padding: 32rpx; + background: #FFF1F2; + border: 1rpx solid #FFE4E6; + border-radius: 24rpx; + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; + box-sizing: border-box; +} + +.logout-btn:active { + opacity: 0.8; +} + +.logout-icon { + font-size: 32rpx; +} + +.logout-text { + font-size: 30rpx; + font-weight: 600; + color: #EF4444; +} + +.bottom-safe { + height: 40rpx; +} diff --git a/miniprogram/pages/promo/index.js b/miniprogram/pages/promo/index.js new file mode 100644 index 0000000..d0d120e --- /dev/null +++ b/miniprogram/pages/promo/index.js @@ -0,0 +1,270 @@ +// pages/promo/index.js +const app = getApp() +const { request } = require('../../utils/request') + +Page({ + data: { + balance: '0.00', + totalEarned: '0.00', + pendingAmount: '0.00', + bindingCount: 0, + paidCount: 0, + expiringCount: 0, + activeTab: 0, + userList: [], + listTotal: 0, + listPage: 1, + listLoading: false, + listFinished: false, + commissionRate: '', + bindingDays: '', + testCommissionType: '', + testCommissionRate: '', + testCommissionAmount: '', + testNoPayment: false, + withdrawMinYuan: '1.00', + withdrawMaxYuan: '', + withdrawFeePct: 0, + requireWithdrawAudit: true, + showWithdrawDialog: false, + withdrawAmountInput: '', + withdrawError: '', + withdrawFeeYuan: '0.00', + withdrawActualYuan: '0.00' + }, + + onLoad() { + this.loadStats() + this.loadBindings(true) + }, + + onShow() { + this.loadStats() + }, + + /** 加载推广统计数据 */ + loadStats() { + request({ + url: '/api/distribution/stats', + method: 'GET', + success: (res) => { + const payload = res && res.data + if (payload && payload.code === 200 && payload.data) { + const d = payload.data + const title = d.promoCenterTitle || '推广中心' + wx.setNavigationBarTitle({ title }) + this.setData({ + balance: d.walletBalance, + totalEarned: d.totalEarned, + pendingAmount: d.pendingAmount, + bindingCount: d.bindingCount, + paidCount: d.paidCount, + expiringCount: d.expiringCount, + totalInvite: d.totalInvite, + commissionRate: d.commissionRate, + bindingDays: d.bindingDays, + testCommissionType: d.testCommissionType, + testCommissionRate: d.testCommissionRate, + testCommissionAmount: d.testCommissionAmount, + testNoPayment: d.testNoPayment, + withdrawMinYuan: d.withdrawMinYuan != null ? d.withdrawMinYuan : '1.00', + withdrawMaxYuan: d.withdrawMaxYuan != null && d.withdrawMaxYuan !== '' ? d.withdrawMaxYuan : '', + withdrawFeePct: d.withdrawFeePct != null ? d.withdrawFeePct : 0, + requireWithdrawAudit: d.requireWithdrawAudit !== false, + }) + } + } + }) + }, + + /** 加载绑定用户列表(tab: 0=绑定中 1=已付款 2=已过期) */ + loadBindings(reset = false) { + if (this.data.listLoading) return + if (!reset && this.data.listFinished) return + + const page = reset ? 1 : this.data.listPage + this.setData({ listLoading: true }) + + request({ + url: `/api/distribution/bindings?tab=${this.data.activeTab}&page=${page}&pageSize=10`, + method: 'GET', + success: (res) => { + const payload = res && res.data + if (payload && payload.code === 200 && payload.data) { + const { list, total } = payload.data + const formatted = (list || []).map(item => ({ + ...item, + createdAtStr: item.createdAt ? this._fmtTimestamp(item.createdAt) : '' + })) + const newList = reset ? formatted : [...this.data.userList, ...formatted] + this.setData({ + userList: newList, + listTotal: total, + listPage: page + 1, + listFinished: newList.length >= total, + }) + } + }, + complete: () => { + this.setData({ listLoading: false }) + } + }) + }, + + /** 时间戳格式化为 YYYY-MM-DD */ + _fmtTimestamp(ts) { + if (!ts) return '' + const d = new Date(ts * 1000) + if (isNaN(d.getTime())) return '' + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + return `${y}-${m}-${day}` + }, + + /** 切换用户列表 Tab */ + switchTab(e) { + const index = parseInt(e.currentTarget.dataset.index) + if (index === this.data.activeTab) return + this.setData({ activeTab: index, userList: [], listPage: 1, listFinished: false }) + this.loadBindings(true) + }, + + /** 上拉加载更多 */ + onReachBottom() { + this.loadBindings(false) + }, + + /** 申请提现:打开自定义金额弹框 */ + handleWithdraw() { + const balance = parseFloat(this.data.balance) + if (balance < 1) { + wx.showToast({ title: '余额不足1元,暂无法提现', icon: 'none' }) + return + } + const pct = this.data.withdrawFeePct || 0 + const feeYuan = (balance * pct / 100).toFixed(2) + const actualYuan = (balance - balance * pct / 100).toFixed(2) + this.setData({ + showWithdrawDialog: true, + withdrawAmountInput: this.data.balance, + withdrawFeeYuan: feeYuan, + withdrawActualYuan: actualYuan, + withdrawError: '' + }) + }, + + /** 关闭提现弹框 */ + closeWithdrawDialog() { + this.setData({ + showWithdrawDialog: false, + withdrawError: '' + }) + }, + + /** 输入金额:实时计算手续费与实际到账 */ + onWithdrawInput(e) { + const raw = e.detail.value + const val = parseFloat(raw) + const pct = this.data.withdrawFeePct || 0 + let feeYuan = '0.00' + let actualYuan = '0.00' + if (raw !== '' && !isNaN(val) && val >= 0) { + const fee = val * pct / 100 + feeYuan = fee.toFixed(2) + actualYuan = (val - fee).toFixed(2) + } + this.setData({ + withdrawAmountInput: raw, + withdrawFeeYuan: feeYuan, + withdrawActualYuan: actualYuan, + withdrawError: '' + }) + }, + + /** 确认提现(使用用户填写的金额) */ + confirmWithdraw() { + const balance = parseFloat(this.data.balance) + const val = parseFloat(this.data.withdrawAmountInput) + const minYuan = parseFloat(this.data.withdrawMinYuan) || 1 + const pct = this.data.withdrawFeePct || 0 + + if (isNaN(val)) { + this.setData({ withdrawError: '请输入正确的金额' }) + return + } + if (val < 1) { + this.setData({ withdrawError: '单次提现金额至少 1 元' }) + return + } + if (val > balance) { + this.setData({ withdrawError: '不可超过当前可提现金额' }) + return + } + const actualYuan = val - val * pct / 100 + if (actualYuan < minYuan) { + this.setData({ withdrawError: `实际到账金额不得低于最低提现金额 ¥${minYuan.toFixed(2)}` }) + return + } + + const amountFen = Math.floor(val * 100) + this.setData({ withdrawError: '' }) + + request({ + url: '/api/distribution/withdraw', + method: 'POST', + data: { amountFen }, + success: (r) => { + const payload = r && r.data + if (payload && payload.code === 200) { + const msg = payload.msg || payload.message || '' + wx.showToast({ title: '申请已提交', icon: 'success' }) + this.setData({ showWithdrawDialog: false }) + this.loadStats() + // 免审核且已自动发起微信转账时,自动进入提现记录页 + if (msg.indexOf('已自动发起') !== -1) { + setTimeout(() => { + wx.navigateTo({ url: '/pages/promo/withdrawals' }) + }, 500) + } + } else { + const errMsg = (payload && (payload.msg || payload.message)) || '申请失败,请稍后重试' + this.setData({ withdrawError: errMsg }) + } + } + }) + }, + + /** 查看提现记录 */ + goToWithdrawHistory() { + wx.navigateTo({ url: '/pages/promo/withdrawals' }) + }, + + /** 生成海报 */ + generatePoster() { + wx.navigateTo({ url: '/pages/promo/poster' }) + }, + + /** 分享到朋友圈:引导用户使用右上角菜单 */ + shareToTimeline() { + wx.showToast({ title: '请点击右上角 ··· 选择「分享到朋友圈」', icon: 'none', duration: 2500 }) + }, + + /** 分享给好友 */ + onShareAppMessage() { + const { getSharePathByScope } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 - 发现你的内在潜能', + path: getSharePathByScope('/pages/index/index') + } + }, + + /** 分享到朋友圈 */ + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 - 发现你的内在潜能', + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/promo/index.json b/miniprogram/pages/promo/index.json new file mode 100644 index 0000000..1996801 --- /dev/null +++ b/miniprogram/pages/promo/index.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "推广中心", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" +} \ No newline at end of file diff --git a/miniprogram/pages/promo/index.wxml b/miniprogram/pages/promo/index.wxml new file mode 100644 index 0000000..edc9f4d --- /dev/null +++ b/miniprogram/pages/promo/index.wxml @@ -0,0 +1,219 @@ + + + + + + + + 🧧 + + + 可提现金额 + + + {{commissionRate}}% 高额返利 + + + + + ¥{{balance}} + 累计: ¥{{totalEarned}} | 待审核: ¥{{pendingAmount}} + + + + + + + 查看提现记录 + + + + + + + + 申请提现 + 可提现 ¥{{balance}},请输入本次提现金额 + + + ¥ + + + + 本次最高可提 ¥{{balance}} + + + 手续费 ¥{{withdrawFeeYuan}} + 实际到账 ¥{{withdrawActualYuan}} + + + {{withdrawError}} + + + + 取消 + 确定 + + + + + + + + {{bindingCount}} + 绑定中 + + + {{paidCount}} + 已付款 + + + {{expiringCount}} + 即将过期 + + + {{totalInvite}} + 总邀请 + + + + + + + + 🛡️ + + 推广收益规则 + + + + + 用户绑定有效期为 {{bindingDays}}天,期满自动解除 + + + 💰 + 单笔提现:最低 ¥{{withdrawMinYuan}} + 最高 ¥{{withdrawMaxYuan}} + 不设上限 + + + 📋 + 提现手续费:{{withdrawFeePct}}% + + + + + + + + + 👥 + 绑定用户 + ({{totalInvite}}) + + + + + + + 绑定中 ({{bindingCount}}) + + + 已付款 ({{paidCount}}) + + + 已过期 ({{expiringCount}}) + + + + + + + 🔍 + + 目前还没有绑定的用户哦 + 快去分享链接邀请好友吧 + + + + + + + + + {{item.nickname ? item.nickname[0] : '?'}} + + + + + + {{item.remainDays}}天到期 + 已过期 + 已付款 + + + + + + 加载中... + + + — 已加载全部 — + + + + + + + + + 🎨 + + + 生成推广海报 + 一键生成您的专属精美海报 + + + + + + 💬 + + + 分享到朋友圈 + 通过右上角菜单分享到朋友圈 + + + + + + + + + 分享专属链接,好友通过链接点击后自动绑定 + 购买任意测评即可获得 90% 的现金返利 + + + + diff --git a/miniprogram/pages/promo/index.wxss b/miniprogram/pages/promo/index.wxss new file mode 100644 index 0000000..66fb8be --- /dev/null +++ b/miniprogram/pages/promo/index.wxss @@ -0,0 +1,550 @@ +/* pages/promo/index.wxss */ +page { + background-color: #FFFFFF; +} + +.container { + padding: 32rpx; + background-color: #FFFFFF; +} + +/* ===== 顶部统计卡片 ===== */ +.hero-card { + background: #FFF5F6; + border-radius: 40rpx; + padding: 40rpx; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.02); + position: relative; + overflow: hidden; + margin-bottom: 40rpx; +} + +.hero-header-row { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 48rpx; +} + +.hero-left { + display: flex; + align-items: center; + gap: 20rpx; +} + +.wallet-icon { + width: 88rpx; + height: 88rpx; + background: #FFFFFF; + border-radius: 24rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.03); +} + +.emoji { font-size: 44rpx; } + +.title-text .label { + font-size: 24rpx; + color: #6B7280; + font-weight: 500; +} + +.badge { + display: flex; + align-items: center; + gap: 6rpx; + margin-top: 6rpx; +} + +.badge .dot { + width: 10rpx; + height: 10rpx; + background: #F43F5E; + border-radius: 50%; +} + +.badge-text { + font-size: 22rpx; + color: #F43F5E; + font-weight: 600; +} + +.hero-right { + text-align: right; +} + +.amount { + font-size: 72rpx; + font-weight: 700; + color: #111827; + line-height: 1; +} + +.amount-sub { + font-size: 22rpx; + color: #9CA3AF; + margin-top: 16rpx; + display: block; +} + +.withdraw-btn { + width: 100%; + height: 104rpx; + background: #F3F4F6; + color: #9CA3AF; + border-radius: 52rpx; + font-size: 30rpx; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 32rpx; + border: none; +} + +.withdraw-btn::after { border: none; } + +.withdraw-btn:not(.disabled) { + background: #F43F5E; + color: #FFFFFF; +} + +.record-link { + display: flex; + align-items: center; + justify-content: center; + gap: 6rpx; + font-size: 26rpx; + color: #F43F5E; + font-weight: 600; +} + +/* ===== 四格统计 ===== */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 24rpx; + margin-bottom: 40rpx; +} + +.stat-item { + background: #FFFFFF; + padding: 28rpx 16rpx; + border-radius: 32rpx; + border: 1rpx solid #F3F4F6; + text-align: center; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04); +} + +.stat-val { + font-size: 40rpx; + font-weight: 800; + color: #111827; + display: block; + margin-bottom: 4rpx; +} + +.stat-val.highlight { color: #F59E0B; } + +.stat-label { + font-size: 22rpx; + color: #6B7280; + font-weight: 500; +} + +/* ===== 通用 Section ===== */ +.section { + background: #FFFFFF; + border-radius: 48rpx; + border: 1rpx solid #F3F4F6; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04); + margin-bottom: 40rpx; + overflow: hidden; +} + +.section-header { + padding: 40rpx; + display: flex; + align-items: center; + gap: 16rpx; +} + +.section-header.border-b { + border-bottom: 1rpx solid #F9FAFB; +} + +.header-icon { + width: 56rpx; + height: 56rpx; + border-radius: 16rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.header-icon.red { background: #FFF1F2; } + +.section-title { + font-size: 28rpx; + font-weight: 800; + color: #111827; +} + +.count-label { + font-size: 24rpx; + color: #9CA3AF; + font-weight: 400; + margin-left: 8rpx; +} + +/* 规则列表 */ +.rule-list { + padding: 0 40rpx 40rpx; + display: flex; + flex-direction: column; + gap: 28rpx; +} + +.rule-item { + display: flex; + gap: 24rpx; + align-items: flex-start; +} + +.rule-bullet { + font-size: 20rpx; + margin-top: 4rpx; +} + +.rule-text { + font-size: 24rpx; + color: #6B7280; + line-height: 1.5; +} + +.highlight { color: #F43F5E; font-weight: 700; } + +/* 用户列表 Tab */ +.tab-bar { + display: flex; + background: #F9FAFB; + margin: 16rpx 32rpx; + padding: 8rpx; + border-radius: 24rpx; +} + +.tab-item { + flex: 1; + text-align: center; + padding: 20rpx 0; + font-size: 24rpx; + color: #6B7280; + font-weight: 500; + border-radius: 16rpx; + transition: all 0.2s; +} + +.tab-item.active { + background: #FFFFFF; + color: #F43F5E; + font-weight: 800; + box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05); +} + +.empty-state { + padding: 100rpx 80rpx; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.empty-icon { + width: 160rpx; + height: 160rpx; + background: linear-gradient(to top right, #F9FAFB, #FFFFFF); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 32rpx; + box-shadow: inset 0 2rpx 10rpx rgba(0,0,0,0.02); +} + +.empty-text { + font-size: 28rpx; + color: #9CA3AF; + font-weight: 600; +} + +.empty-sub { + font-size: 20rpx; + color: #D1D5DB; + margin-top: 8rpx; +} + +/* ===== 操作菜单 ===== */ +.menu-card { + background: #FFFFFF; + border-radius: 48rpx; + border: 1rpx solid #F3F4F6; + box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04); + overflow: hidden; + margin-bottom: 40rpx; +} + +.menu-item { + display: flex; + align-items: center; + padding: 32rpx 40rpx; + border-bottom: 1rpx solid #F9FAFB; + background: #FFFFFF; + text-align: left; + line-height: 1.4; +} + +.menu-item:last-child { border-bottom: none; } + +.menu-item.share-btn { + width: 100%; + margin: 0; + border-radius: 0; +} + +.menu-item.share-btn::after { border: none; } + +.menu-icon-wrap { + width: 88rpx; + height: 88rpx; + border-radius: 32rpx; + display: flex; + align-items: center; + justify-content: center; + margin-right: 32rpx; +} + +.menu-icon-wrap.rose { background: #FFF1F2; color: #F43F5E; } +.menu-icon-wrap.emerald { background: #ECFDF5; color: #10B981; } +.menu-icon-wrap.violet { background: #F5F3FF; color: #8B5CF6; } + +.menu-info { flex: 1; } + +.menu-name { + font-size: 28rpx; + font-weight: 800; + color: #111827; + display: block; +} + +.menu-desc { + font-size: 22rpx; + color: #6B7280; + display: block; + margin-top: 4rpx; +} + +.menu-arrow { + font-size: 36rpx; + color: #D1D5DB; +} + +/* ===== 底部提示 ===== */ +.footer-tip { + padding: 40rpx 60rpx; + text-align: center; +} + +.footer-tip text { + display: block; + font-size: 22rpx; + color: #D1D5DB; + line-height: 1.6; + font-weight: 500; +} + +/* ===== 绑定用户列表条目 ===== */ +.user-item { + display: flex; + align-items: center; + padding: 28rpx 32rpx; + border-bottom: 1rpx solid #F3F4F6; +} +.user-item:last-child { border-bottom: none; } + +.user-avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + flex-shrink: 0; + margin-right: 24rpx; + background: #F3F4F6; +} +.user-avatar-placeholder { + display: flex; + align-items: center; + justify-content: center; + background: #FECDD3; +} +.user-avatar-letter { + font-size: 36rpx; + color: #F43F5E; + font-weight: 700; +} + +.user-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 6rpx; +} +.user-name { + font-size: 28rpx; + font-weight: 600; + color: #1F2937; +} +.user-time { + font-size: 22rpx; + color: #9CA3AF; +} + +.user-badge { + font-size: 22rpx; + font-weight: 600; + padding: 6rpx 18rpx; + border-radius: 20rpx; + flex-shrink: 0; +} +.badge-active { + background: #FEF3C7; + color: #D97706; +} +.badge-paid { + background: #D1FAE5; + color: #059669; +} +.badge-expired { + background: #F3F4F6; + color: #9CA3AF; +} + +/* 列表底部提示 */ +.list-footer { + padding: 32rpx 0; + text-align: center; +} +.list-footer-text { + font-size: 24rpx; + color: #D1D5DB; +} + +.safe-bottom { height: 60rpx; } + +/* ===== 提现金额弹框 ===== */ +.withdraw-mask { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.withdraw-dialog { + width: 640rpx; + padding: 40rpx 40rpx 32rpx; + border-radius: 32rpx; + background: #ffffff; + box-shadow: 0 24rpx 60rpx rgba(15, 23, 42, 0.25); +} + +.dialog-title { + font-size: 32rpx; + font-weight: 700; + color: #111827; +} + +.dialog-sub { + margin-top: 8rpx; + font-size: 24rpx; + color: #6b7280; +} + +.amount-input-row { + margin-top: 32rpx; + padding: 16rpx 20rpx; + border-radius: 20rpx; + background: #f9fafb; + display: flex; + align-items: center; +} + +.currency { + font-size: 32rpx; + font-weight: 600; + color: #111827; + margin-right: 8rpx; +} + +.amount-input { + flex: 1; + font-size: 32rpx; + font-weight: 600; + color: #111827; +} + +.amount-input::placeholder { + color: #9ca3af; +} + +.amount-hint { + margin-top: 8rpx; + font-size: 22rpx; + color: #9ca3af; +} +.fee-actual-row { + margin-top: 12rpx; + font-size: 24rpx; + color: #6b7280; +} +.fee-actual-row .actual { + margin-left: 24rpx; + color: #059669; + font-weight: 500; +} + +.error-text { + margin-top: 8rpx; + font-size: 22rpx; + color: #dc2626; +} + +.dialog-actions { + margin-top: 32rpx; + display: flex; + gap: 16rpx; +} + +.dialog-actions .btn { + flex: 1; + height: 80rpx; + border-radius: 9999rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 28rpx; + font-weight: 600; +} + +.dialog-actions .btn.cancel { + background: #f3f4f6; + color: #4b5563; +} + +.dialog-actions .btn.confirm { + background: #f43f5e; + color: #ffffff; +} + diff --git a/miniprogram/pages/promo/poster.js b/miniprogram/pages/promo/poster.js new file mode 100644 index 0000000..1c6186e --- /dev/null +++ b/miniprogram/pages/promo/poster.js @@ -0,0 +1,74 @@ +const { getApiBase } = require('../../utils/request') + +Page({ + data: { + loading: true, + loadingText: '正在生成海报...', + posterUrl: '' + }, + + onLoad() { + this.loadPoster() + }, + + /** 从后端接口下载完整合成海报 */ + loadPoster() { + const app = getApp() + const gd = app.globalData || {} + const storedUser = wx.getStorageSync('userInfo') || {} + const userInfo = gd.userInfo || storedUser + const apiBase = getApiBase() + const token = wx.getStorageSync('token') || gd.token || '' + const scope = gd.appScope || 'personal' + // 企业 ID:按优先级取 scene > globalData.userInfo > storage.userInfo + const eid = scope === 'enterprise' + ? (gd.enterpriseIdFromScene || (gd.userInfo && gd.userInfo.enterpriseId) || storedUser.enterpriseId || null) + : null + let url = apiBase.replace(/\/$/, '') + '/api/distribution/poster' + if (eid) url += `?eid=${eid}&scope=enterprise` + else if (scope === 'enterprise') url += '?scope=enterprise' // 企业模式但 eid 未知,后端从 DB 取 + else url += '?scope=personal' + + wx.downloadFile({ + url, + header: token ? { Authorization: 'Bearer ' + token } : {}, + success: (res) => { + if (res.statusCode === 200 && res.tempFilePath) { + this.setData({ posterUrl: res.tempFilePath, loading: false }) + } else { + this.setData({ loadingText: '海报生成失败', loading: false }) + wx.showToast({ title: '生成失败', icon: 'none' }) + } + }, + fail: () => { + this.setData({ loadingText: '请求失败,请重试', loading: false }) + wx.showToast({ title: '请求失败', icon: 'none' }) + } + }) + }, + + /** 保存到相册 */ + savePoster() { + if (this.data.loading || !this.data.posterUrl) return + wx.showLoading({ title: '正在保存...' }) + wx.saveImageToPhotosAlbum({ + filePath: this.data.posterUrl, + success: () => { + wx.hideLoading() + wx.showToast({ title: '已保存到相册', icon: 'success' }) + }, + fail: (err) => { + wx.hideLoading() + if (err.errMsg && err.errMsg.indexOf('auth deny') >= 0) { + wx.showModal({ + title: '提示', + content: '需要您授权保存图片到相册', + success: (r) => { if (r.confirm) wx.openSetting() } + }) + } else { + wx.showToast({ title: '保存失败', icon: 'none' }) + } + } + }) + } +}) diff --git a/miniprogram/pages/promo/poster.json b/miniprogram/pages/promo/poster.json new file mode 100644 index 0000000..4fc8ed5 --- /dev/null +++ b/miniprogram/pages/promo/poster.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "生成推广海报", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} \ No newline at end of file diff --git a/miniprogram/pages/promo/poster.wxml b/miniprogram/pages/promo/poster.wxml new file mode 100644 index 0000000..a88c0ae --- /dev/null +++ b/miniprogram/pages/promo/poster.wxml @@ -0,0 +1,16 @@ + + + + + + {{loadingText}} + + + + + 保存到相册后可分享至朋友圈 + + + diff --git a/miniprogram/pages/promo/poster.wxss b/miniprogram/pages/promo/poster.wxss new file mode 100644 index 0000000..44742b1 --- /dev/null +++ b/miniprogram/pages/promo/poster.wxss @@ -0,0 +1,83 @@ +.container { + min-height: 100vh; + background-color: #f8fafc; + display: flex; + flex-direction: column; + align-items: center; + padding: 40rpx 0; +} + +.poster-preview { + position: relative; + width: 600rpx; + min-height: 800rpx; + background-color: #fff; + border-radius: 32rpx; + box-shadow: 0 20rpx 50rpx rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.poster-img { + width: 100%; + display: block; +} + +.loading-mask { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background-color: rgba(255, 255, 255, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 10; +} + +.loading-spinner { + width: 60rpx; height: 60rpx; + border: 4rpx solid #f3f4f6; + border-top: 4rpx solid #f43f5e; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 20rpx; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.loading-text { font-size: 24rpx; color: #64748b; } + +.action-bar { + position: fixed; + bottom: 0; left: 0; right: 0; + background-color: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + padding: 40rpx; + display: flex; + flex-direction: column; + align-items: center; + border-radius: 40rpx 40rpx 0 0; + box-shadow: 0 -10rpx 30rpx rgba(0, 0, 0, 0.05); +} + +.save-btn { + width: 100% !important; + height: 100rpx; + background: #f43f5e; + color: #fff; + border-radius: 24rpx; + font-size: 32rpx; + font-weight: bold; + box-shadow: 0 10rpx 20rpx rgba(244, 63, 94, 0.2); + margin-bottom: 20rpx; +} + +.save-btn[disabled] { + background: #cbd5e1; + box-shadow: none; +} + +.tip-text { font-size: 24rpx; color: #94a3b8; margin-bottom: 20rpx; } +.safe-bottom { height: env(safe-area-inset-bottom); } diff --git a/miniprogram/pages/promo/withdrawals.js b/miniprogram/pages/promo/withdrawals.js new file mode 100644 index 0000000..29354a3 --- /dev/null +++ b/miniprogram/pages/promo/withdrawals.js @@ -0,0 +1,236 @@ +// pages/promo/withdrawals.js - 提现记录列表页 +const { request } = require('../../utils/request') + +Page({ + data: { + list: [], + page: 1, + pageSize: 10, + total: 0, + loading: false, + finished: false, + // 确认收款按钮 loading 状态(按单条) + confirmingId: null + }, + + onLoad() { + this.loadData(true) + }, + + // 加载提现记录列表(reset 为 true 时重置分页) + loadData(reset = false) { + if (this.data.loading) return + if (!reset && this.data.finished) return + + const page = reset ? 1 : this.data.page + this.setData({ loading: true }) + + request({ + url: `/api/distribution/withdrawals?page=${page}&pageSize=${this.data.pageSize}`, + method: 'GET', + success: (res) => { + const payload = res && res.data + if (payload && payload.code === 200 && payload.data) { + const { list = [], total = 0 } = payload.data + const mapped = list.map(item => { + const statusCode = this._normalizeStatusCode(item.status) + const amountYuan = item.amountYuan != null + ? item.amountYuan + : (item.amountFen != null ? (item.amountFen / 100).toFixed(2) : '') + + const balanceAfterYuan = item.balanceAfterYuan != null + ? item.balanceAfterYuan + : (item.balanceAfterFen != null ? (item.balanceAfterFen / 100).toFixed(2) : '') + + const feeYuan = item.feeYuan != null + ? item.feeYuan + : (item.feeFen != null ? (item.feeFen / 100).toFixed(2) : '0.00') + + return { + ...item, + statusCode, + amountYuan, + balanceAfterYuan, + feeYuan, + // 申请时间:用 createdAt + applyAtStr: this._fmtTime(item.createdAt), + // 处理时间:用 transferAt(如有) + handleAtStr: this._fmtTime(item.transferAt), + statusTagText: this._statusTagText(statusCode), + statusTagClass: this._statusTagClass(statusCode) + } + }) + const newList = reset ? mapped : [...this.data.list, ...mapped] + this.setData({ + list: newList, + total, + page: page + 1, + finished: newList.length >= total + }) + } + }, + complete: () => { + this.setData({ loading: false }) + } + }) + }, + + // 时间戳格式化 + _fmtTime(ts) { + if (!ts) return '' + const d = new Date(ts * 1000) + if (isNaN(d.getTime())) return '' + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + const hh = String(d.getHours()).padStart(2, '0') + const mm = String(d.getMinutes()).padStart(2, '0') + return `${y}-${m}-${day} ${hh}:${mm}` + }, + + // 将后端 status(数字 / 中文 / 英文)统一转成数字枚举: + // 0=审核中,1=已驳回,2=待收款,3=已收款,4=已过期 + _normalizeStatusCode(status) { + if (typeof status === 'number') return status + if (typeof status === 'string') { + const s = status.trim() + if (/^\d+$/.test(s)) return parseInt(s, 10) + switch (s) { + case 'pending': + case '审核中': + return 0 + case 'rejected': + case '已驳回': + return 1 + case '待收款': + return 2 + case 'transferred': + case '已收款': + return 3 + case '已过期': + return 4 + default: + return 0 + } + } + return 0 + }, + + // 状态中文文案 + _statusTagText(code) { + switch (code) { + case 0: + return '待审核' + case 1: + return '已驳回' + case 2: + return '待收款' + case 3: + return '已收款' + case 4: + return '已过期' + default: + return '' + } + }, + + // 顶部右侧小标签样式 + _statusTagClass(code) { + switch (code) { + case 0: + return 'tag-pending' + case 1: + return 'tag-rejected' + case 2: + return 'tag-wait' + case 3: + return 'tag-transferred' + case 4: + return 'tag-expired' + default: + return '' + } + }, + + // 用户点击单条「确认收款」 + handleConfirmReceipt(e) { + const id = e.currentTarget.dataset.id + if (!id || this.data.confirmingId === id) return + + const item = (this.data.list || []).find(x => String(x.id) === String(id)) + if (!item) { + wx.showToast({ title: '记录不存在', icon: 'none' }) + return + } + + // 兼容驼峰 / 下划线字段 + const packageInfo = item.packageInfo || item.package_info + if (!packageInfo) { + wx.showToast({ title: '该提现单无法确认收款,请联系客服', icon: 'none' }) + return + } + + if (!wx.canIUse || !wx.canIUse('requestMerchantTransfer')) { + wx.showModal({ + content: '当前微信版本过低,无法使用确认收款功能,请升级微信后重试', + showCancel: false + }) + return + } + + this.setData({ confirmingId: id }) + + wx.showLoading({ title: '正在调起确认收款...' }) + + // 获取 AppID:优先记录里的,其次运行时获取 + let appId = item.appId || item.app_id + try { + if (!appId && wx.getAccountInfoSync) { + const info = wx.getAccountInfoSync() + appId = info && info.miniProgram && info.miniProgram.appId + } + } catch (err) {} + + // 商户号:从记录中取(后端已写入 mch_id) + const mchId = item.mchId || item.mch_id + + wx.requestMerchantTransfer({ + mchId, + appId, + // 文档里字段名是 package,对应后台返回的 package_info + package: packageInfo, + success: (res) => { + console.log('requestMerchantTransfer success', res) + wx.showToast({ title: '已调起收款确认', icon: 'none', duration: 2000 }) + // 主动查询转账状态并更新订单,实现及时刷新(参考商户单号查询:https://pay.weixin.qq.com/doc/v3/merchant/4012716437) + request({ + url: '/api/distribution/withdrawals/query-transfer', + method: 'POST', + data: { id: item.id }, + success: (r) => { + const p = r && r.data + if (p && p.code === 200 && p.data && p.data.status === 3) { + wx.showToast({ title: '已收款', icon: 'success' }) + } + }, + complete: () => { + this.loadData(true) + } + }) + }, + fail: (err) => { + console.error('requestMerchantTransfer fail', err) + wx.showToast({ title: '调起失败,请稍后重试', icon: 'none' }) + }, + complete: () => { + wx.hideLoading() + this.setData({ confirmingId: null }) + } + }) + }, + + onReachBottom() { + this.loadData(false) + } +}) + diff --git a/miniprogram/pages/promo/withdrawals.json b/miniprogram/pages/promo/withdrawals.json new file mode 100644 index 0000000..35f1b24 --- /dev/null +++ b/miniprogram/pages/promo/withdrawals.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "提现记录", + "usingComponents": {} +} + diff --git a/miniprogram/pages/promo/withdrawals.wxml b/miniprogram/pages/promo/withdrawals.wxml new file mode 100644 index 0000000..3505ee8 --- /dev/null +++ b/miniprogram/pages/promo/withdrawals.wxml @@ -0,0 +1,77 @@ + + + + + + + + 📭 + + 暂无提现记录 + 成功发起提现后,将在这里展示进度 + + + + + + + + 单号:{{item.out_bill_no}} + + {{item.statusTagText}} + + + + + + + ¥{{item.amountYuan}} + + + 手续费 + ¥{{item.feeYuan}} + + + 提现后余额 + ¥{{item.balanceAfterYuan}} + + + + + + 申请时间:{{item.applyAtStr}} + 处理时间:{{item.handleAtStr}} + + + + + 拒绝原因: + {{item.auditNote}} + + + + + + + + + + 加载中... + + + — 已加载全部 — + + + + + + + diff --git a/miniprogram/pages/promo/withdrawals.wxss b/miniprogram/pages/promo/withdrawals.wxss new file mode 100644 index 0000000..d06f78b --- /dev/null +++ b/miniprogram/pages/promo/withdrawals.wxss @@ -0,0 +1,244 @@ +.page { + min-height: 100vh; + background: #f9fafb; + padding: 24rpx 32rpx 32rpx; + box-sizing: border-box; +} + +.header { + margin-bottom: 24rpx; +} + +.title { + font-size: 32rpx; + font-weight: 700; + color: #111827; +} + +.sub-title { + margin-top: 6rpx; + font-size: 24rpx; + color: #6b7280; +} + +.empty { + margin-top: 80rpx; + display: flex; + flex-direction: column; + align-items: center; + color: #9ca3af; +} + +.empty-icon { + width: 120rpx; + height: 120rpx; + border-radius: 60rpx; + background: #f3f4f6; + display: flex; + align-items: center; + justify-content: center; + font-size: 56rpx; + margin-bottom: 16rpx; +} + +.empty-text { + font-size: 28rpx; + margin-bottom: 4rpx; + color: #4b5563; +} + +.empty-sub { + font-size: 24rpx; + color: #9ca3af; +} + +.list { + margin-top: 12rpx; +} + +.item { + background: #ffffff; + border-radius: 24rpx; + padding: 24rpx 28rpx; + margin-bottom: 20rpx; + box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04); +} + +.item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16rpx; +} + +.order-no { + font-size: 24rpx; + color: #6b7280; +} + +.status-tag { + padding: 6rpx 16rpx; + border-radius: 999rpx; + font-size: 22rpx; +} + +.tag-pending { + background: #e5e7eb; + color: #4b5563; +} + +.tag-approved { + background: #dbeafe; + color: #1d4ed8; +} + +.tag-wait { + background: #dbeafe; + color: #1d4ed8; +} + +.tag-rejected { + background: #fee2e2; + color: #b91c1c; +} + +.tag-transferred { + background: #dcfce7; + color: #15803d; +} + +.tag-expired { + background: #f3f4f6; + color: #9ca3af; +} + +.item-top { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.amount-block { + display: flex; + flex-direction: column; + gap: 6rpx; +} + +.amount { + font-size: 32rpx; + font-weight: 700; + color: #111827; +} + +.fee-block { + display: flex; + flex-direction: column; + gap: 4rpx; +} + +.fee-label { + font-size: 22rpx; + color: #9ca3af; +} + +.fee-value { + font-size: 26rpx; + color: #6b7280; +} + +.balance-block { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.balance-label { + font-size: 22rpx; + color: #9ca3af; +} + +.balance-value { + margin-top: 4rpx; + font-size: 26rpx; + color: #111827; +} + +.time-block { + margin-top: 16rpx; + display: flex; + flex-direction: column; + gap: 6rpx; +} + +.time-row { + font-size: 22rpx; + color: #6b7280; +} + +.reject-box { + margin-top: 16rpx; + padding: 16rpx; + border-radius: 12rpx; + background: #fef2f2; +} + +.reject-title { + font-size: 24rpx; + font-weight: 600; + color: #b91c1c; +} + +.reject-text { + font-size: 24rpx; + color: #b91c1c; +} + +.item-bottom { + margin-top: 10rpx; +} + +.note { + font-size: 24rpx; + color: #6b7280; +} + +.footer { + padding: 16rpx 0; + text-align: center; +} + +.footer-text { + font-size: 24rpx; + color: #9ca3af; +} + +.confirm-wrapper { + margin-top: 20rpx; + padding: 0 8rpx 4rpx; +} + +.confirm-btn { + width: 100%; + height: 80rpx; + border-radius: 999rpx; + background: linear-gradient(135deg, #FF6B8A 0%, #FF8E53 100%); + color: #ffffff; + font-size: 30rpx; + font-weight: 600; + box-shadow: 0 10rpx 18rpx rgba(255, 107, 138, 0.35); + border: none; + display: flex; + align-items: center; + justify-content: center; + padding: 0; +} + +.confirm-btn[disabled] { + opacity: 0.7; + box-shadow: none; +} + +.safe-bottom { + height: calc(32rpx + env(safe-area-inset-bottom)); +} + diff --git a/miniprogram/pages/purchase/index.js b/miniprogram/pages/purchase/index.js new file mode 100644 index 0000000..ee6cb7b --- /dev/null +++ b/miniprogram/pages/purchase/index.js @@ -0,0 +1,247 @@ +// pages/purchase/index.js - 开通会员(深度服务价格:个人/企业区分,类目由后端配置可新增) +const app = getApp() +const payment = require('../../utils/payment') +const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js') + +Page({ + data: { + activeTab: 'personal', + personalCategories: [], + enterpriseCategories: [], + loading: true, + purchasing: false, + hasPhone: false, + successModal: { + visible: false, + title: '', + content: '', + wechat: '' + } + }, + + onLoad(options) { + const tab = (options && options.tab === 'enterprise') ? 'enterprise' : 'personal' + this.setData({ activeTab: tab }) + wx.setNavigationBarTitle({ title: tab === 'enterprise' ? '开通企业版' : '开通个人版' }) + this.loadDeepPricing() + }, + + onShow() { + if (!ensureProfileCompleteAndRedirect()) return + this.setData({ hasPhone: hasPhone() }) + }, + + loadDeepPricing() { + const apiBase = app.globalData.apiBase || '' + if (!apiBase) { + this.setData({ loading: false }) + return + } + this.setData({ loading: true }) + Promise.all([ + this.requestDeepPricing('personal'), + this.requestDeepPricing('enterprise') + ]).then(([personal, enterprise]) => { + this.setData({ + personalCategories: personal || [], + enterpriseCategories: enterprise || [], + loading: false + }) + }).catch(() => { + this.setData({ loading: false }) + }) + }, + + requestDeepPricing(scope) { + return new Promise((resolve) => { + wx.request({ + url: `${app.globalData.apiBase.replace(/\/$/, '')}/api/config/deep-pricing`, + method: 'GET', + data: { scope }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200 && Array.isArray(res.data.data && res.data.data.categories)) { + resolve(res.data.data.categories) + } else { + resolve([]) + } + }, + fail: () => resolve([]) + }) + }) + }, + + switchTab(e) { + const tab = e.currentTarget.dataset.tab + this.setData({ activeTab: tab }) + }, + + // 无需再次授权时,直接点击按钮执行购买/咨询 + handlePurchaseTap(e) { + const tab = e.currentTarget.dataset.tab + const index = e.currentTarget.dataset.index + this.handlePurchase(tab, index) + }, + + // 实际执行购买/咨询逻辑(已确保有手机号) + handlePurchase(tab, index) { + if (!ensureProfileCompleteAndRedirect()) return + if (index === undefined || index === null) return + const list = tab === 'enterprise' ? this.data.enterpriseCategories : this.data.personalCategories + const category = list[index] + if (!category) return + + if (category.actionType === 'buy' && category.productKey) { + this.purchasePersonal(category) + } else { + this.applyConsult(category) + } + }, + + // 购买/企业咨询按钮:就地触发微信系统手机号授权,然后执行 handlePurchase + onGetPhoneNumberForPurchase(e) { + const tab = e.currentTarget.dataset.tab + const index = e.currentTarget.dataset.index + const { code, errMsg } = e.detail || {} + + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + if (!hasPhone()) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + // 用户拒绝但之前已授权过,本地已有手机号,则直接继续 + this.handlePurchase(tab, index) + return + } + + if (!code) { + if (hasPhone()) { + this.handlePurchase(tab, index) + } else { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + } + return + } + + bindPhoneByCode(code) + .then(() => { + this.setData({ hasPhone: true }) + this.handlePurchase(tab, index) + }) + .catch(() => { + // 失败时只提示,不阻塞后续再次点击 + }) + }, + + purchasePersonal(category) { + if (this.data.purchasing) return + this.setData({ purchasing: true }) + wx.showLoading({ title: '处理中...', mask: true }) + const deepProductId = category.id || category.productKey || '' + const title = category.title || '个人深度服务(1v1深度解读)' + payment.purchasePersonalDeepService({ + deepProductId, + description: title, + success: () => { + wx.hideLoading() + this.setData({ purchasing: false }) + this._reportCrmLead(category, 'buy') + const successMsg = (category.successMessage || '购买成功!我们的顾问会尽快与您联系,为您提供专属深度解读服务。').trim() + const wechat = (category.serviceWechat || '').trim() + this._showSuccessModal('购买成功', successMsg, wechat) + }, + fail: () => { + wx.hideLoading() + this.setData({ purchasing: false }) + } + }) + }, + + applyConsult(category) { + // serviceWechat 展示给用户,consultWechat 是存客宝 API key + const wechat = (category.serviceWechat || '').trim() + const apiKey = (category.consultWechat || '').trim() + const successMsg = (category.successMessage || '感谢您的申请,我们的顾问会尽快与您联系!').trim() + wx.showLoading({ title: '提交中...', mask: true }) + if (apiKey) { + this._reportCrmLead(category, 'consult') + } + setTimeout(() => { + wx.hideLoading() + this._showSuccessModal('申请成功', successMsg, wechat) + }, 600) + }, + + _showSuccessModal(title, content, wechat) { + this.setData({ + successModal: { + visible: true, + title: title || '成功', + content: content || '', + wechat: wechat || '' + } + }) + }, + + closeSuccessModal() { + this.setData({ 'successModal.visible': false }) + }, + + copyWechat() { + const wechat = this.data.successModal.wechat + if (!wechat) return + wx.setClipboardData({ + data: wechat, + success: () => wx.showToast({ title: '已复制微信号', icon: 'success' }) + }) + }, + + /** + * 向后端上报存客宝线索,后端负责签名和调用存客宝 API + * @param {Object} category 深度服务类目对象(需含 consultWechat / title) + * @param {string} actionType 'buy'(付款完成)| 'consult'(申请咨询) + */ + _reportCrmLead(category, actionType) { + const apiKey = category.consultWechat || '' + if (!apiKey) return + const apiBase = app.globalData.apiBase || '' + if (!apiBase) return + + const isEnterprise = this.data.activeTab === 'enterprise' + const source = (isEnterprise ? '企业深度服务' : '个人深度服务') + (category.title ? `-${category.title}` : '') + const remark = actionType === 'buy' ? '完成付款' : '申请咨询' + + wx.request({ + url: `${apiBase.replace(/\/$/, '')}/api/crm/report`, + method: 'POST', + header: { + Authorization: `Bearer ${wx.getStorageSync('token') || ''}`, + 'Content-Type': 'application/json', + }, + data: { + apiKey, + source, + remark, + siteTags: category.title || '', + }, + success(res) { + console.log('[CRM] 线索上报结果', res.data) + }, + fail(err) { + console.warn('[CRM] 线索上报请求失败', err) + }, + }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { title: '神仙团队AI性格测试 - 发现你的内在潜能', path: getSharePath('/pages/purchase/index') } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '神仙团队AI性格测试 - 发现你的内在潜能', + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/purchase/index.json b/miniprogram/pages/purchase/index.json new file mode 100644 index 0000000..de2823c --- /dev/null +++ b/miniprogram/pages/purchase/index.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "开通会员", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black", + "usingComponents": {} +} diff --git a/miniprogram/pages/purchase/index.wxml b/miniprogram/pages/purchase/index.wxml new file mode 100644 index 0000000..0a4868c --- /dev/null +++ b/miniprogram/pages/purchase/index.wxml @@ -0,0 +1,135 @@ + + + + + + + + + + + {{successModal.title}} + {{successModal.content}} + + 客服微信 + {{successModal.wechat}} + + + + + + + + + + + + 加载中... + + + + + + + {{item.title}} + + ¥ + {{item.price}} + {{item.priceUnit || '/次'}} + + {{item.subtitle}} + + + + + {{f}} + + + + + + + + + 暂无可用的个人版套餐 + + + + + + + + + {{item.title}} + {{item.subtitle}} + + + {{item.priceDisplay || '¥' + item.price}} + {{item.userLimit}} + + + + + + {{f}} + + + + + + + + + 暂无可用的企业版套餐 + + + + + + 🔒 + 微信安全支付 + + + + 即时到账开通 + + + 📞 + 7×24客服 + + + diff --git a/miniprogram/pages/purchase/index.wxss b/miniprogram/pages/purchase/index.wxss new file mode 100644 index 0000000..683da97 --- /dev/null +++ b/miniprogram/pages/purchase/index.wxss @@ -0,0 +1,789 @@ +/* pages/purchase/index.wxss - 按旧版模板重构 */ + +/* ── 成功弹窗 ── */ +.success-mask { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 999; + display: flex; + align-items: center; + justify-content: center; +} + +.success-dialog { + width: 600rpx; + background: #fff; + border-radius: 24rpx; + padding: 56rpx 40rpx 40rpx; + display: flex; + flex-direction: column; + align-items: center; +} + +.success-icon-wrap { + margin-bottom: 32rpx; +} + +.success-icon-circle { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + background: linear-gradient(135deg, #22c55e, #16a34a); + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 24rpx rgba(34, 197, 94, 0.35); +} + +.success-icon-check { + font-size: 56rpx; + color: #fff; + font-weight: bold; + line-height: 1; +} + +.success-dialog-title { + font-size: 40rpx; + font-weight: bold; + color: #1a1a1a; + margin-bottom: 20rpx; +} + +.success-dialog-content { + font-size: 28rpx; + color: #555; + text-align: center; + line-height: 1.7; + margin-bottom: 32rpx; +} + +.success-wechat-wrap { + width: 100%; + background: #f5f5f5; + border-radius: 12rpx; + padding: 20rpx 24rpx; + margin-bottom: 32rpx; + display: flex; + align-items: center; + gap: 16rpx; +} + +.success-wechat-label { + font-size: 24rpx; + color: #999; + flex-shrink: 0; +} + +.success-wechat-val { + font-size: 28rpx; + color: #333; + font-weight: 500; + flex: 1; + word-break: break-all; +} + +.success-dialog-btns { + width: 100%; + display: flex; + flex-direction: row; + gap: 16rpx; +} + +.success-btn-copy { + flex: 1; + background: #2563eb; + color: #fff; + font-size: 28rpx; + font-weight: 500; + padding: 24rpx 0; + border-radius: 12rpx; + border: none; +} + +.success-btn-close { + flex: 1; + background: #f5f5f5; + color: #333; + font-size: 28rpx; + padding: 24rpx 0; + border-radius: 12rpx; + border: none; +} + + +.container { + width: 100vw; + min-height: 100vh; + background-color: #f5f5f5; + padding-bottom: 160rpx; +} + +/* 用户权益状态 */ +.status-card { + display: flex; + align-items: center; + padding: 40rpx; + margin: 24rpx; + background: #fff; + border-radius: 16rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); +} + +.status-card.vip { + background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%); + border: 2rpx solid #9333ea; +} + +.status-icon { + font-size: 48rpx; + margin-right: 20rpx; +} + +.status-info { + flex: 1; +} + +.status-title { + display: block; + font-size: 32rpx; + font-weight: bold; + color: #333; +} + +.status-desc { + display: block; + font-size: 24rpx; + color: #666; + margin-top: 4rpx; +} + +.test-count { + text-align: center; +} + +.count-num { + display: block; + font-size: 40rpx; + font-weight: bold; + color: #9333ea; +} + +.count-label { + display: block; + font-size: 22rpx; + color: #999; +} + +/* 区块 */ +.section { + margin: 24rpx; +} + +.section-title { + display: block; + font-size: 34rpx; + font-weight: bold; + color: #333; + margin-bottom: 8rpx; +} + +.section-subtitle { + display: block; + font-size: 24rpx; + color: #999; + margin-bottom: 24rpx; +} + +/* Tabs 区域 */ +.tabs-container { + background-color: #fff; + padding: 24rpx 32rpx; + margin: 24rpx; + border-radius: 16rpx; +} + +.tabs-list { + display: flex; + background-color: #f5f5f5; + border-radius: 12rpx; + padding: 8rpx; +} + +.tab-item { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 16rpx 24rpx; + border-radius: 8rpx; + transition: all 0.3s; +} + +.tab-item.active { + background-color: #fff; +} + +.tab-icon { + font-size: 32rpx; + margin-right: 8rpx; +} + +.tab-text { + font-size: 28rpx; + color: #666; +} + +.tab-item.active .tab-text { + color: #333; + font-weight: 500; +} + +.loading-wrap { + padding: 80rpx; + text-align: center; +} + +.loading-text { + font-size: 28rpx; + color: #999; +} + +.empty-tip { + padding: 60rpx 24rpx; + text-align: center; + font-size: 28rpx; + color: #999; +} + +/* 定价卡片(已去除个人版/企业版彩色边框,统一灰色细边) */ +.pricing-card { + background-color: #fff; + border-radius: 16rpx; + padding: 40rpx; + margin-bottom: 24rpx; + border: 2rpx solid #e5e5e5; +} + +.pricing-card.featured, +.pricing-card.featured-blue { + border-color: #e5e5e5; + border-width: 2rpx; +} + +/* 企业版 header flex 布局 */ +.card-header-flex { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 32rpx; +} + +.header-left { + flex: 1; +} + +.plan-title-sm { + font-size: 36rpx; + font-weight: bold; + color: #333; + display: block; + margin-bottom: 8rpx; +} + +.plan-desc { + font-size: 24rpx; + color: #666; + display: block; +} + +.header-right { + text-align: right; +} + +.price-amount-small { + font-size: 40rpx; + font-weight: bold; + color: #333; + display: block; + margin-bottom: 4rpx; +} + +.price-limit { + font-size: 22rpx; + color: #999; + display: block; +} + +.check-icon.blue { + background-color: #2563eb; +} + +.purchase-button.secondary { + background-color: #2563eb; +} + +.card-header { + text-align: center; + margin-bottom: 32rpx; +} + +.plan-title { + font-size: 40rpx; + font-weight: bold; + color: #333; + display: block; + margin-bottom: 20rpx; +} + +.price-section { + display: flex; + align-items: baseline; + justify-content: center; + margin-bottom: 16rpx; +} + +.price-symbol { + font-size: 40rpx; + color: #333; + font-weight: bold; +} + +.price-amount { + font-size: 72rpx; + font-weight: bold; + color: #333; + margin: 0 8rpx; +} + +.price-unit { + font-size: 28rpx; + color: #666; +} + +.plan-subtitle { + font-size: 26rpx; + color: #666; + display: block; +} + +/* 功能列表 */ +.features-list { + margin-bottom: 32rpx; +} + +.feature-item { + display: flex; + align-items: flex-start; + margin-bottom: 20rpx; +} + +.feature-item:last-child { + margin-bottom: 0; +} + +.check-icon { + width: 32rpx; + height: 32rpx; + border-radius: 50%; + background-color: #22c55e; + color: #fff; + font-size: 20rpx; + display: flex; + align-items: center; + justify-content: center; + margin-right: 16rpx; + flex-shrink: 0; + text-align: center; +} + +.feature-text { + flex: 1; + font-size: 26rpx; + color: #666; + line-height: 1.6; +} + +/* 购买按钮 */ +.purchase-button { + width: 100%; + padding: 28rpx; + border-radius: 12rpx; + text-align: center; + box-sizing: border-box; +} + +.purchase-button.primary { + background-color: #9333ea; +} + +.purchase-button .button-text { + font-size: 32rpx; + color: #fff; + font-weight: 500; +} + +/* 分润机制 */ +.commission-section { + background-color: #f9fafb; + border-radius: 16rpx; + padding: 32rpx; + margin: 24rpx; +} + +.commission-header { + display: flex; + align-items: center; + margin-bottom: 24rpx; +} + +.commission-icon { + font-size: 32rpx; + margin-right: 12rpx; +} + +.commission-title { + font-size: 32rpx; + font-weight: 500; + color: #333; +} + +.commission-list { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.commission-item { + display: flex; + justify-content: space-between; + align-items: center; +} + +.commission-label { + font-size: 26rpx; + color: #666; +} + +.commission-value { + font-size: 28rpx; + font-weight: 500; + color: #333; +} + +/* VIP套餐网格 */ +.vip-cards { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16rpx; + margin-bottom: 32rpx; +} + +.vip-card { + position: relative; + background: #fff; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + padding: 24rpx; + text-align: center; +} + +.vip-card.selected { + border-color: #9333ea; + background: #faf5ff; +} + +.vip-card.hot { + border-color: #f59e0b; +} + +.vip-tag { + position: absolute; + top: 8rpx; + right: 8rpx; + background: #9333ea; + color: #fff; + font-size: 18rpx; + padding: 4rpx 12rpx; + border-radius: 8rpx; +} + +.vip-hot-tag, +.vip-best-tag { + position: absolute; + top: -2rpx; + left: 50%; + transform: translateX(-50%); + background: #f59e0b; + color: #fff; + font-size: 18rpx; + padding: 4rpx 16rpx; + border-radius: 0 0 8rpx 8rpx; +} + +.vip-best-tag { + background: #ef4444; +} + +.vip-name { + display: block; + font-size: 26rpx; + color: #333; + font-weight: 500; + margin-top: 16rpx; + margin-bottom: 12rpx; +} + +.vip-price { + display: flex; + align-items: baseline; + justify-content: center; + margin-bottom: 8rpx; +} + +.vip-price .price-symbol { + font-size: 28rpx; + color: #333; +} + +.vip-price .price-num { + font-size: 48rpx; + font-weight: bold; + color: #333; +} + +.vip-original { + display: block; + font-size: 22rpx; + color: #999; + text-decoration: line-through; +} + +.vip-avg { + display: block; + font-size: 22rpx; + color: #9333ea; + margin-top: 4rpx; +} + +/* 次数包 */ +.count-cards { + display: flex; + gap: 16rpx; +} + +.count-card { + flex: 1; + position: relative; + background: #fff; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + padding: 24rpx 16rpx; + text-align: center; +} + +.count-card.selected { + border-color: #9333ea; + background: #faf5ff; +} + +.count-save { + position: absolute; + top: -2rpx; + right: -2rpx; + background: #ef4444; + color: #fff; + font-size: 18rpx; + padding: 4rpx 12rpx; + border-radius: 0 8rpx 0 8rpx; +} + +.count-num-big { + font-size: 48rpx; + font-weight: bold; + color: #333; +} + +.count-unit { + font-size: 24rpx; + color: #999; + display: block; + margin-bottom: 12rpx; +} + +.count-price { + display: flex; + align-items: baseline; + justify-content: center; +} + +.count-price .price-symbol { + font-size: 24rpx; + color: #333; +} + +.count-price .price-num { + font-size: 36rpx; + font-weight: bold; + color: #333; +} + +/* 单次购买 */ +.single-list { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.single-item { + display: flex; + align-items: center; + background: #fff; + border-radius: 16rpx; + padding: 24rpx; + border: 2rpx solid #e5e5e5; +} + +.single-icon { + font-size: 36rpx; + margin-right: 16rpx; +} + +.single-info { + flex: 1; +} + +.single-name { + display: block; + font-size: 28rpx; + font-weight: 500; + color: #333; +} + +.single-desc { + display: block; + font-size: 22rpx; + color: #999; + margin-top: 4rpx; +} + +.single-price { + text-align: center; +} + +.single-price .price-num { + display: block; + font-size: 28rpx; + font-weight: bold; + color: #9333ea; +} + +.price-action { + display: block; + font-size: 22rpx; + color: #9333ea; +} + +/* 底部安全提示 */ +.safety-tips { + display: flex; + justify-content: center; + gap: 32rpx; + padding: 24rpx; + margin: 24rpx; +} + +.safety-item { + display: flex; + align-items: center; + gap: 8rpx; +} + +.safety-icon { + font-size: 24rpx; +} + +.safety-text { + font-size: 22rpx; + color: #999; +} + +.privileges { + background: #fff; + border-radius: 16rpx; + padding: 32rpx; +} + +.privilege-list { + display: flex; + flex-direction: column; + gap: 16rpx; +} + +.privilege-item { + display: flex; + align-items: center; + gap: 12rpx; +} + +.privilege-icon { + font-size: 28rpx; +} + +.privilege-text { + font-size: 26rpx; + color: #333; +} + +.bottom-placeholder { + height: 160rpx; +} + +/* 底部购买栏 */ +.bottom-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: #fff; + display: flex; + align-items: center; + padding: 24rpx 32rpx; + padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); + box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06); + z-index: 100; +} + +.price-info { + flex: 1; + display: flex; + align-items: baseline; +} + +.total-label { + font-size: 28rpx; + color: #666; +} + +.total-price { + font-size: 40rpx; + font-weight: bold; + color: #e63946; +} + +.buy-btn { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + padding: 24rpx 48rpx; + border-radius: 48rpx; + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.buy-btn.disabled { + opacity: 0.6; +} + +.buy-btn-text { + font-size: 30rpx; + font-weight: 600; + color: #fff; +} diff --git a/miniprogram/pages/recharge/index.js b/miniprogram/pages/recharge/index.js new file mode 100644 index 0000000..c9e060f --- /dev/null +++ b/miniprogram/pages/recharge/index.js @@ -0,0 +1,83 @@ +const app = getApp() +const payment = require('../../utils/payment') +const { request } = require('../../utils/request') + +Page({ + data: { + enterpriseId: 0, + enterpriseName: '', + amountFen: 0, + amountYuan: '0.00', + paying: false + }, + + onLoad(options) { + const rawScene = options && options.scene ? decodeURIComponent(options.scene) : '' + const sceneParams = {} + if (rawScene) { + rawScene.split('&').forEach(pair => { + const [k, v] = pair.split('=') + if (k) sceneParams[k] = v || '' + }) + } + + const enterpriseId = parseInt(sceneParams.eid || options.eid || 0, 10) || 0 + const amountFen = parseInt(sceneParams.a || options.amountFen || 0, 10) || 0 + const amountYuan = (amountFen / 100).toFixed(2) + + if (enterpriseId > 0) { + app.globalData.enterpriseIdFromScene = enterpriseId + } + + this.setData({ + enterpriseId, + enterpriseName: (app.globalData.userInfo && app.globalData.userInfo.enterpriseName) || '', + amountFen, + amountYuan + }) + + app.ensureLogin() + .then(() => { + if (enterpriseId > 0) { + request({ + url: '/api/enterprise/bind', + method: 'POST', + data: { enterpriseId }, + success: (res) => { + const payload = res && res.data && res.data.data ? res.data.data : {} + const enterpriseName = payload.enterpriseName || this.data.enterpriseName || '' + this.setData({ enterpriseName }) + }, + fail: () => {} + }) + } + }) + .catch(() => { + wx.showToast({ title: '请先登录', icon: 'none' }) + }) + }, + + submitRecharge() { + if (this.data.paying) return + if (!this.data.enterpriseId || !this.data.amountFen) { + wx.showToast({ title: '充值参数无效', icon: 'none' }) + return + } + + this.setData({ paying: true }) + payment.recharge({ + amountYuan: Number(this.data.amountYuan), + enterpriseId: this.data.enterpriseId, + success: () => { + this.setData({ paying: false }) + wx.showToast({ title: '充值成功', icon: 'success' }) + setTimeout(() => { + wx.navigateTo({ url: `/pages/enterprise/index?eid=${this.data.enterpriseId}` }) + }, 1200) + }, + fail: () => { + this.setData({ paying: false }) + } + }) + } +}) diff --git a/miniprogram/pages/recharge/index.json b/miniprogram/pages/recharge/index.json new file mode 100644 index 0000000..bbe8410 --- /dev/null +++ b/miniprogram/pages/recharge/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "企业余额充值" +} diff --git a/miniprogram/pages/recharge/index.wxml b/miniprogram/pages/recharge/index.wxml new file mode 100644 index 0000000..c146da3 --- /dev/null +++ b/miniprogram/pages/recharge/index.wxml @@ -0,0 +1,20 @@ + + + 企业余额充值 + 扫码后在小程序内完成支付,支付成功后自动进入企业余额 + + + 充值金额 + ¥{{amountYuan}} + + + + 企业名称 + {{enterpriseName || ('企业 #' + enterpriseId)}} + + + + + diff --git a/miniprogram/pages/recharge/index.wxss b/miniprogram/pages/recharge/index.wxss new file mode 100644 index 0000000..88ca441 --- /dev/null +++ b/miniprogram/pages/recharge/index.wxss @@ -0,0 +1,106 @@ +.recharge-page { + min-height: 100vh; + padding: 40rpx 28rpx; + background: linear-gradient(180deg, #f5f3ff 0%, #ffffff 100%); + box-sizing: border-box; +} + +.card { + background: #ffffff; + border-radius: 32rpx; + padding: 40rpx 32rpx; + box-shadow: 0 20rpx 60rpx rgba(124, 58, 237, 0.08); + border: 2rpx solid rgba(124, 58, 237, 0.06); +} + +.title { + font-size: 40rpx; + font-weight: 700; + color: #111827; +} + +.subtitle { + margin-top: 16rpx; + font-size: 26rpx; + line-height: 1.7; + color: #6b7280; +} + +.amount-box { + margin-top: 36rpx; + padding: 32rpx; + background: #f8fafc; + border-radius: 24rpx; + text-align: center; +} + +.amount-label { + display: block; + font-size: 24rpx; + color: #6b7280; +} + +.amount-value { + display: block; + margin-top: 12rpx; + font-size: 64rpx; + font-weight: 700; + color: #7c3aed; +} + +.meta-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 28rpx; + padding: 24rpx 8rpx 0; + font-size: 26rpx; + gap: 24rpx; +} + +.meta-label { + color: #6b7280; +} + +.meta-value { + color: #111827; + font-weight: 600; + max-width: 420rpx; + text-align: right; + line-height: 1.5; +} + +.pay-btn { + margin-top: 44rpx; + height: 96rpx; + line-height: 96rpx; + border-radius: 999rpx; + background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 55%, #a855f7 100%); + color: #ffffff; + font-size: 30rpx; + font-weight: 600; + box-shadow: 0 18rpx 36rpx rgba(124, 58, 237, 0.28); + border: none; + display: flex; + align-items: center; + justify-content: center; +} + +.pay-btn::after { + border: none; +} + +.pay-btn-text { + position: relative; + padding-right: 28rpx; +} + +.pay-btn-text::after { + content: '>'; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + font-size: 24rpx; + opacity: 0.9; +} diff --git a/miniprogram/pages/result/disc.js b/miniprogram/pages/result/disc.js new file mode 100644 index 0000000..301dfb7 --- /dev/null +++ b/miniprogram/pages/result/disc.js @@ -0,0 +1,157 @@ +// pages/result/disc.js - DISC结果页(支持付费墙 + 历史详情拉取) +const app = getApp() +const payment = require('../../utils/payment') + +function toIntPercent(v) { + if (v == null) return 0 + const n = typeof v === 'number' ? v : Number(v) + return Number.isFinite(n) ? Math.round(n) : 0 +} + +function withPercentagesInt(data) { + if (!data) return data + const p = data.percentages || {} + const out = { ...data } + out.percentagesInt = { + D: toIntPercent(p.D ?? p.d), + I: toIntPercent(p.I ?? p.i), + S: toIntPercent(p.S ?? p.s), + C: toIntPercent(p.C ?? p.c) + } + return out +} + +Page({ + data: { + result: null, + typeList: [ + { type: 'D', label: 'D型 - 支配型', colorClass: 'fill-d' }, + { type: 'I', label: 'I型 - 影响型', colorClass: 'fill-i' }, + { type: 'S', label: 'S型 - 稳健型', colorClass: 'fill-s' }, + { type: 'C', label: 'C型 - 谨慎型', colorClass: 'fill-c' } + ], + payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }, + testResultId: null, + hasReloadedAfterPay: false + }, + + onLoad(options) { + const id = options && options.id + const type = options && options.type + if (id && type === 'disc') { + this.setData({ testResultId: id }) + this.loadDetail(id) + return + } + const result = wx.getStorageSync('discResult') + if (result) { + this.setData({ result: withPercentagesInt(result) }) + this.initPayInfoFromRuntime('disc') + } else { + wx.showToast({ title: '暂无测试结果', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }, + + loadDetail(id) { + const apiBase = app.globalData?.apiBase || '' + const token = app.globalData?.token || wx.getStorageSync('token') || '' + if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/detail`, + method: 'GET', + header: token ? { Authorization: `Bearer ${token}` } : {}, + data: { id }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const payload = res.data.data || {} + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.setData({ result: withPercentagesInt(data) }) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ payInfo }) + } else { + wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + + initPayInfoFromRuntime(testType) { + app.getRuntimeConfig() + .then((cfg) => { + const reportRequires = cfg.reportRequiresPayment || {} + const pricing = cfg.pricing || {} + const requiresPayment = !!(reportRequires && reportRequires[testType]) + const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0) + this.setData({ + payInfo: { requiresPayment, isPaid: false, amountYuan } + }) + }) + .catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } })) + }, + + unlockFullReport() { + const { payInfo, testResultId, hasReloadedAfterPay } = this.data + if (!payInfo.requiresPayment || payInfo.isPaid) return + app.ensureLogin && app.ensureLogin().then((logged) => { + if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return } + payment.purchaseDiscTest({ + testResultId: testResultId || undefined, + success: () => { + wx.showToast({ title: '已解锁完整报告', icon: 'success' }) + this.setData({ 'payInfo.isPaid': true }) + if (testResultId && !hasReloadedAfterPay) { + this.setData({ hasReloadedAfterPay: true }) + setTimeout(() => this.loadDetail(testResultId), 500) + } + }, + fail: () => {} + }) + }) + }, + + retakeTest() { + if (!this.data.testResultId) { + wx.removeStorageSync('discResult') + } + wx.navigateTo({ url: '/pages/test/disc' }) + }, + + goHome() { + const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal' + if (scope === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + }, + + onShareAppMessage() { + const result = this.data.result + const { getSharePathByScope } = require('../../utils/share') + return { + title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`, + path: getSharePathByScope('/pages/index/index') + } + }, + + onShareTimeline() { + const result = this.data.result + const { buildShareQuery } = require('../../utils/share') + return { + title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`, + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/result/disc.json b/miniprogram/pages/result/disc.json new file mode 100644 index 0000000..adb3138 --- /dev/null +++ b/miniprogram/pages/result/disc.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "DISC结果", + "navigationBarBackgroundColor": "#3B82F6", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/result/disc.wxml b/miniprogram/pages/result/disc.wxml new file mode 100644 index 0000000..a3a7797 --- /dev/null +++ b/miniprogram/pages/result/disc.wxml @@ -0,0 +1,81 @@ + + + + + + + 您的DISC性格类型 + {{result.dominantType}}{{result.secondaryType ? ' + ' + result.secondaryType : ''}} + {{result.description.description}} + + + + + + 完整DISC报告 + • 四维得分详情 + • 性格特征与优劣势 + • 职业匹配建议 + + + + 解锁完整报告 + ¥{{payInfo.amountYuan}} / 次 + + + + + + DISC得分详情 + + + {{item.label}} + {{result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}} + + + + + + + + + + 主要性格特征分析 + + 优势 + + + {{item}} + + + + 需要注意的方面 + + + {{item}} + + + + + + 职业匹配度分析 + + + {{item}} + + + + + + + 重新测试 + + + 返回首页 + + + + + diff --git a/miniprogram/pages/result/disc.wxss b/miniprogram/pages/result/disc.wxss new file mode 100644 index 0000000..4c17f45 --- /dev/null +++ b/miniprogram/pages/result/disc.wxss @@ -0,0 +1,197 @@ +/* pages/result/disc.wxss - 按旧版模板重构 */ +.result-page { + width: 100%; + min-height: 100vh; + background-color: #f5f5f5; +} + +.content-scroll { + width: 100%; + height: 100vh; +} + +.content-container { + padding: 32rpx; + padding-bottom: 64rpx; +} + +.type-card, +.analysis-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + margin-bottom: 32rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.type-header { + text-align: center; + margin-bottom: 40rpx; +} + +.type-label { + display: block; + font-size: 32rpx; + color: #333; +} + +.type-value { + display: block; + font-size: 56rpx; + font-weight: bold; + color: #8b5cf6; + margin: 16rpx 0; +} + +.type-description { + display: block; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.section-title, +.card-title { + display: block; + font-size: 34rpx; + font-weight: bold; + color: #333; + margin-bottom: 20rpx; +} + +.score-item { + margin-bottom: 18rpx; +} + +.score-header { + display: flex; + justify-content: space-between; + margin-bottom: 8rpx; +} + +.score-label, +.score-value { + font-size: 28rpx; + color: #666; +} + +.progress-bar-container { + width: 100%; + height: 14rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + overflow: hidden; +} + +.progress-bar { + height: 100%; + border-radius: 8rpx; +} + +.fill-d { background: linear-gradient(90deg, #ef4444 0%, #f87171 100%); } +.fill-i { background: linear-gradient(90deg, #f59e0b 0%, #fbbf24 100%); } +.fill-s { background: linear-gradient(90deg, #10b981 0%, #34d399 100%); } +.fill-c { background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 100%); } + +.trait-section { + margin-bottom: 18rpx; +} + +.trait-title { + display: block; + font-size: 30rpx; + color: #333; + margin-bottom: 8rpx; +} + +.trait-item { + display: flex; + align-items: flex-start; + margin-bottom: 6rpx; +} + +.trait-bullet { + margin-right: 12rpx; + color: #8b5cf6; +} + +.trait-text { + flex: 1; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.career-item { + display: flex; + align-items: center; + margin-bottom: 16rpx; +} + +.career-dot { + width: 14rpx; + height: 14rpx; + background-color: #8b5cf6; + border-radius: 50%; + margin-right: 16rpx; +} + +.career-text { + flex: 1; + font-size: 28rpx; + color: #333; +} + +.action-section { + margin-top: 16rpx; + padding-bottom: 40rpx; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 100% !important; + padding: 28rpx; + border-radius: 16rpx; + margin-bottom: 20rpx; + border: none; + box-sizing: border-box; + overflow: hidden; +} + +.btn::after { + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%); + box-shadow: 0 8rpx 24rpx rgba(59, 130, 246, 0.3); +} + +.btn-outline { + background: #fff; + border: 2rpx solid #3B82F6; +} + +.btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +.btn-text-outline { + font-size: 30rpx; + color: #3B82F6; + font-weight: 500; +} + +.paywall-card { margin-top: 24rpx; border-radius: 24rpx; overflow: hidden; } +.paywall-content { position: relative; } +.paywall-blur { padding: 32rpx 24rpx 140rpx; border-radius: 24rpx; background: rgba(255,255,255,0.7); backdrop-filter: blur(18rpx); } +.paywall-mask { position: absolute; left: 0; top: 0; right: 0; bottom: 0; border-radius: 24rpx; background: linear-gradient(180deg, rgba(255,255,255,0.1), rgba(245,245,245,0.98)); pointer-events: none; } +.paywall-fake-title { display: block; font-size: 30rpx; font-weight: 600; color: #444; margin-bottom: 16rpx; } +.paywall-fake-line { display: block; font-size: 26rpx; color: #888; line-height: 1.8; } +.paywall-btn { position: absolute; left: 5%; right: 5%; width: 90%; bottom: 56rpx; padding: 20rpx 0; border-radius: 999rpx; background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%); box-shadow: 0 8rpx 24rpx rgba(59,130,246,0.35); display: flex; flex-direction: row; align-items: baseline; justify-content: center; gap: 12rpx; } +.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; } +.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; } diff --git a/miniprogram/pages/result/mbti.js b/miniprogram/pages/result/mbti.js new file mode 100644 index 0000000..a921b35 --- /dev/null +++ b/miniprogram/pages/result/mbti.js @@ -0,0 +1,224 @@ +// pages/result/mbti.js - MBTI结果页(支持付费墙 + 历史详情拉取) +const app = getApp() +const payment = require('../../utils/payment') +const { hasPhone, bindPhoneByCode, ensureProfileCompleteAndRedirect } = require('../../utils/phoneAuth.js') + +Page({ + data: { + result: null, + dimensions: [], + mbtiDesc: { + title: '', + description: '', + strengths: [], + weaknesses: [], + careers: [], + relationships: '' + }, + payInfo: { + requiresPayment: false, + isPaid: false, + amountYuan: 0 + }, + testResultId: null, + hasReloadedAfterPay: false, + hasPhone: false + }, + + onLoad(options) { + const id = options && options.id + const type = options && options.type + + if (id && type === 'mbti') { + this.setData({ testResultId: id }) + this.loadDetail(id) + return + } + + const result = wx.getStorageSync('mbtiResult') + if (result) { + this.applyResult(result) + this.initPayInfoFromRuntime('mbti') + } else { + wx.showToast({ title: '暂无测试结果', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }, + + onShow() { + if (!ensureProfileCompleteAndRedirect()) return + this.setData({ hasPhone: hasPhone() }) + }, + + loadDetail(id) { + const apiBase = app.globalData?.apiBase || '' + const token = app.globalData?.token || wx.getStorageSync('token') || '' + if (!apiBase) { + wx.showToast({ title: '配置异常', icon: 'none' }) + return + } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/detail`, + method: 'GET', + header: token ? { Authorization: `Bearer ${token}` } : {}, + data: { id }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const payload = res.data.data || {} + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.applyResult(data) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ payInfo }) + } else { + wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + + applyResult(result) { + if (!result) return + const desc = result.description || {} + const dimensions = (result.dimensionScores && !result.locked) + ? [ + { key: 'EI', left: '外向(E)', right: '内向(I)', ...result.dimensionScores.EI }, + { key: 'SN', left: '感觉(S)', right: '直觉(N)', ...result.dimensionScores.SN }, + { key: 'TF', left: '思考(T)', right: '情感(F)', ...result.dimensionScores.TF }, + { key: 'JP', left: '判断(J)', right: '知觉(P)', ...result.dimensionScores.JP } + ] + : [] + this.setData({ + result, + dimensions, + mbtiDesc: { + title: desc.name || '', + description: desc.description || '', + strengths: desc.strengths || [], + weaknesses: desc.weaknesses || [], + careers: desc.careers || [], + relationships: desc.relationships || '' + } + }) + }, + + initPayInfoFromRuntime(testType) { + app.getRuntimeConfig() + .then((cfg) => { + const pricing = cfg.pricing || {} + const reportRequires = cfg.reportRequiresPayment || {} + const requiresPayment = !!(reportRequires && reportRequires[testType]) + const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0) + this.setData({ + payInfo: { + requiresPayment, + isPaid: false, + amountYuan + } + }) + }) + .catch(() => { + this.setData({ + payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } + }) + }) + }, + + unlockFullReport() { + const { payInfo, testResultId, hasReloadedAfterPay } = this.data + if (!payInfo.requiresPayment || payInfo.isPaid) return + app.ensureLogin && app.ensureLogin().then((logged) => { + if (!logged) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + payment.purchaseMbtiTest({ + testResultId: testResultId || undefined, + success: () => { + wx.showToast({ title: '已解锁完整报告', icon: 'success' }) + this.setData({ 'payInfo.isPaid': true }) + if (testResultId && !hasReloadedAfterPay) { + this.setData({ hasReloadedAfterPay: true }) + setTimeout(() => this.loadDetail(testResultId), 500) + } + }, + fail: () => {} + }) + }) + }, + + // 付费解锁按钮:就地触发微信手机号授权,然后调用 unlockFullReport + onGetPhoneNumberForMbtiPay(e) { + if (!ensureProfileCompleteAndRedirect()) return + const { code, errMsg } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + if (!hasPhone()) { + wx.showToast({ title: '需要授权手机号才能继续', icon: 'none' }) + return + } + this.unlockFullReport() + return + } + if (!code) { + if (hasPhone()) { + this.unlockFullReport() + } else { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + } + return + } + bindPhoneByCode(code) + .then(() => { + this.setData({ hasPhone: true }) + this.unlockFullReport() + }) + .catch(() => { + // 保持在当前页,等待用户重新点击 + }) + }, + + retakeTest() { + if (!this.data.testResultId) { + wx.removeStorageSync('mbtiResult') + } + wx.navigateTo({ url: '/pages/test/mbti' }) + }, + + goHome() { + const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal' + if (scope === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + }, + + onShareAppMessage() { + const result = this.data.result + const { getSharePathByScope } = require('../../utils/share') + return { + title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`, + path: getSharePathByScope('/pages/index/index'), + imageUrl: '/images/share-mbti.png' + } + }, + + onShareTimeline() { + const result = this.data.result + const { buildShareQuery } = require('../../utils/share') + return { + title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`, + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/result/mbti.json b/miniprogram/pages/result/mbti.json new file mode 100644 index 0000000..4f43927 --- /dev/null +++ b/miniprogram/pages/result/mbti.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "MBTI结果", + "navigationBarBackgroundColor": "#FF6B8A", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/result/mbti.wxml b/miniprogram/pages/result/mbti.wxml new file mode 100644 index 0000000..8c3932e --- /dev/null +++ b/miniprogram/pages/result/mbti.wxml @@ -0,0 +1,107 @@ + + + + + + + 您的MBTI性格类型 + {{result.mbtiType}} + {{mbtiDesc.title}} + {{mbtiDesc.description}} + + + + + + + 完整性格分析 + • 四维得分与主导倾向 + • 优势与需要注意的方面 + • 职业匹配与人际关系建议 + + + + + + + + + + + + + {{item.left}} + {{item.right}} + + + + + + {{item.dominant}} + {{item.percentage}}% + + + + + + + 性格特征分析 + + 优势 + + + {{item}} + + + + + 需要注意的方面 + + + {{item}} + + + + + + 职业匹配度分析 + + + {{item}} + + + + + 人际关系分析 + {{mbtiDesc.relationships}} + + + + + + 重新测试 + + + 返回首页 + + + + + diff --git a/miniprogram/pages/result/mbti.wxss b/miniprogram/pages/result/mbti.wxss new file mode 100644 index 0000000..ccbf617 --- /dev/null +++ b/miniprogram/pages/result/mbti.wxss @@ -0,0 +1,240 @@ +/* pages/result/mbti.wxss - 按旧版模板重构 */ +.result-page { + width: 100%; + min-height: 100vh; + background-color: #f5f5f5; +} + +.content-scroll { + width: 100%; + height: 100vh; +} + +.content-container { + padding: 32rpx; + padding-bottom: 64rpx; +} + +.type-card, +.analysis-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + margin-bottom: 32rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.type-header { + text-align: center; + margin-bottom: 40rpx; +} + +.type-label { + display: block; + font-size: 32rpx; + color: #333; + margin-bottom: 16rpx; +} + +.type-value { + display: block; + font-size: 72rpx; + font-weight: bold; + color: #8b5cf6; + margin: 16rpx 0; +} + +.type-title { + display: block; + font-size: 34rpx; + color: #a78bfa; + margin-bottom: 20rpx; +} + +.type-description { + display: block; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.dimensions-section .dimension-item { + margin-bottom: 28rpx; +} + +.dimension-labels, +.dimension-values { + display: flex; + justify-content: space-between; +} + +.label-left, +.label-right, +.value-left, +.value-right { + font-size: 24rpx; + color: #666; +} + +.progress-bar-container { + width: 100%; + height: 16rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + margin: 8rpx 0; + overflow: hidden; +} + +.progress-bar { + height: 100%; + border-radius: 8rpx; + background: linear-gradient(90deg, #8b5cf6 0%, #a78bfa 100%); +} + +.card-title { + display: block; + font-size: 36rpx; + font-weight: bold; + color: #333; + margin-bottom: 24rpx; +} + +.trait-section { + margin-bottom: 24rpx; +} + +.trait-title { + display: block; + font-size: 30rpx; + color: #333; + margin-bottom: 12rpx; +} + +.trait-item { + display: flex; + align-items: flex-start; + margin-bottom: 8rpx; +} + +.trait-bullet { + margin-right: 12rpx; + color: #8b5cf6; +} + +.trait-text { + flex: 1; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.career-item { + display: flex; + align-items: center; + margin-bottom: 18rpx; +} + +.career-dot { + width: 14rpx; + height: 14rpx; + border-radius: 50%; + background-color: #8b5cf6; + margin-right: 18rpx; +} + +.career-text { + flex: 1; + font-size: 28rpx; + color: #333; +} + +.relationship-text { + font-size: 28rpx; + color: #666; + line-height: 1.7; +} + +.action-section { + margin-top: 16rpx; + padding-bottom: 40rpx; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 100% !important; + padding: 28rpx; + border-radius: 16rpx; + margin-bottom: 20rpx; + border: none; + box-sizing: border-box; + overflow: hidden; +} + +/* 消除微信 button 伪元素自带边框 */ +.btn::after { + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%); + box-shadow: 0 8rpx 24rpx rgba(255, 107, 138, 0.3); +} + +.btn-outline { + background: #fff; + border: 2rpx solid #FF6B8A; +} + +.btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +.btn-text-outline { + font-size: 30rpx; + color: #FF6B8A; + font-weight: 500; +} + +/* 付费墙 */ +.paywall-card { + margin-top: 24rpx; + border-radius: 24rpx; + overflow: hidden; +} +.paywall-content { position: relative; } +.paywall-blur { + padding: 32rpx 24rpx 140rpx; + border-radius: 24rpx; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(18rpx); +} +.paywall-mask { + position: absolute; + left: 0; top: 0; right: 0; bottom: 0; + border-radius: 24rpx; + background: linear-gradient(180deg, rgba(255,255,255,0.1), rgba(245,245,245,0.98)); + pointer-events: none; +} +.paywall-fake-title { display: block; font-size: 30rpx; font-weight: 600; color: #444; margin-bottom: 16rpx; } +.paywall-fake-line { display: block; font-size: 26rpx; color: #888; line-height: 1.8; } +.paywall-btn { + position: absolute; + left: 5%; right: 5%; + width: 90%; + bottom: 56rpx; + padding: 20rpx 0; + border-radius: 999rpx; + background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%); + box-shadow: 0 8rpx 24rpx rgba(255, 107, 138, 0.35); + display: flex; + flex-direction: row; + align-items: baseline; + justify-content: center; + gap: 12rpx; +} +.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; } +.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; } diff --git a/miniprogram/pages/result/pdp.js b/miniprogram/pages/result/pdp.js new file mode 100644 index 0000000..51ae737 --- /dev/null +++ b/miniprogram/pages/result/pdp.js @@ -0,0 +1,157 @@ +// pages/result/pdp.js - PDP结果页(支持付费墙 + 历史详情拉取) +const app = getApp() +const payment = require('../../utils/payment') + +const PDP_KEYS = ['Tiger', 'Peacock', 'Koala', 'Owl', 'Chameleon'] + +function toIntPercent(v) { + if (v == null) return 0 + const n = typeof v === 'number' ? v : Number(v) + return Number.isFinite(n) ? Math.round(n) : 0 +} + +function withPercentagesInt(data) { + if (!data) return data + const p = data.percentages || {} + const out = { ...data } + const ints = {} + PDP_KEYS.forEach((k) => { ints[k] = toIntPercent(p[k] ?? p[k.toLowerCase()]) }) + out.percentagesInt = ints + return out +} + +Page({ + data: { + result: null, + typeList: [ + { type: 'Tiger', emoji: '🐅', label: '老虎型', colorClass: 'fill-tiger' }, + { type: 'Peacock', emoji: '🦚', label: '孔雀型', colorClass: 'fill-peacock' }, + { type: 'Koala', emoji: '🐨', label: '考拉型', colorClass: 'fill-koala' }, + { type: 'Owl', emoji: '🦉', label: '猫头鹰型', colorClass: 'fill-owl' }, + { type: 'Chameleon', emoji: '🦎', label: '变色龙型', colorClass: 'fill-chameleon' } + ], + payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }, + testResultId: null, + hasReloadedAfterPay: false + }, + + onLoad(options) { + const id = options && options.id + const type = options && options.type + if (id && type === 'pdp') { + this.setData({ testResultId: id }) + this.loadDetail(id) + return + } + const result = wx.getStorageSync('pdpResult') + if (result) { + this.setData({ result: withPercentagesInt(result) }) + this.initPayInfoFromRuntime('pdp') + } else { + wx.showToast({ title: '暂无测试结果', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + } + }, + + loadDetail(id) { + const apiBase = app.globalData?.apiBase || '' + const token = app.globalData?.token || wx.getStorageSync('token') || '' + if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/detail`, + method: 'GET', + header: token ? { Authorization: `Bearer ${token}` } : {}, + data: { id }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const payload = res.data.data || {} + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.setData({ result: withPercentagesInt(data) }) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ payInfo }) + } else { + wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + + initPayInfoFromRuntime(testType) { + app.getRuntimeConfig() + .then((cfg) => { + const reportRequires = cfg.reportRequiresPayment || {} + const pricing = cfg.pricing || {} + const requiresPayment = !!(reportRequires && reportRequires[testType]) + const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0) + this.setData({ + payInfo: { requiresPayment, isPaid: false, amountYuan } + }) + }) + .catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } })) + }, + + unlockFullReport() { + const { payInfo, testResultId, hasReloadedAfterPay } = this.data + if (!payInfo.requiresPayment || payInfo.isPaid) return + app.ensureLogin && app.ensureLogin().then((logged) => { + if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return } + payment.purchasePdpTest({ + testResultId: testResultId || undefined, + success: () => { + wx.showToast({ title: '已解锁完整报告', icon: 'success' }) + this.setData({ 'payInfo.isPaid': true }) + if (testResultId && !hasReloadedAfterPay) { + this.setData({ hasReloadedAfterPay: true }) + setTimeout(() => this.loadDetail(testResultId), 500) + } + }, + fail: () => {} + }) + }) + }, + + retakeTest() { + if (!this.data.testResultId) { + wx.removeStorageSync('pdpResult') + } + wx.navigateTo({ url: '/pages/test/pdp' }) + }, + + goHome() { + const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal' + if (scope === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + }, + + onShareAppMessage() { + const result = this.data.result + const { getSharePathByScope } = require('../../utils/share') + return { + title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`, + path: getSharePathByScope('/pages/index/index') + } + }, + + onShareTimeline() { + const result = this.data.result + const { buildShareQuery } = require('../../utils/share') + return { + title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`, + query: buildShareQuery() + } + } +}) diff --git a/miniprogram/pages/result/pdp.json b/miniprogram/pages/result/pdp.json new file mode 100644 index 0000000..e924a5f --- /dev/null +++ b/miniprogram/pages/result/pdp.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "PDP结果", + "navigationBarBackgroundColor": "#F59E0B", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/result/pdp.wxml b/miniprogram/pages/result/pdp.wxml new file mode 100644 index 0000000..523da7d --- /dev/null +++ b/miniprogram/pages/result/pdp.wxml @@ -0,0 +1,87 @@ + + + + + + + PDP性格类型 + {{result.description.type || result.dominantType}}{{result.description.emoji || ''}} + {{result.description.title}} + {{result.description.description}} + + + + + + 完整PDP报告 + • 五维得分详情 + • 性格特征与团队角色 + • 推荐职业 + + + + 解锁完整报告 + ¥{{payInfo.amountYuan}} / 次 + + + + + + PDP得分详情 + + + {{item.emoji}} {{item.label}} + {{result.percentagesInt && result.percentagesInt[item.type] != null ? result.percentagesInt[item.type] + '%' : '0%'}} + + + + + + + + + + 性格特征 + + 优势 + + + {{item}} + + + + 需要注意的方面 + + + {{item}} + + + + + + 团队角色 + {{result.description.teamRole}} + + + + 推荐职业 + + + {{item}} + + + + + + + 重新测试 + + + 返回首页 + + + + + diff --git a/miniprogram/pages/result/pdp.wxss b/miniprogram/pages/result/pdp.wxss new file mode 100644 index 0000000..e70c699 --- /dev/null +++ b/miniprogram/pages/result/pdp.wxss @@ -0,0 +1,211 @@ +/* pages/result/pdp.wxss - 按旧版模板重构 */ +.result-page { + width: 100%; + min-height: 100vh; + background-color: #f5f5f5; +} + +.content-scroll { + width: 100%; + height: 100vh; +} + +.content-container { + padding: 32rpx; + padding-bottom: 64rpx; +} + +.type-card, +.analysis-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + margin-bottom: 32rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.type-header { + text-align: center; + margin-bottom: 40rpx; +} + +.type-label { + display: block; + font-size: 30rpx; + color: #333; +} + +.type-value { + display: block; + font-size: 56rpx; + font-weight: bold; + color: #8b5cf6; + margin: 14rpx 0; +} + +.type-title { + display: block; + font-size: 34rpx; + color: #333; + margin-bottom: 16rpx; +} + +.type-description { + display: block; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.section-title, +.card-title { + display: block; + font-size: 34rpx; + font-weight: bold; + color: #333; + margin-bottom: 20rpx; +} + +.score-item { + margin-bottom: 18rpx; +} + +.score-header { + display: flex; + justify-content: space-between; + margin-bottom: 8rpx; +} + +.score-label, +.score-value { + font-size: 28rpx; + color: #666; +} + +.progress-bar-container { + width: 100%; + height: 14rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + overflow: hidden; +} + +.progress-bar { + height: 100%; + border-radius: 8rpx; +} + +.fill-tiger { background: #ef4444; } +.fill-peacock { background: #eab308; } +.fill-koala { background: #22c55e; } +.fill-owl { background: #3b82f6; } +.fill-chameleon { background: #a855f7; } + +.trait-section { + margin-bottom: 18rpx; +} + +.trait-title { + display: block; + font-size: 30rpx; + color: #333; + margin-bottom: 8rpx; +} + +.trait-item { + display: flex; + align-items: flex-start; + margin-bottom: 6rpx; +} + +.trait-bullet { + margin-right: 12rpx; + color: #8b5cf6; +} + +.trait-text { + flex: 1; + font-size: 28rpx; + color: #666; + line-height: 1.6; +} + +.relationship-text { + font-size: 28rpx; + color: #666; + line-height: 1.7; +} + +.career-item { + display: flex; + align-items: center; + margin-bottom: 16rpx; +} + +.career-dot { + width: 14rpx; + height: 14rpx; + background-color: #8b5cf6; + border-radius: 50%; + margin-right: 16rpx; +} + +.career-text { + flex: 1; + font-size: 28rpx; + color: #333; +} + +.action-section { + margin-top: 16rpx; + padding-bottom: 40rpx; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 100% !important; + padding: 28rpx; + border-radius: 16rpx; + margin-bottom: 20rpx; + border: none; + box-sizing: border-box; + overflow: hidden; +} + +.btn::after { + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%); + box-shadow: 0 8rpx 24rpx rgba(245, 158, 11, 0.3); +} + +.btn-outline { + background: #fff; + border: 2rpx solid #F59E0B; +} + +.btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +.btn-text-outline { + font-size: 30rpx; + color: #F59E0B; + font-weight: 500; +} + +.paywall-card { margin-top: 24rpx; border-radius: 24rpx; overflow: hidden; } +.paywall-content { position: relative; } +.paywall-blur { padding: 32rpx 24rpx 140rpx; border-radius: 24rpx; background: rgba(255,255,255,0.7); backdrop-filter: blur(18rpx); } +.paywall-mask { position: absolute; left: 0; top: 0; right: 0; bottom: 0; border-radius: 24rpx; background: linear-gradient(180deg, rgba(255,255,255,0.1), rgba(245,245,245,0.98)); pointer-events: none; } +.paywall-fake-title { display: block; font-size: 30rpx; font-weight: 600; color: #444; margin-bottom: 16rpx; } +.paywall-fake-line { display: block; font-size: 26rpx; color: #888; line-height: 1.8; } +.paywall-btn { position: absolute; left: 5%; right: 5%; width: 90%; bottom: 56rpx; padding: 20rpx 0; border-radius: 999rpx; background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%); box-shadow: 0 8rpx 24rpx rgba(245,158,11,0.35); display: flex; flex-direction: row; align-items: baseline; justify-content: center; gap: 12rpx; } +.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; } +.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; } diff --git a/miniprogram/pages/result/resume.js b/miniprogram/pages/result/resume.js new file mode 100644 index 0000000..bb18907 --- /dev/null +++ b/miniprogram/pages/result/resume.js @@ -0,0 +1,524 @@ +// pages/result/resume.js - 简历综合分析结果页 +const app = getApp() +const payment = require('../../utils/payment') + +Page({ + data: { + loading: true, + error: '', + content: '', + sections: [], + resumeData: null, + fileUrl: '', + progress: 0, + analyzingTip: '正在准备分析数据...', + analyzingTitle: '综合分析生成中', + // 支付相关 + payInfo: { + requiresPayment: false, + isPaid: false, + amountYuan: 0 + }, + testResultId: 0, + paying: false + }, + + onLoad(options) { + const fileUrl = options.fileUrl ? decodeURIComponent(options.fileUrl) : '' + this.setData({ fileUrl }) + + // 从历史记录进入:直接读已存的数据,不重新生成 + if (options.id && options.type === 'resume') { + this.loadFromHistory(options.id) + } else { + this.fetchResumeAnalysis() + } + }, + + loadFromHistory(id) { + this.setData({ loading: true, error: '', content: '', sections: [], resumeData: null }) + const apiBase = (app.globalData && app.globalData.apiBase) || wx.getStorageSync('apiBase') || '' + const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' + wx.request({ + url: `${apiBase}/api/test/detail?id=${id}`, + method: 'GET', + timeout: 15000, + header: { 'Authorization': token ? `Bearer ${token}` : '' }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.data) { + const payload = res.data.data + let data = payload.data || payload + + // 兼容:历史数据可能是字符串,或包在 content 里的 JSON 字符串 + data = this.normalizeStructuredData(data) + + const structured = !!data && data._structured === true + + let content = '' + let sections = [] + + if (structured) { + const built = this.buildSectionsFromStructured(data) + content = built.content + sections = built.sections + } else { + const raw = data.content || '' + content = raw + sections = this.parseContent(raw) + } + const requiresPayment = !!payload.requiresPayment + const isPaid = !!payload.isPaid + const amountYuan = payload.paidAmount ? payload.paidAmount / 100 : 0 + this.setData({ + loading: false, + testResultId: payload.id || parseInt(id) || 0, + content: (!requiresPayment || isPaid) ? content : '', + sections: (!requiresPayment || isPaid) ? sections : [], + resumeData: structured ? data : null, + payInfo: { requiresPayment: requiresPayment && !isPaid, isPaid, amountYuan } + }) + } else { + this.setData({ loading: false, error: '加载历史记录失败,请返回重试。' }) + } + }, + fail: () => { + this.setData({ loading: false, error: '网络请求失败,请检查网络后重试。' }) + } + }) + }, + + fetchResumeAnalysis() { + this.setData({ loading: true, error: '', content: '', sections: [], resumeData: null, progress: 0 }) + + const tips = [ + '正在汇总各项测评结果...', + '正在解析简历内容...', + 'AI 正在进行深度匹配分析...', + '正在生成综合评估报告...', + '分析即将完成,请稍候...' + ] + let progress = 0 + let tipIndex = 0 + + // 进度动画 + const timer = setInterval(() => { + progress += 2 + if (progress > 98) progress = 98 + if (progress > (tipIndex + 1) * 18 && tipIndex < tips.length - 1) tipIndex++ + this.setData({ progress: Math.floor(progress), analyzingTip: tips[tipIndex] }) + }, 200) + + const apiBase = (app.globalData && app.globalData.apiBase) || wx.getStorageSync('apiBase') || '' + const token = (app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' + + // 获取 enterpriseId + const gd = app.globalData || {} + const storedUser = wx.getStorageSync('userInfo') || null + const enterpriseId = gd.enterpriseIdFromScene + || (gd.userInfo && gd.userInfo.enterpriseId) + || (storedUser && storedUser.enterpriseId) + || null + + const postData = {} + if (this.data.fileUrl) postData.fileUrl = this.data.fileUrl + if (enterpriseId) postData.enterpriseId = enterpriseId + + wx.request({ + url: `${apiBase}/api/resume/analyze`, + method: 'POST', + timeout: 120000, + header: { + 'Content-Type': 'application/json', + 'Authorization': token ? `Bearer ${token}` : '' + }, + data: postData, + success: (res) => { + clearInterval(timer) + if (res.statusCode === 200 && res.data) { + if (res.data.code === 200) { + let d = res.data.data || {} + const p = d._payment || {} + const requiresPayment = !!p.requiresPayment + const amountYuan = p.amountYuan || 0 + const resultId = d._testResultId || 0 + + // 兼容:API 直接返回结构化对象,或把 JSON 放在 content 里 + d = this.normalizeStructuredData(d) + + const structured = !!d && d._structured === true + + let content = '' + let sections = [] + + if (structured) { + const built = this.buildSectionsFromStructured(d) + content = built.content + sections = built.sections + } else { + const raw = d.content || '' + content = raw || '分析已完成,但未返回可展示的内容。' + sections = this.parseContent(raw) + } + + this.setData({ + loading: false, + progress: 100, + testResultId: resultId, + content: requiresPayment ? '' : content, + sections: requiresPayment ? [] : sections, + resumeData: structured ? d : null, + payInfo: { requiresPayment, isPaid: !requiresPayment, amountYuan } + }) + } else { + this.setData({ + loading: false, + error: res.data.message || res.data.msg || '综合分析失败,请稍后重试。' + }) + } + } else { + this.setData({ + loading: false, + error: '服务器返回异常,请稍后重试。' + }) + } + }, + fail: (err) => { + clearInterval(timer) + const isTimeout = err && (err.errMsg || '').indexOf('timeout') !== -1 + this.setData({ + loading: false, + error: isTimeout ? 'AI 分析耗时较长,请点击「重新生成」再试。' : '网络请求失败,请检查网络后重试。' + }) + } + }) + }, + + // 支付解锁 + doPay() { + if (this.data.paying) return + this.setData({ paying: true }) + const testResultId = this.data.testResultId || 0 + payment.purchaseResumeAnalysis({ + testResultId: testResultId > 0 ? testResultId : undefined, + success: () => { + this.setData({ paying: false, 'payInfo.requiresPayment': false, 'payInfo.isPaid': true }) + // 支付成功后重新拉取完整内容 + if (testResultId > 0) { + this.loadFromHistory(testResultId) + } + }, + fail: (err) => { + this.setData({ paying: false }) + wx.showToast({ title: (err && err.message) || '支付失败,请重试', icon: 'none' }) + } + }) + }, + + goHome() { + const scope = (app.globalData && app.globalData.appScope) || 'personal' + if (scope === 'enterprise') { + wx.navigateTo({ url: '/pages/enterprise/index' }) + } else { + wx.switchTab({ url: '/pages/index/index' }) + } + }, + + onShareAppMessage() { + return { + title: '我刚刚完成了一份人才简历综合分析报告,快来看看吧!', + path: '/pages/index/index' + } + }, + + onPullDownRefresh() { + this.fetchResumeAnalysis() + wx.stopPullDownRefresh() + }, + + // 智能解析 AI 输出的长文本,按标题分块 + parseContent(content) { + if (!content) return []; + + // 1. 预处理:去掉首尾空行 + content = content.trim(); + + // 2. 尝试分割 + // 使用正则匹配头部标记:## Title 或 ### Title 或 **一、Title** 或 一、**Title** + const parts = content.split(/\n(?=#{2,4}\s+|\*\*?[\d一二三四五六七八九十]+[、\.].*?\*\*?|【.*?】)/); + const sections = []; + + parts.forEach(part => { + let title = ''; + let body = ''; + + // 匹配 ## 标题 + const hMatch = part.match(/^(?:#{2,4}\s+)(.*?)\n([\s\S]*)$/m); + // 匹配 **一、标题** 或 一、**标题** + const bMatch = part.match(/^(?:\*\*?[\d一二三四五六七八九十]+[、\.].*?\*\*?)([\s\S]*)$/m); + // 匹配 【标题】 + const kMatch = part.match(/^(?:【(.*?)】)\n?([\s\S]*)$/m); + + if (hMatch) { + title = hMatch[1].replace(/[\*#]/g, '').trim(); + body = hMatch[2].trim(); + } else if (kMatch) { + title = kMatch[1].trim(); + body = kMatch[2].trim(); + } else if (bMatch) { + const rawTitleMatch = part.match(/^(\*\*?.*?\*\*?)/); + title = rawTitleMatch ? rawTitleMatch[1].replace(/[\*#]/g, '').trim() : ''; + body = bMatch[1].trim(); + } else { + body = part.trim(); + } + + // 清洗正文中的 Markdown 符号 + body = this.cleanMarkdown(body); + + if (title || body) { + sections.push({ + title: title || '报告详情', + icon: this.getSectionIcon(title), + body: body + }); + } + }); + + return sections; + }, + + // 兼容各种后端返回形态,统一整理为结构化对象或普通文本 + normalizeStructuredData(raw) { + let data = raw + + // 字符串:尝试直接当 JSON 解析 + if (typeof data === 'string') { + try { + const parsed = JSON.parse(data) + if (parsed && typeof parsed === 'object') { + data = parsed + } + } catch (e) {} + } + + // 包在 content 里的 JSON 字符串 + if (data && typeof data === 'object' && typeof data.content === 'string') { + const text = data.content.trim() + if (text.startsWith('{')) { + try { + const parsedInner = JSON.parse(text) + if (parsedInner && typeof parsedInner === 'object') { + // 把外层 fileUrl 等透传下去 + if (data.fileUrl && !parsedInner.fileUrl) { + parsedInner.fileUrl = data.fileUrl + } + data = parsedInner + } + } catch (e) {} + } + } + + // 标记是否为结构化 JSON + if ( + data && + typeof data === 'object' && + (data.version === 2 || + !!data.overview || + !!data.portrait || + !!data.hrView || + !!data.bossView) + ) { + data._structured = true + } + + return data + }, + + // 从结构化 JSON 构建前端展示区块 + buildSectionsFromStructured(data) { + const sections = [] + + if (data.overview) { + sections.push({ + title: '整体人才画像', + icon: '👤', + body: this.cleanMarkdown(data.overview) + }) + } + + if (data.resumeHighlights) { + sections.push({ + title: '简历要点', + icon: '📄', + body: this.cleanMarkdown(data.resumeHighlights) + }) + } + + const portrait = data.portrait || {} + if ( + (portrait.coreStrengths && portrait.coreStrengths.length) || + (portrait.coreRisks && portrait.coreRisks.length) || + portrait.workStyle + ) { + const lines = [] + if (portrait.coreStrengths && portrait.coreStrengths.length) { + lines.push('【核心优势】') + lines.push(...portrait.coreStrengths.map((s) => '- ' + s)) + } + if (portrait.coreRisks && portrait.coreRisks.length) { + lines.push('') + lines.push('【潜在风险】') + lines.push(...portrait.coreRisks.map((s) => '- ' + s)) + } + if (portrait.workStyle) { + lines.push('') + lines.push('【工作风格】') + lines.push(portrait.workStyle) + } + sections.push({ + title: '人才画像', + icon: '🌈', + body: this.cleanMarkdown(lines.join('\n')) + }) + } + + const hrView = data.hrView || {} + const role = hrView.roleRecommend || {} + if ((role.bestFit && role.bestFit.length) || (role.notSuitable && role.notSuitable.length)) { + const lines = [] + if (role.bestFit && role.bestFit.length) { + lines.push('【推荐岗位】') + lines.push(...role.bestFit.map((s) => '- ' + s)) + } + if (role.notSuitable && role.notSuitable.length) { + lines.push('') + lines.push('【不适合场景】') + lines.push(...role.notSuitable.map((s) => '- ' + s)) + } + sections.push({ + title: '岗位匹配建议(HR视角)', + icon: '🧭', + body: this.cleanMarkdown(lines.join('\n')) + }) + } + + if (hrView.lifecycle) { + const lc = hrView.lifecycle + const parts = [] + if (lc.onboarding) parts.push('【入职】' + lc.onboarding) + if (lc.probation) parts.push('【试用期】' + lc.probation) + if (lc.growth) parts.push('【成长】' + lc.growth) + if (lc.retention) parts.push('【留存】' + lc.retention) + if (parts.length) { + sections.push({ + title: '员工全生命周期预测', + icon: '📈', + body: this.cleanMarkdown(parts.join('\n\n')) + }) + } + } + + if (hrView.performance || hrView.teamFit || hrView.complianceRisk) { + const lines = [] + if (hrView.performance) { + lines.push('【绩效潜力】' + (hrView.performance.potential || '')) + if (hrView.performance.drivers && hrView.performance.drivers.length) { + lines.push('驱动因素:' + hrView.performance.drivers.join(';')) + } + if (hrView.performance.risks && hrView.performance.risks.length) { + lines.push('风险提示:' + hrView.performance.risks.join(';')) + } + lines.push('') + } + if (hrView.teamFit) { + if (hrView.teamFit.bestTeam) { + lines.push('【适配团队】' + hrView.teamFit.bestTeam) + } + if (hrView.teamFit.manageAdvice) { + lines.push('【管理建议】' + hrView.teamFit.manageAdvice) + } + lines.push('') + } + if (hrView.complianceRisk) { + const level = hrView.complianceRisk.level || '' + const notes = hrView.complianceRisk.notes || '' + lines.push('【合规风险】' + level + (notes ? ';' + notes : '')) + } + if (lines.length) { + sections.push({ + title: '绩效 · 团队 · 合规', + icon: '📊', + body: this.cleanMarkdown(lines.join('\n')) + }) + } + } + + const bossView = data.bossView || {} + if (bossView.headline || (bossView.metrics && bossView.metrics.length) || bossView.costInsight) { + const lines = [] + if (bossView.headline) { + lines.push('【一句话结论】' + bossView.headline) + } + if (bossView.metrics && bossView.metrics.length) { + lines.push('') + lines.push('【关键指标】') + bossView.metrics.forEach((m) => { + if (!m) return + lines.push(`- ${m.label}:${m.value || ''}`) + }) + } + if (bossView.costInsight) { + lines.push('') + lines.push('【用人成本与产出】' + bossView.costInsight) + } + sections.push({ + title: '给老板看的摘要', + icon: '💼', + body: this.cleanMarkdown(lines.join('\n')) + }) + } + + const mainContent = + data.overview || + data.resumeHighlights || + (data.portrait && data.portrait.workStyle) || + '' + + return { + content: this.cleanMarkdown(mainContent), + sections + } + }, + + // 清洗正文中的 Markdown 符号 + cleanMarkdown(text) { + if (!text) return ''; + return text + .replace(/\*\*\*(.+?)\*\*\*/g, '$1') // ***bold italic*** + .replace(/\*\*(.+?)\*\*/g, '$1') // **bold** + .replace(/\*(.+?)\*/g, '$1') // *italic* + .replace(/^#{1,6}\s+/gm, '') // ## 标题符号 + .replace(/^[-*]\s+/gm, '• ') // - 无序列表 → 圆点 + .replace(/^\d+\.\s+/gm, (m) => m) // 保留有序列表编号 + .replace(/`(.+?)`/g, '$1') // `code` + .replace(/\[(.+?)\]\(.+?\)/g, '$1') // [链接](url) → 链接文字 + .trim(); + }, + + // 根据标题关键词匹配图标 + getSectionIcon(title) { + if (!title) return '📝'; + const map = { + '评估': '📊', '综述': '📊', '评价': '📊', '总评': '📊', + '优势': '🌟', '特质': '🌟', '性格': '🌟', + '岗位': '🎯', '适配': '🎯', '匹配': '🎯', '核心': '🎯', + '职业': '💼', '发展': '💼', '建议': '💡', '提升': '💡', + '面试': '🗣️', '录用': '✅', '风险': '⚠️' + }; + for (let key in map) { + if (title.indexOf(key) !== -1) return map[key]; + } + return '📝'; + } +}) + diff --git a/miniprogram/pages/result/resume.json b/miniprogram/pages/result/resume.json new file mode 100644 index 0000000..de5d9c8 --- /dev/null +++ b/miniprogram/pages/result/resume.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "简历综合分析" +} + diff --git a/miniprogram/pages/result/resume.wxml b/miniprogram/pages/result/resume.wxml new file mode 100644 index 0000000..316f1f1 --- /dev/null +++ b/miniprogram/pages/result/resume.wxml @@ -0,0 +1,86 @@ + + + + + + + + + {{analyzingTitle || '正在分析中'}} + {{analyzingTip}} + + + + 这个过程可能需要30秒到1分钟,请耐心等待... + + + + + + + + + {{error ? '⚠️' : '📋'}} + + {{error ? '生成失败' : '人才综合评估报告'}} + {{error ? '请检查后重新生成' : '基于测评结果 · AI 综合分析'}} + + + + + + 失败原因 + {{error}} + + 点击重新生成 + + + + + + 🔒 + 报告已生成,需付费解锁 + 完整的简历综合分析报告已生成\n支付后即可查看全部内容 + + 解锁价格 + ¥{{payInfo.amountYuan}} + + + {{paying ? '支付中...' : '立即支付解锁'}} + + + + + + + + {{item.icon}} + {{item.title}} + + + {{item.body}} + + + + + + + 综合分析综评 + {{content}} + + + + + + + 重新分析生成 + + + 返回首页 + + + + + diff --git a/miniprogram/pages/result/resume.wxss b/miniprogram/pages/result/resume.wxss new file mode 100644 index 0000000..d7ebde7 --- /dev/null +++ b/miniprogram/pages/result/resume.wxss @@ -0,0 +1,348 @@ +/* pages/result/resume.wxss - 简历综合分析结果页 */ +.container { + min-height: 100vh; + background-color: #f5f5f5; + padding: 0 24rpx 60rpx; +} + +/* ========== 分析中模态框 ========== */ +.analyzing-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.analyzing-content { + background-color: #fff; + border-radius: 24rpx; + padding: 80rpx 60rpx; + width: 560rpx; + display: flex; + flex-direction: column; + align-items: center; + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15); +} + +.analyzing-icon { + width: 120rpx; + height: 120rpx; + margin-bottom: 40rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.spinner { + width: 120rpx; + height: 120rpx; + border: 8rpx solid #e0e0e0; + border-top-color: #e63946; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.analyzing-title { + font-size: 36rpx; + font-weight: 600; + color: #333; + margin-bottom: 16rpx; +} + +.analyzing-desc { + font-size: 28rpx; + color: #e63946; + margin-bottom: 24rpx; + text-align: center; + font-weight: 500; +} + +.analyzing-bar { + width: 400rpx; + height: 12rpx; + background: #e5e5e5; + border-radius: 6rpx; + overflow: hidden; + margin-bottom: 24rpx; +} + +.analyzing-bar-fill { + height: 100%; + background: linear-gradient(90deg, #e63946, #ff6b9d); + border-radius: 6rpx; + transition: width 0.3s ease; +} + +.analyzing-hint { + font-size: 24rpx; + color: #999; + text-align: center; + line-height: 1.6; +} + +/* ========== 顶部 Hero 区域 ========== */ +.hero-banner { + display: flex; + align-items: center; + border-radius: 20rpx; + padding: 32rpx 28rpx; + margin: 24rpx 0; + gap: 20rpx; +} + +.hero-success { + background: linear-gradient(135deg, #1a1a2e 0%, #3a3a5c 100%); +} + +.hero-error { + background: linear-gradient(135deg, #c62828 0%, #e53935 100%); +} + +.hero-icon { + font-size: 64rpx; + line-height: 1; + flex-shrink: 0; +} + +.hero-text-wrap { + display: flex; + flex-direction: column; + gap: 8rpx; +} + +.hero-title { + font-size: 34rpx; + font-weight: 700; + color: #fff; + display: block; +} + +.hero-sub { + font-size: 24rpx; + color: rgba(255, 255, 255, 0.65); + display: block; +} + +/* ========== 通用卡片 ========== */ +.card { + background-color: #fff; + border-radius: 16rpx; + padding: 40rpx 32rpx; + margin-bottom: 24rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); + word-wrap: break-word; +} + +.card-title { + font-size: 34rpx; + font-weight: 700; + color: #1a1a2e; + display: block; + margin-bottom: 24rpx; + border-left: 8rpx solid #e63946; + padding-left: 20rpx; +} + +.card-text { + font-size: 28rpx; + color: #333; + line-height: 1.8; + display: block; + white-space: pre-wrap; +} + +.error-title { + color: #f44336; + border-left-color: #f44336; +} + +.error-message { + color: #666; + background-color: #f9f9f9; + padding: 20rpx; + border-radius: 12rpx; +} + +.action-btn-retry { + margin-top: 32rpx; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + height: 88rpx; + border-radius: 44rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 6rpx 16rpx rgba(230, 57, 70, 0.25); +} + +.retry-text { + font-size: 30rpx; + color: #fff; + font-weight: 600; +} + +/* ========== 分块卡片 ========== */ +.section-card { + padding: 0; + overflow: hidden; +} + +.section-header { + display: flex; + align-items: center; + padding: 28rpx 32rpx 20rpx; + background: linear-gradient(135deg, #fff5f5 0%, #fff 100%); + border-bottom: 1rpx solid #f5f5f5; +} + +.section-icon { + font-size: 40rpx; + margin-right: 16rpx; + line-height: 1; +} + +.section-title-text { + font-size: 30rpx; + font-weight: 700; + color: #1a1a2e; + flex: 1; + display: block; +} + +.section-body { + padding: 24rpx 32rpx 32rpx; +} + +.section-body .content-text { + font-size: 27rpx; + color: #444; + line-height: 1.9; + letter-spacing: 0.5rpx; +} + +/* ========== 操作按钮 ========== */ +.action-section { + margin-top: 16rpx; + padding-bottom: 40rpx; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 100%!important; + height: 100rpx; + min-height: 100rpx; + border-radius: 28rpx; + margin-bottom: 20rpx; + padding: 0; + border: none; + line-height: 100rpx; + box-sizing: border-box; + overflow: hidden; +} + +.btn::after { + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.3); +} + +.btn-outline { + background: #fff; + border: 2rpx solid #e63946; +} + +.btn-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} + +.btn-text-outline { + font-size: 30rpx; + color: #e63946; + font-weight: 500; +} + +/* ========== 支付解锁卡片 ========== */ +.pay-gate-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 60rpx 40rpx 48rpx; + text-align: center; +} + +.pay-gate-icon { + font-size: 80rpx; + margin-bottom: 24rpx; +} + +.pay-gate-title { + font-size: 36rpx; + font-weight: 700; + color: #1f2937; + margin-bottom: 16rpx; +} + +.pay-gate-desc { + font-size: 28rpx; + color: #6b7280; + line-height: 1.6; + margin-bottom: 36rpx; +} + +.pay-gate-amount { + display: flex; + align-items: center; + gap: 16rpx; + background: #fff5f5; + border-radius: 20rpx; + padding: 20rpx 40rpx; + margin-bottom: 40rpx; +} + +.pay-amount-label { + font-size: 26rpx; + color: #6b7280; +} + +.pay-amount-value { + font-size: 52rpx; + font-weight: 800; + color: #e63946; +} + +.pay-gate-btn { + width: 100%; + height: 96rpx; + background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%); + border-radius: 48rpx; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 24rpx rgba(230, 57, 70, 0.35); +} + +.pay-gate-btn.paying { + opacity: 0.7; +} + +.pay-gate-btn-text { + font-size: 32rpx; + font-weight: 700; + color: #fff; +} diff --git a/miniprogram/pages/test-select/index.js b/miniprogram/pages/test-select/index.js new file mode 100644 index 0000000..90002dd --- /dev/null +++ b/miniprogram/pages/test-select/index.js @@ -0,0 +1,21 @@ +// pages/test-select/index.js - 人脸测试后的详情性格测试入口(MBTI / PDP / DISC) +Page({ + data: {}, + + onLoad() {}, + + // 进入 MBTI 测试 + goMBTI() { + wx.navigateTo({ url: '/pages/test/mbti' }) + }, + + // 进入 PDP 测试 + goPDP() { + wx.navigateTo({ url: '/pages/test/pdp' }) + }, + + // 进入 DISC 测试 + goDISC() { + wx.navigateTo({ url: '/pages/test/disc' }) + } +}) diff --git a/miniprogram/pages/test-select/index.json b/miniprogram/pages/test-select/index.json new file mode 100644 index 0000000..e4c0c1e --- /dev/null +++ b/miniprogram/pages/test-select/index.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "详细性格测试", + "navigationBarBackgroundColor": "#FFFFFF", + "usingComponents": {} +} diff --git a/miniprogram/pages/test-select/index.wxml b/miniprogram/pages/test-select/index.wxml new file mode 100644 index 0000000..d35442c --- /dev/null +++ b/miniprogram/pages/test-select/index.wxml @@ -0,0 +1,40 @@ + + + + 选择一项详细性格测试 + 完成问卷即可获得对应维度的专业报告,与面相分析结果互补 + + + + + 🧠 + + + MBTI 性格测试 + 16型人格 · 发现你的认知与决策风格 + + + + + + + 🦁 + + + PDP 行为偏好测试 + 老虎 / 孔雀 / 考拉 / 猫头鹰 · 行为风格 + + + + + + + 📊 + + + DISC 性格测试 + D/I/S/C 四维 · 沟通与行为倾向 + + + + diff --git a/miniprogram/pages/test-select/index.wxss b/miniprogram/pages/test-select/index.wxss new file mode 100644 index 0000000..9b85d95 --- /dev/null +++ b/miniprogram/pages/test-select/index.wxss @@ -0,0 +1,107 @@ +/* pages/test-select/index.wxss - 详细性格测试入口,与现有 UI 风格一致 */ +.container { + min-height: 100vh; + background: linear-gradient(180deg, #fff5f5 0%, #f5f5f5 50%, #ffffff 100%); + padding: 24rpx 24rpx 60rpx; + box-sizing: border-box; +} + +.card { + background-color: #fff; + border-radius: 16rpx; + padding: 32rpx; + margin-bottom: 24rpx; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04); +} + +/* 顶部说明 */ +.intro-card { + text-align: center; + padding: 40rpx 32rpx; +} + +.intro-title { + display: block; + font-size: 34rpx; + font-weight: 700; + color: #e63946; + margin-bottom: 16rpx; +} + +.intro-desc { + display: block; + font-size: 26rpx; + color: #666; + line-height: 1.6; +} + +/* 入口卡片(与 result 页 next-btn 同色系) */ +.entry-card { + display: flex; + align-items: center; + padding: 28rpx 32rpx; + transition: opacity 0.2s; + border-radius: 16rpx; + border: 2rpx solid transparent; +} + +.entry-card:active { + opacity: 0.9; + background: linear-gradient(135deg, rgba(230, 57, 70, 0.04) 0%, rgba(255, 107, 157, 0.04) 100%); + border-color: rgba(230, 57, 70, 0.15); +} + +.entry-icon-wrap { + width: 88rpx; + height: 88rpx; + border-radius: 20rpx; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; + flex-shrink: 0; +} + +.entry-icon-wrap.mbti { + background: linear-gradient(135deg, rgba(124, 58, 237, 0.15) 0%, rgba(139, 92, 246, 0.2) 100%); +} + +.entry-icon-wrap.pdp { + background: linear-gradient(135deg, rgba(230, 57, 70, 0.12) 0%, rgba(255, 107, 157, 0.15) 100%); +} + +.entry-icon-wrap.disc { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.12) 0%, rgba(96, 165, 250, 0.18) 100%); +} + +.entry-emoji { + font-size: 44rpx; +} + +.entry-content { + flex: 1; + min-width: 0; +} + +.entry-name { + display: block; + font-size: 30rpx; + font-weight: 600; + color: #333; + margin-bottom: 8rpx; +} + +.entry-brief { + display: block; + font-size: 24rpx; + color: #888; + line-height: 1.4; +} + +.entry-arrow { + font-size: 32rpx; + color: #e63946; + font-weight: 600; + margin-left: 16rpx; + flex-shrink: 0; +} diff --git a/miniprogram/pages/test/disc.js b/miniprogram/pages/test/disc.js new file mode 100644 index 0000000..95863a5 --- /dev/null +++ b/miniprogram/pages/test/disc.js @@ -0,0 +1,134 @@ +// pages/test/disc.js +const { discQuestions, shuffleQuestions } = require('../../utils/questions') +const { discDescriptions } = require('../../utils/descriptions') +const app = getApp() + +Page({ + data: { + questions: [], + currentIndex: 0, + currentQuestion: null, + answers: {}, + selectedAnswer: null, + total: discQuestions.length, + answeredCount: 0, + progress: 0, + timeRemaining: 15 * 60, + formatTime: '15:00', + isSubmitting: false + }, + + timer: null, + + onLoad() { + const questions = shuffleQuestions(discQuestions) + this.setData({ questions, currentQuestion: questions[0] }) + this.startTimer() + }, + + onUnload() { + if (this.timer) clearInterval(this.timer) + }, + + startTimer() { + this.timer = setInterval(() => { + let time = this.data.timeRemaining - 1 + if (time <= 0) { + clearInterval(this.timer) + this.submitTest() + return + } + const minutes = Math.floor(time / 60) + const seconds = time % 60 + this.setData({ + timeRemaining: time, + formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}` + }) + }, 1000) + }, + + selectAnswer(e) { + const value = e.currentTarget.dataset.value + const questionId = this.data.currentQuestion.id + let answers = { ...this.data.answers } + answers[questionId] = value + + this.setData({ + selectedAnswer: value, + answers: answers, + answeredCount: Object.keys(answers).length, + progress: (Object.keys(answers).length / this.data.total) * 100 + }) + + setTimeout(() => { + if (this.data.currentIndex < this.data.total - 1) { + this.nextQuestion() + } + }, 300) + }, + + prevQuestion() { + if (this.data.currentIndex > 0) { + const newIndex = this.data.currentIndex - 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + nextQuestion() { + if (this.data.currentIndex < this.data.total - 1) { + const newIndex = this.data.currentIndex + 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + submitTest() { + if (this.data.isSubmitting) return + this.setData({ isSubmitting: true }) + + const scores = { D: 0, I: 0, S: 0, C: 0 } + Object.values(this.data.answers).forEach(value => { + if (scores.hasOwnProperty(value)) scores[value]++ + }) + + const total = Object.values(scores).reduce((sum, v) => sum + v, 0) + const percentages = { + D: Math.round((scores.D / total) * 100), + I: Math.round((scores.I / total) * 100), + S: Math.round((scores.S / total) * 100), + C: Math.round((scores.C / total) * 100) + } + + const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0] + const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0] + + const resultData = { + scores, + percentages, + dominantType, + secondaryType, + description: discDescriptions[dominantType], + testDuration: 15 * 60 - this.data.timeRemaining, + completedAt: new Date().toISOString(), + // 便于后端留存完整答题过程 + answers: this.data.answers + } + + // 本地缓存 + 全局缓存 + wx.setStorageSync('discResult', resultData) + if (app && typeof app.saveTestResult === 'function') { + app.saveTestResult('disc', resultData) + } + + wx.redirectTo({ url: '/pages/result/disc' }) + } +}) diff --git a/miniprogram/pages/test/disc.json b/miniprogram/pages/test/disc.json new file mode 100644 index 0000000..b7c83cd --- /dev/null +++ b/miniprogram/pages/test/disc.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "DISC测试", + "navigationBarBackgroundColor": "#3B82F6", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/test/disc.wxml b/miniprogram/pages/test/disc.wxml new file mode 100644 index 0000000..a83cc11 --- /dev/null +++ b/miniprogram/pages/test/disc.wxml @@ -0,0 +1,48 @@ + + + + + 问题 {{currentIndex + 1}}/{{total}} + 剩余时间: {{formatTime}} + + + + + + + + + {{currentQuestion.question}} + + + + + + {{option.text}} + + + + + + + + 上一题 + + + 跳过 + + + + + + {{isSubmitting ? '计算中...' : '完成测试,查看结果'}} + + + diff --git a/miniprogram/pages/test/disc.wxss b/miniprogram/pages/test/disc.wxss new file mode 100644 index 0000000..49f2191 --- /dev/null +++ b/miniprogram/pages/test/disc.wxss @@ -0,0 +1,172 @@ +/* pages/test/disc.wxss - 按旧版模板重构 */ +.test-page { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + background-color: #fff; +} + +.progress-section { + padding: 32rpx; + border-bottom: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.progress-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16rpx; +} + +.question-count { + font-size: 28rpx; + font-weight: 500; + color: #333; +} + +.time-remaining { + font-size: 28rpx; + color: #999; +} + +.progress-bar-container { + width: 100%; + height: 8rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%); + border-radius: 8rpx; + transition: width 0.3s ease; +} + +.content-area { + flex: 1; + overflow-y: auto; + padding: 32rpx; +} + +.question-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.question-text { + display: block; + font-size: 40rpx; + font-weight: 500; + color: #333; + line-height: 1.6; + margin-bottom: 48rpx; +} + +.options-container { + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.option-item { + display: flex; + align-items: center; + padding: 32rpx; + background: #fff; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + transition: all 0.3s ease; +} + +.option-item.selected { + background-color: rgba(59, 130, 246, 0.1); + border-color: #3B82F6; +} + +.radio-button { + width: 40rpx; + height: 40rpx; + border-radius: 50%; + border: 2rpx solid #d1d5db; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; + flex-shrink: 0; +} + +.radio-button.checked { + background-color: #3B82F6; + border-color: #3B82F6; +} + +.radio-inner { + width: 16rpx; + height: 16rpx; + border-radius: 50%; + background-color: #fff; +} + +.option-text { + flex: 1; + font-size: 32rpx; + color: #333; + line-height: 1.5; +} + +.footer-buttons { + display: flex; + gap: 24rpx; + padding: 32rpx; + border-top: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.nav-button { + flex: 1; + padding: 28rpx; + border-radius: 16rpx; + text-align: center; +} + +.nav-button.secondary { + background-color: #fff; + border: 2rpx solid #3B82F6; +} + +.nav-button.secondary .button-text { + color: #3B82F6; +} + +.nav-button.disabled { + opacity: 0.4; + pointer-events: none; +} + +.button-text { + font-size: 32rpx; + font-weight: 500; +} + +.submit-wrap { + padding: 0 32rpx 32rpx; +} + +.submit-button { + background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%); + border-radius: 16rpx; + padding: 28rpx; + text-align: center; +} + +.submit-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} diff --git a/miniprogram/pages/test/mbti.js b/miniprogram/pages/test/mbti.js new file mode 100644 index 0000000..d4bb51e --- /dev/null +++ b/miniprogram/pages/test/mbti.js @@ -0,0 +1,180 @@ +// pages/test/mbti.js - MBTI测试页面逻辑 +const { mbtiQuestions, shuffleQuestions } = require('../../utils/questions') +const { mbtiDescriptions } = require('../../utils/descriptions') +const payment = require('../../utils/payment') +const app = getApp() + +Page({ + data: { + questions: [], + currentIndex: 0, + currentQuestion: null, + answers: {}, + selectedAnswer: null, + total: mbtiQuestions.length, + answeredCount: 0, + progress: 0, + timeRemaining: 30 * 60, // 30分钟 + formatTime: '30:00', + isSubmitting: false, + canAccess: false + }, + + timer: null, + + onLoad() { + const questions = shuffleQuestions(mbtiQuestions) + this.setData({ + questions, + currentQuestion: questions[0], + canAccess: true + }) + this.startTimer() + }, + + // 检查访问权限 + checkAccess() { + // 当前策略:所有测试免费开放,直接允许访问 + // 若后续恢复收费,可重新启用 payment.canTakeTest 等校验逻辑 + return true + }, + + onUnload() { + if (this.timer) { + clearInterval(this.timer) + } + }, + + // 启动计时器 + startTimer() { + this.timer = setInterval(() => { + let time = this.data.timeRemaining - 1 + if (time <= 0) { + clearInterval(this.timer) + this.submitTest() + return + } + const minutes = Math.floor(time / 60) + const seconds = time % 60 + this.setData({ + timeRemaining: time, + formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}` + }) + }, 1000) + }, + + // 选择答案 + selectAnswer(e) { + const value = e.currentTarget.dataset.value + const questionId = this.data.currentQuestion.id + + let answers = { ...this.data.answers } + answers[questionId] = value + + this.setData({ + selectedAnswer: value, + answers: answers, + answeredCount: Object.keys(answers).length, + progress: (Object.keys(answers).length / this.data.total) * 100 + }) + + // 自动跳转下一题 + setTimeout(() => { + if (this.data.currentIndex < this.data.total - 1) { + this.nextQuestion() + } + }, 300) + }, + + // 上一题 + prevQuestion() { + if (this.data.currentIndex > 0) { + const newIndex = this.data.currentIndex - 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + // 下一题 + nextQuestion() { + if (this.data.currentIndex < this.data.total - 1) { + const newIndex = this.data.currentIndex + 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + // 提交测试 + submitTest() { + if (this.data.isSubmitting) return + this.setData({ isSubmitting: true }) + + const result = this.calculateResult() + + // 保存结果 + const resultData = { + ...result, + testDuration: 30 * 60 - this.data.timeRemaining, + completedAt: new Date().toISOString(), + timestamp: new Date().toISOString() + } + wx.setStorageSync('mbtiResult', resultData) + app.saveTestResult('mbti', resultData) + + // 跳转到结果页 + wx.redirectTo({ + url: '/pages/result/mbti' + }) + }, + + // 计算MBTI结果 + calculateResult() { + const answers = this.data.answers + const scores = { E: 0, I: 0, S: 0, N: 0, T: 0, F: 0, J: 0, P: 0 } + + // 统计各维度得分 + Object.values(answers).forEach(value => { + if (scores.hasOwnProperty(value)) { + scores[value]++ + } + }) + + // 确定MBTI类型 + const mbtiType = [ + scores.E >= scores.I ? 'E' : 'I', + scores.S >= scores.N ? 'S' : 'N', + scores.T >= scores.F ? 'T' : 'F', + scores.J >= scores.P ? 'J' : 'P' + ].join('') + + // 计算各维度百分比 + const dimensionScores = { + EI: { E: scores.E, I: scores.I, dominant: scores.E >= scores.I ? 'E' : 'I', percentage: Math.round((Math.max(scores.E, scores.I) / (scores.E + scores.I)) * 100) }, + SN: { S: scores.S, N: scores.N, dominant: scores.S >= scores.N ? 'S' : 'N', percentage: Math.round((Math.max(scores.S, scores.N) / (scores.S + scores.N)) * 100) }, + TF: { T: scores.T, F: scores.F, dominant: scores.T >= scores.F ? 'T' : 'F', percentage: Math.round((Math.max(scores.T, scores.F) / (scores.T + scores.F)) * 100) }, + JP: { J: scores.J, P: scores.P, dominant: scores.J >= scores.P ? 'J' : 'P', percentage: Math.round((Math.max(scores.J, scores.P) / (scores.J + scores.P)) * 100) } + } + + // 计算置信度 + const confidence = Math.round( + (dimensionScores.EI.percentage + dimensionScores.SN.percentage + + dimensionScores.TF.percentage + dimensionScores.JP.percentage) / 4 + ) + + return { + mbtiType, + scores, + dimensionScores, + confidence, + description: mbtiDescriptions[mbtiType] || {} + } + } +}) diff --git a/miniprogram/pages/test/mbti.json b/miniprogram/pages/test/mbti.json new file mode 100644 index 0000000..8224f00 --- /dev/null +++ b/miniprogram/pages/test/mbti.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "MBTI测试", + "navigationBarBackgroundColor": "#FF6B8A", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/test/mbti.wxml b/miniprogram/pages/test/mbti.wxml new file mode 100644 index 0000000..04a538d --- /dev/null +++ b/miniprogram/pages/test/mbti.wxml @@ -0,0 +1,48 @@ + + + + + 问题 {{currentIndex + 1}}/{{total}} + 剩余时间: {{formatTime}} + + + + + + + + + {{currentQuestion.question}} + + + + + + {{option.text}} + + + + + + + + 上一题 + + + 跳过 + + + + + + {{isSubmitting ? '计算中...' : '完成测试,查看结果'}} + + + diff --git a/miniprogram/pages/test/mbti.wxss b/miniprogram/pages/test/mbti.wxss new file mode 100644 index 0000000..1927ace --- /dev/null +++ b/miniprogram/pages/test/mbti.wxss @@ -0,0 +1,172 @@ +/* pages/test/mbti.wxss - 按旧版模板重构 */ +.test-page { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + background-color: #fff; +} + +.progress-section { + padding: 32rpx; + border-bottom: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.progress-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16rpx; +} + +.question-count { + font-size: 28rpx; + font-weight: 500; + color: #333; +} + +.time-remaining { + font-size: 28rpx; + color: #999; +} + +.progress-bar-container { + width: 100%; + height: 8rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%); + border-radius: 8rpx; + transition: width 0.3s ease; +} + +.content-area { + flex: 1; + overflow-y: auto; + padding: 32rpx; +} + +.question-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.question-text { + display: block; + font-size: 40rpx; + font-weight: 500; + color: #333; + line-height: 1.6; + margin-bottom: 48rpx; +} + +.options-container { + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.option-item { + display: flex; + align-items: center; + padding: 32rpx; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + transition: all 0.3s ease; +} + +.option-item.selected { + background-color: rgba(255, 107, 138, 0.12); + border-color: #FF6B8A; +} + +.radio-button { + width: 40rpx; + height: 40rpx; + border-radius: 50%; + border: 2rpx solid #d1d5db; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; + flex-shrink: 0; + transition: all 0.3s ease; +} + +.radio-button.checked { + background-color: #FF6B8A; + border-color: #FF6B8A; +} + +.radio-inner { + width: 16rpx; + height: 16rpx; + border-radius: 50%; + background-color: #fff; +} + +.option-text { + flex: 1; + font-size: 32rpx; + color: #333; + line-height: 1.5; +} + +.footer-buttons { + display: flex; + gap: 24rpx; + padding: 32rpx; + border-top: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.nav-button { + flex: 1; + padding: 28rpx; + border-radius: 16rpx; + text-align: center; +} + +.nav-button.secondary { + background-color: #fff; + border: 2rpx solid #FF6B8A; +} + +.nav-button.secondary .button-text { + color: #FF6B8A; +} + +.nav-button.disabled { + opacity: 0.4; + pointer-events: none; +} + +.button-text { + font-size: 32rpx; + font-weight: 500; +} + +.submit-wrap { + padding: 0 32rpx 32rpx; +} + +.submit-button { + background: linear-gradient(135deg, #FF6B8A 0%, #ff8fa3 100%); + border-radius: 16rpx; + padding: 28rpx; + text-align: center; +} + +.submit-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} diff --git a/miniprogram/pages/test/pdp.js b/miniprogram/pages/test/pdp.js new file mode 100644 index 0000000..33e067f --- /dev/null +++ b/miniprogram/pages/test/pdp.js @@ -0,0 +1,137 @@ +// pages/test/pdp.js +const { pdpQuestions, shuffleQuestions } = require('../../utils/questions') +const { pdpDescriptions } = require('../../utils/descriptions') +const app = getApp() + +Page({ + data: { + questions: [], + currentIndex: 0, + currentQuestion: null, + answers: {}, + selectedAnswer: null, + total: pdpQuestions.length, + answeredCount: 0, + progress: 0, + timeRemaining: 15 * 60, + formatTime: '15:00', + isSubmitting: false + }, + + timer: null, + + onLoad() { + const questions = shuffleQuestions(pdpQuestions) + this.setData({ questions, currentQuestion: questions[0] }) + this.startTimer() + }, + + onUnload() { + if (this.timer) clearInterval(this.timer) + }, + + startTimer() { + this.timer = setInterval(() => { + let time = this.data.timeRemaining - 1 + if (time <= 0) { + clearInterval(this.timer) + this.submitTest() + return + } + const minutes = Math.floor(time / 60) + const seconds = time % 60 + this.setData({ + timeRemaining: time, + formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}` + }) + }, 1000) + }, + + getEmoji(value) { + const emojis = { Tiger: '🐅', Peacock: '🦚', Koala: '🐨', Owl: '🦉', Chameleon: '🦎' } + return emojis[value] || '🔹' + }, + + selectAnswer(e) { + const value = e.currentTarget.dataset.value + const questionId = this.data.currentQuestion.id + let answers = { ...this.data.answers } + answers[questionId] = value + + this.setData({ + selectedAnswer: value, + answers: answers, + answeredCount: Object.keys(answers).length, + progress: (Object.keys(answers).length / this.data.total) * 100 + }) + + setTimeout(() => { + if (this.data.currentIndex < this.data.total - 1) { + this.nextQuestion() + } + }, 300) + }, + + prevQuestion() { + if (this.data.currentIndex > 0) { + const newIndex = this.data.currentIndex - 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + nextQuestion() { + if (this.data.currentIndex < this.data.total - 1) { + const newIndex = this.data.currentIndex + 1 + const newQuestion = this.data.questions[newIndex] + this.setData({ + currentIndex: newIndex, + currentQuestion: newQuestion, + selectedAnswer: this.data.answers[newQuestion.id] || null + }) + } + }, + + submitTest() { + if (this.data.isSubmitting) return + this.setData({ isSubmitting: true }) + + const scores = { Tiger: 0, Peacock: 0, Koala: 0, Owl: 0, Chameleon: 0 } + Object.values(this.data.answers).forEach(value => { + if (scores.hasOwnProperty(value)) scores[value]++ + }) + + const total = Object.values(scores).reduce((sum, v) => sum + v, 0) + const percentages = {} + Object.keys(scores).forEach(key => { + percentages[key] = Math.round((scores[key] / total) * 100) + }) + + const dominantType = Object.entries(scores).sort((a, b) => b[1] - a[1])[0][0] + const secondaryType = Object.entries(scores).sort((a, b) => b[1] - a[1])[1][0] + + const resultData = { + scores, + percentages, + dominantType, + secondaryType, + description: pdpDescriptions[dominantType], + testDuration: 15 * 60 - this.data.timeRemaining, + completedAt: new Date().toISOString(), + // 便于后端留存完整答题过程 + answers: this.data.answers + } + + // 本地缓存 + 全局缓存 + wx.setStorageSync('pdpResult', resultData) + if (app && typeof app.saveTestResult === 'function') { + app.saveTestResult('pdp', resultData) + } + + wx.redirectTo({ url: '/pages/result/pdp' }) + } +}) diff --git a/miniprogram/pages/test/pdp.json b/miniprogram/pages/test/pdp.json new file mode 100644 index 0000000..ffe77f1 --- /dev/null +++ b/miniprogram/pages/test/pdp.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "PDP测试", + "navigationBarBackgroundColor": "#F59E0B", + "navigationBarTextStyle": "white", + "usingComponents": {} +} diff --git a/miniprogram/pages/test/pdp.wxml b/miniprogram/pages/test/pdp.wxml new file mode 100644 index 0000000..8fe9180 --- /dev/null +++ b/miniprogram/pages/test/pdp.wxml @@ -0,0 +1,48 @@ + + + + + 问题 {{currentIndex + 1}}/{{total}} + 剩余时间: {{formatTime}} + + + + + + + + + {{currentQuestion.question}} + + + + + + {{option.text}} + + + + + + + + 上一题 + + + 跳过 + + + + + + {{isSubmitting ? '计算中...' : '完成测试,查看结果'}} + + + diff --git a/miniprogram/pages/test/pdp.wxss b/miniprogram/pages/test/pdp.wxss new file mode 100644 index 0000000..0b815c5 --- /dev/null +++ b/miniprogram/pages/test/pdp.wxss @@ -0,0 +1,171 @@ +/* pages/test/pdp.wxss - 按旧版模板重构 */ +.test-page { + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + background-color: #fff; +} + +.progress-section { + padding: 32rpx; + border-bottom: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.progress-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16rpx; +} + +.question-count { + font-size: 28rpx; + font-weight: 500; + color: #333; +} + +.time-remaining { + font-size: 28rpx; + color: #999; +} + +.progress-bar-container { + width: 100%; + height: 8rpx; + background-color: #e5e5e5; + border-radius: 8rpx; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%); + border-radius: 8rpx; + transition: width 0.3s ease; +} + +.content-area { + flex: 1; + overflow-y: auto; + padding: 32rpx; +} + +.question-card { + background-color: #fff; + border-radius: 24rpx; + padding: 48rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); +} + +.question-text { + display: block; + font-size: 40rpx; + font-weight: 500; + color: #333; + line-height: 1.6; + margin-bottom: 48rpx; +} + +.options-container { + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.option-item { + display: flex; + align-items: center; + padding: 32rpx; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + transition: all 0.3s ease; +} + +.option-item.selected { + background-color: rgba(245, 158, 11, 0.12); + border-color: #F59E0B; +} + +.radio-button { + width: 40rpx; + height: 40rpx; + border-radius: 50%; + border: 2rpx solid #d1d5db; + display: flex; + align-items: center; + justify-content: center; + margin-right: 24rpx; + flex-shrink: 0; +} + +.radio-button.checked { + background-color: #F59E0B; + border-color: #F59E0B; +} + +.radio-inner { + width: 16rpx; + height: 16rpx; + border-radius: 50%; + background-color: #fff; +} + +.option-text { + flex: 1; + font-size: 32rpx; + color: #333; + line-height: 1.5; +} + +.footer-buttons { + display: flex; + gap: 24rpx; + padding: 32rpx; + border-top: 1rpx solid #e5e5e5; + flex-shrink: 0; +} + +.nav-button { + flex: 1; + padding: 28rpx; + border-radius: 16rpx; + text-align: center; +} + +.nav-button.secondary { + background-color: #fff; + border: 2rpx solid #F59E0B; +} + +.nav-button.secondary .button-text { + color: #F59E0B; +} + +.nav-button.disabled { + opacity: 0.4; + pointer-events: none; +} + +.button-text { + font-size: 32rpx; + font-weight: 500; +} + +.submit-wrap { + padding: 0 32rpx 32rpx; +} + +.submit-button { + background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%); + border-radius: 16rpx; + padding: 28rpx; + text-align: center; +} + +.submit-text { + font-size: 32rpx; + font-weight: 600; + color: #fff; +} diff --git a/miniprogram/pages/user-profile/index.js b/miniprogram/pages/user-profile/index.js new file mode 100644 index 0000000..3731e70 --- /dev/null +++ b/miniprogram/pages/user-profile/index.js @@ -0,0 +1,205 @@ +// pages/user-profile/index.js - 个人资料页 +const app = getApp() +const { request } = require('../../utils/request') +const { bindPhoneByCode } = require('../../utils/phoneAuth') + +Page({ + data: { + userInfo: null, + nickname: '', + avatar: '', + birthday: '', + gender: 0, + genderIndex: 0, + genderText: '', + genderOptions: ['保密', '男', '女'], + phone: '', + avatarLetter: '我', + avatarBgColor: '#6366f1', + nicknameFocused: false, + saving: false + }, + + onLoad() { + this.loadUserInfo() + }, + + onShow() { + // 从其他页返回时刷新 + if (app.globalData.userInfo) { + this.loadUserInfo() + } + }, + + loadUserInfo() { + const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') + if (!userInfo) { + wx.showToast({ title: '请先登录', icon: 'none' }) + setTimeout(() => wx.navigateBack(), 1500) + return + } + + const nickname = (userInfo.nickname || userInfo.nickName || '').trim() + const avatar = userInfo.avatar || userInfo.avatarUrl || '' + const birthday = userInfo.birthday || '' + const gender = (userInfo.gender !== undefined && userInfo.gender !== null) ? Number(userInfo.gender) : 0 + const genderText = this._genderText(gender) + const genderIndex = Math.min(Math.max(0, gender), 2) + const phone = userInfo.phone || '' + + const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我') + + this.setData({ + userInfo, + nickname, + avatar, + birthday, + gender, + genderIndex, + genderText, + phone, + avatarLetter, + avatarBgColor + }) + }, + + _genderText(g) { + const map = { 0: '保密', 1: '男', 2: '女' } + return map[g] || '保密' + }, + + _avatarFromNickname(name) { + const str = (name && String(name).trim()) || '我' + const letter = str.charAt(0).toUpperCase() || '我' + const palette = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#14b8a6', '#0ea5e9', '#3b82f6', '#eab308'] + let hash = 0 + for (let i = 0; i < str.length; i++) hash += str.charCodeAt(i) + const bgColor = palette[Math.abs(hash) % palette.length] + return { avatarLetter: letter, avatarBgColor: bgColor } + }, + + onChooseAvatar(e) { + const { avatarUrl } = e.detail + if (!avatarUrl || !app.globalData.token) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + wx.showLoading({ title: '上传中...' }) + const token = app.globalData.token + const apiBase = app.globalData.apiBase + wx.uploadFile({ + url: `${apiBase}/api/upload/image`, + filePath: avatarUrl, + name: 'file', + header: { 'Authorization': `Bearer ${token}` }, + success: (res) => { + const data = res.data + let json = {} + try { json = typeof data === 'string' ? JSON.parse(data) : data } catch (_) {} + if (json.code === 200 && json.data && json.data.url) { + const avatar = json.data.url + this.setData({ avatar }) + this._saveProfile({ avatar }) + } else { + wx.hideLoading() + wx.showToast({ title: json.message || '上传失败', icon: 'none' }) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '上传失败', icon: 'none' }) + } + }) + }, + + onGetPhoneNumber(e) { + const { errMsg, code } = e.detail || {} + if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) { + wx.showToast({ title: '需要授权手机号才能获取', icon: 'none' }) + return + } + if (!code) { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + return + } + bindPhoneByCode(code).then((user) => { + this.setData({ phone: user.phone || '' }) + }).catch(() => {}) + }, + + onNicknameRowTap() { + this.setData({ nicknameFocused: true }) + }, + + onNicknameBlur() { + this.setData({ nicknameFocused: false }) + }, + + onNicknameChange(e) { + const nickname = (e?.detail?.value || '').trim() + const { avatarLetter, avatarBgColor } = this._avatarFromNickname(nickname || '我') + this.setData({ nickname, avatarLetter, avatarBgColor }) + }, + + onBirthdayChange(e) { + const birthday = (e?.detail?.value || '').trim() + this.setData({ birthday }) + }, + + onGenderChange(e) { + const idx = parseInt(e?.detail?.value, 10) || 0 + const gender = idx + const genderText = this.data.genderOptions[idx] || '保密' + this.setData({ gender, genderIndex: idx, genderText }) + }, + + onSave() { + const { nickname, birthday, gender, userInfo } = this.data + const profile = {} + const origNickname = (userInfo?.nickname || userInfo?.nickName || '').trim() + const origBirthday = userInfo?.birthday || '' + const origGender = (userInfo?.gender !== undefined && userInfo?.gender !== null) ? Number(userInfo.gender) : 0 + if (nickname !== origNickname) profile.nickname = nickname + if (birthday !== origBirthday) profile.birthday = birthday + if (gender !== origGender) profile.gender = gender + if (Object.keys(profile).length === 0) { + wx.showToast({ title: '暂无修改', icon: 'none' }) + return + } + this._saveProfile(profile) + }, + + _saveProfile(profile) { + if (!app.globalData.token || !profile || Object.keys(profile).length === 0) return + if (this.data.saving) return + this.setData({ saving: true }) + wx.showLoading({ title: '保存中...' }) + request({ + url: '/api/auth/wechat/profile', + method: 'PUT', + data: profile, + success: (res) => { + wx.hideLoading() + this.setData({ saving: false }) + const payload = res && res.data + if (payload && payload.code === 200) { + const updated = { ...(app.globalData.userInfo || {}), ...(payload.data || {}), ...profile } + if (profile.nickname !== undefined) updated.nickname = profile.nickname + if (profile.avatar !== undefined) updated.avatar = profile.avatar + if (profile.birthday !== undefined) updated.birthday = profile.birthday + if (profile.gender !== undefined) updated.gender = profile.gender + app.globalData.userInfo = updated + wx.setStorageSync('userInfo', updated) + wx.showToast({ title: '已保存', icon: 'success' }) + } else { + wx.showToast({ title: payload?.message || '保存失败', icon: 'none' }) + } + }, + fail: () => { + wx.hideLoading() + this.setData({ saving: false }) + wx.showToast({ title: '网络错误', icon: 'none' }) + } + }) + } +}) diff --git a/miniprogram/pages/user-profile/index.json b/miniprogram/pages/user-profile/index.json new file mode 100644 index 0000000..7318c6a --- /dev/null +++ b/miniprogram/pages/user-profile/index.json @@ -0,0 +1,4 @@ +{ + "navigationBarTitleText": "个人资料", + "usingComponents": {} +} diff --git a/miniprogram/pages/user-profile/index.wxml b/miniprogram/pages/user-profile/index.wxml new file mode 100644 index 0000000..0d7c2b0 --- /dev/null +++ b/miniprogram/pages/user-profile/index.wxml @@ -0,0 +1,66 @@ + + + + + + + 点击更换头像 + + + + 头像、昵称、手机号为必填项 + + + + + + 昵称 + + + + + + + 生日 + + {{birthday || '请选择生日'}} + + + + + + + + 性别 + + {{genderText}} + + + + + + + 手机号 + + + + + + + + diff --git a/miniprogram/pages/user-profile/index.wxss b/miniprogram/pages/user-profile/index.wxss new file mode 100644 index 0000000..a2604f5 --- /dev/null +++ b/miniprogram/pages/user-profile/index.wxss @@ -0,0 +1,206 @@ +/* pages/user-profile/index.wxss */ +page { + background-color: #F9FAFB; +} + +.page { + min-height: 100vh; + padding: 32rpx; +} + +.profile-card { + background: #FFFFFF; + border-radius: 28rpx; + padding: 48rpx 36rpx; + box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05); +} + +.avatar-section { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 48rpx; +} + +.avatar-btn { + width: 160rpx !important; + height: 160rpx !important; + padding: 0 !important; + margin: 0 !important; + background: transparent !important; + border: none !important; +} + +.avatar-btn::after { + border: none !important; +} + +.avatar-img { + width: 160rpx; + height: 160rpx; + border-radius: 50%; + display: block; + border: 4rpx solid #F3F4F6; +} + +.avatar-letter-wrap { + width: 160rpx; + height: 160rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 4rpx solid #F3F4F6; +} + +.avatar-letter { + font-size: 72rpx; + font-weight: 700; + color: #FFFFFF; +} + +.avatar-hint { + font-size: 24rpx; + color: #9CA3AF; + margin-top: 16rpx; +} + +.required-tip { + font-size: 24rpx; + color: #9CA3AF; + margin-bottom: 24rpx; + padding: 0 4rpx; +} + +.form-section { + border-top: 1rpx solid #F3F4F6; +} + +.form-item { + display: flex; + align-items: center; + padding: 32rpx 0; + border-bottom: 1rpx solid #F3F4F6; +} + +.form-item:last-child { + border-bottom: none; +} + +.form-label { + width: 140rpx; + font-size: 28rpx; + color: #6B7280; + flex-shrink: 0; +} + +.form-input { + flex: 1; + font-size: 30rpx; + color: #111827; + min-width: 0; +} + +.form-picker { + flex: 1; + font-size: 30rpx; + color: #111827; + min-width: 0; +} + +.form-value { + font-size: 30rpx; + color: #111827; +} + +.form-placeholder { + color: #9CA3AF; +} + +.form-readonly { + color: #9CA3AF; +} + +.form-picker { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.form-arrow { + font-size: 36rpx; + color: #D1D5DB; + margin-left: 16rpx; + flex-shrink: 0; +} + +/* 整行可点击 */ +.form-item-tappable { + cursor: pointer; +} + +.form-item-tappable:active { + background: #F9FAFB; +} + +.form-item-readonly { + /* 仅展示,点击有提示 */ +} + +.form-item-readonly:active { + background: #F9FAFB; +} + +/* 手机号授权按钮 */ +.form-item-phone-auth { + padding: 0; +} + +.phone-auth-btn { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + padding: 32rpx 0; + margin: 0; + background: transparent; + border: none; + text-align: left; +} + +.phone-auth-btn::after { + border: none; +} + +.phone-auth-text { + font-size: 30rpx; + color: #7C3AED; + font-weight: 500; +} + +.picker-full-row { + display: block; + width: 100%; +} + +/* 保存按钮 */ +.save-btn { + width: 100%; + margin-top: 48rpx; + padding: 28rpx; + font-size: 32rpx; + font-weight: 600; + color: #FFFFFF; + background: linear-gradient(135deg, #7C3AED 0%, #6366F1 100%); + border-radius: 16rpx; + border: none; +} + +.save-btn::after { + border: none; +} + +.save-btn[disabled] { + opacity: 0.6; +} diff --git a/miniprogram/project.config.json b/miniprogram/project.config.json new file mode 100644 index 0000000..ec384e5 --- /dev/null +++ b/miniprogram/project.config.json @@ -0,0 +1,51 @@ +{ + "miniprogramRoot": "", + "description": "MBTI/DISC/PDP性格测试系统 - 个人版与企业版", + "setting": { + "urlCheck": false, + "es6": true, + "enhance": true, + "postcss": true, + "preloadBackgroundData": false, + "minified": true, + "newFeature": true, + "coverView": true, + "nodeModules": false, + "autoAudits": false, + "showShadowRootInWxmlPanel": true, + "scopeDataCheck": false, + "uglifyFileName": false, + "checkInvalidKey": true, + "checkSiteMap": true, + "uploadWithSourceMap": true, + "compileHotReLoad": false, + "lazyloadPlaceholderEnable": false, + "useMultiFrameRuntime": true, + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "useIsolateContext": true, + "userConfirmedBundleSwitch": false, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "disableUseStrict": false, + "showES6CompileOption": false, + "useCompilerPlugins": false, + "ignoreUploadUnusedFiles": true + }, + "compileType": "miniprogram", + "condition": {}, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 4 + }, + "libVersion": "3.14.1", + "packOptions": { + "ignore": [], + "include": [] + }, + "appid": "wx3d15ed02e98b04e3" +} diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json new file mode 100644 index 0000000..82b39a7 --- /dev/null +++ b/miniprogram/project.private.config.json @@ -0,0 +1,34 @@ +{ + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "MBTI人格测试系统", + "setting": { + "compileHotReLoad": true + }, + "condition": { + "miniprogram": { + "list": [ + { + "name": "pages/enterprise/index", + "pathName": "pages/enterprise/index", + "query": "scene=uid%253D6%2526eid%253D6", + "scene": null, + "launchMode": "default" + }, + { + "name": "pages/enterprise/index", + "pathName": "pages/enterprise/index", + "query": "scene=e_6", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/enterprise/index", + "pathName": "pages/enterprise/index", + "query": "scene=e_6", + "launchMode": "default", + "scene": null + } + ] + } + } +} \ No newline at end of file diff --git a/miniprogram/sitemap.json b/miniprogram/sitemap.json new file mode 100644 index 0000000..55d1d29 --- /dev/null +++ b/miniprogram/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} diff --git a/miniprogram/utils/descriptions.js b/miniprogram/utils/descriptions.js new file mode 100644 index 0000000..43f60e6 --- /dev/null +++ b/miniprogram/utils/descriptions.js @@ -0,0 +1,44 @@ +// utils/descriptions.js - 测试结果描述 + +// MBTI类型描述 +const mbtiDescriptions = { + ISTJ: { type: "ISTJ", name: "检查员", category: "守护者", description: "务实、负责、可靠的传统主义者", strengths: ["可靠负责", "注重细节", "有条理"], weaknesses: ["可能过于固执", "不喜变化"], careers: ["会计师", "审计师", "项目经理"] }, + ISFJ: { type: "ISFJ", name: "保护者", category: "守护者", description: "安静、友好、负责任的守护者", strengths: ["忠诚体贴", "观察力强", "耐心"], weaknesses: ["不善拒绝", "过于谦虚"], careers: ["护士", "教师", "行政"] }, + INFJ: { type: "INFJ", name: "提倡者", category: "理想主义者", description: "寻求意义和联系的理想主义者", strengths: ["洞察力强", "有远见", "坚定"], weaknesses: ["过于理想化", "容易疲惫"], careers: ["心理咨询", "作家", "人力资源"] }, + INTJ: { type: "INTJ", name: "建筑师", category: "理想主义者", description: "独立、有战略眼光的思考者", strengths: ["战略思维", "独立自信", "意志坚定"], weaknesses: ["可能傲慢", "过于苛刻"], careers: ["战略顾问", "科学家", "架构师"] }, + ISTP: { type: "ISTP", name: "鉴赏家", category: "探险家", description: "灵活、务实的问题解决者", strengths: ["适应力强", "动手能力", "冷静"], weaknesses: ["可能冷漠", "不善表达"], careers: ["工程师", "技术员", "飞行员"] }, + ISFP: { type: "ISFP", name: "探险家", category: "探险家", description: "温和、敏感的艺术家", strengths: ["创造力", "同理心", "灵活"], weaknesses: ["过于敏感", "避免冲突"], careers: ["设计师", "艺术家", "摄影师"] }, + INFP: { type: "INFP", name: "调停者", category: "理想主义者", description: "理想主义、忠诚的调解者", strengths: ["创造力", "同理心", "真诚"], weaknesses: ["过于理想化", "情绪化"], careers: ["作家", "心理咨询", "社工"] }, + INTP: { type: "INTP", name: "逻辑学家", category: "理想主义者", description: "创新、逻辑的思考者", strengths: ["逻辑思维", "创新能力", "客观"], weaknesses: ["可能孤僻", "忽视情感"], careers: ["程序员", "研究员", "分析师"] }, + ESTP: { type: "ESTP", name: "企业家", category: "探险家", description: "精力充沛、务实的行动者", strengths: ["果断", "务实", "善于应变"], weaknesses: ["可能冲动", "缺乏耐心"], careers: ["销售", "企业家", "运动员"] }, + ESFP: { type: "ESFP", name: "表演者", category: "探险家", description: "热情、友好的社交达人", strengths: ["热情友好", "乐观", "灵活"], weaknesses: ["可能肤浅", "容易分心"], careers: ["演员", "销售", "主持人"] }, + ENFP: { type: "ENFP", name: "竞选者", category: "理想主义者", description: "热情、有创造力的社交者", strengths: ["创造力", "热情", "善于沟通"], weaknesses: ["可能不切实际", "缺乏专注"], careers: ["市场营销", "记者", "顾问"] }, + ENTP: { type: "ENTP", name: "辩论家", category: "理想主义者", description: "聪明、好奇的思想家", strengths: ["创新能力", "辩论能力", "适应力"], weaknesses: ["可能争辩", "不善执行"], careers: ["律师", "企业家", "咨询"] }, + ESTJ: { type: "ESTJ", name: "总经理", category: "守护者", description: "务实、果断的组织者", strengths: ["领导力", "组织能力", "务实"], weaknesses: ["可能专制", "不够灵活"], careers: ["管理者", "项目经理", "律师"] }, + ESFJ: { type: "ESFJ", name: "执政官", category: "守护者", description: "热心、合作的支持者", strengths: ["关心他人", "负责任", "善于合作"], weaknesses: ["过于在意评价", "不善拒绝"], careers: ["教师", "护士", "人力资源"] }, + ENFJ: { type: "ENFJ", name: "主人公", category: "理想主义者", description: "有魅力、鼓舞人心的领导者", strengths: ["领导力", "同理心", "说服力"], weaknesses: ["过于理想化", "过度付出"], careers: ["培训师", "顾问", "教师"] }, + ENTJ: { type: "ENTJ", name: "指挥官", category: "理想主义者", description: "大胆、有远见的领导者", strengths: ["领导力", "战略思维", "果断"], weaknesses: ["可能专制", "不耐烦"], careers: ["CEO", "企业家", "律师"] } +} + +// DISC类型描述 +const discDescriptions = { + D: { type: "D型", title: "支配型", color: "#EF4444", description: "天生的领导者,注重结果和效率", strengths: ["决断力强", "目标导向", "行动迅速"], weaknesses: ["可能过于强势", "缺乏耐心"], careers: ["企业高管", "创业者", "项目经理"] }, + I: { type: "I型", title: "影响型", color: "#F59E0B", description: "热情友好,善于社交和表达", strengths: ["善于沟通", "乐观积极", "创意丰富"], weaknesses: ["可能过于乐观", "注意力分散"], careers: ["市场营销", "公关", "培训师"] }, + S: { type: "S型", title: "稳健型", color: "#10B981", description: "稳重可靠,注重团队和谐", strengths: ["可靠稳定", "团队协作", "耐心倾听"], weaknesses: ["可能抗拒变化", "决策较慢"], careers: ["人力资源", "客户服务", "行政"] }, + C: { type: "C型", title: "谨慎型", color: "#3B82F6", description: "注重细节和准确性,追求完美", strengths: ["分析能力强", "注重细节", "准确严谨"], weaknesses: ["可能过于完美主义", "决策较慢"], careers: ["数据分析", "工程师", "会计师"] } +} + +// PDP类型描述 +const pdpDescriptions = { + Tiger: { type: "老虎型", emoji: "🐅", title: "支配者", color: "#F59E0B", description: "天生的领导者,具有强烈的目标导向和执行力", strengths: ["决断力强", "目标导向", "行动迅速"], weaknesses: ["可能过于强势", "缺乏耐心"], careers: ["企业高管", "创业者", "项目经理"], teamRole: "适合担任领导者角色" }, + Peacock: { type: "孔雀型", emoji: "🦚", title: "表现者", color: "#8B5CF6", description: "热情友好,善于表达和社交", strengths: ["善于沟通", "乐观积极", "影响力强"], weaknesses: ["可能过于乐观", "注意力分散"], careers: ["市场营销", "公关", "培训师"], teamRole: "适合担任激励者和调解者角色" }, + Koala: { type: "考拉型", emoji: "🐨", title: "支持者", color: "#10B981", description: "稳重可靠,注重团队和谐", strengths: ["可靠稳定", "团队协作", "善解人意"], weaknesses: ["可能抗拒变化", "决策较慢"], careers: ["人力资源", "客户服务", "教师"], teamRole: "适合担任协调者和支持者角色" }, + Owl: { type: "猫头鹰型", emoji: "🦉", title: "分析者", color: "#3B82F6", description: "注重细节和准确性,追求完美", strengths: ["分析能力强", "注重细节", "系统思维"], weaknesses: ["可能过于完美主义", "不善社交"], careers: ["数据分析师", "工程师", "研究员"], teamRole: "适合担任专家和质量控制者角色" }, + Chameleon: { type: "变色龙型", emoji: "🦎", title: "整合者", color: "#06B6D4", description: "适应能力强,能在不同情境中灵活调整", strengths: ["适应力强", "灵活多变", "平衡能力"], weaknesses: ["可能缺乏主见", "身份认同模糊"], careers: ["咨询顾问", "协调员", "自由职业者"], teamRole: "适合担任协调者和多面手角色" } +} + +module.exports = { + mbtiDescriptions, + discDescriptions, + pdpDescriptions +} diff --git a/miniprogram/utils/payment.js b/miniprogram/utils/payment.js new file mode 100644 index 0000000..b29971b --- /dev/null +++ b/miniprogram/utils/payment.js @@ -0,0 +1,628 @@ +// miniprogram/utils/payment.js +// 微信支付工具类 - 复刻自Soul项目 + +const app = getApp() + +/** + * 生成符合微信规则的订单号 + * 规则示例:FACE20260226151900001(前缀+时间戳+随机数,最长不超过32位) + * @param {String} productType - 业务类型,如 face/mbti/disc/pdp/report/team_analysis/vip 等 + */ +function generateOrderId(productType) { + const now = new Date() + const pad = (n, len = 2) => String(n).padStart(len, '0') + const prefixMap = { + face: 'FACE', + mbti: 'MBTI', + disc: 'DISC', + pdp: 'PDP', + report: 'REPT', + team_analysis: 'TEAM', + vip: 'VIP', + test_count: 'TNUM', + single_test: 'TSGL', + recharge: 'RCG', + deep_personal: 'DPER', + deep_team: 'DTEAM' + } + + const prefix = prefixMap[productType] || (productType || 'ORD').toUpperCase().slice(0, 6) + const dateStr = + now.getFullYear().toString() + + pad(now.getMonth() + 1) + + pad(now.getDate()) + + pad(now.getHours()) + + pad(now.getMinutes()) + + pad(now.getSeconds()) + + // 3位随机数,避免重复,如 001~999 + const rand = pad(Math.floor(Math.random() * 1000), 3) + const raw = `${prefix}${dateStr}${rand}` // 示例:FACE20260226151900001 + + // 微信 out_trade_no 最长 32 字节,这里兜底截断 + return raw.length > 32 ? raw.slice(0, 32) : raw +} + +/** + * 发起微信支付 + * @param {Object} options - 支付选项 + * @param {String} options.orderId - 订单ID + * @param {Number} [options.amount] - 支付金额(分),部分产品使用(如充值/VIP/测试次数等);深度服务与按配置定价的产品传 0 或不传 + * @param {String} options.description - 商品描述 + * @param {String} options.productType - 商品类型: 'vip' | 'test_count' | 'single_test' | 'face' | 'mbti' 等 + * @param {Number} [options.testResultId] - 可选,对应 mbti_test_results.id,用于精确关联测试记录 + * @param {String} [options.deepProductId] - 可选,深度服务对应的套餐ID/产品Key,用于后台从 categories 中选中具体价格 + * @param {Function} options.success - 成功回调 + * @param {Function} options.fail - 失败回调 + */ +function wxPay(options) { + const { orderId, amount = 0, description, productType, testResultId, deepProductId, enterpriseId, success, fail } = options + + wx.showLoading({ + title: '正在支付...', + mask: true + }) + + // 1. 调用后端创建支付订单 + wx.request({ + url: `${app.globalData.apiBase}/api/payment/create`, + method: 'POST', + header: { + 'Authorization': `Bearer ${wx.getStorageSync('token')}`, + 'Content-Type': 'application/json' + }, + data: { + orderId, + amount, + description, + productType, + paymentMethod: 'wechat', + openId: app.globalData.openId || '', + enterpriseId: enterpriseId || 0, + // 创建订单时将本次测试记录ID传给后端,避免每次都只更新“最新一条” + testResultId: testResultId || 0, + // 深度服务使用的具体套餐ID/产品Key(用于从 categories 中选择价格) + deepProductId: deepProductId || '' + }, + success: (res) => { + wx.hideLoading() + + if (res.statusCode === 200 && res.data.code === 200) { + const paymentData = res.data.data + + // 2. 调起微信支付 + wx.requestPayment({ + timeStamp: paymentData.timeStamp, + nonceStr: paymentData.nonceStr, + package: paymentData.package, + signType: paymentData.signType || 'MD5', + paySign: paymentData.paySign, + success: (payRes) => { + console.log('支付成功', payRes) + + // 按照微信文档推荐,仅依赖查询接口确认支付结果,不做本地立即标记 + // 支付成功后轮询后端订单状态 3~5 次,确保通过微信订单查询接口确认成功 + pollOrderStatus(orderId, 5, 1000, (ok, order) => { + if (ok) { + wx.showToast({ + title: '支付成功', + icon: 'success', + duration: 2000 + }) + // 将本地订单信息一并透传给业务方 + success && success({ payRes, order }) + } else { + // 落库状态暂未确认,但微信侧已成功,一般稍后会自动对齐 + wx.showToast({ + title: '支付结果处理中,请稍后在历史中查看', + icon: 'none', + duration: 2500 + }) + success && success({ payRes, order: null }) + } + }) + }, + fail: (payErr) => { + console.error('支付失败', payErr) + + if (payErr.errMsg.indexOf('cancel') !== -1) { + wx.showToast({ + title: '支付已取消', + icon: 'none' + }) + } else { + wx.showToast({ + title: '支付失败', + icon: 'none' + }) + } + + fail && fail(payErr) + } + }) + } else { + wx.showToast({ + title: res.data.message || '创建订单失败', + icon: 'none' + }) + fail && fail(res) + } + }, + fail: (err) => { + wx.hideLoading() + console.error('请求失败', err) + + wx.showToast({ + title: '网络请求失败', + icon: 'none' + }) + + fail && fail(err) + } + }) +} + +/** + * 通知后端支付成功 + */ +function notifyPaymentSuccess(orderId, prepayId) { + wx.request({ + url: `${app.globalData.apiBase}/api/payment/notify`, + method: 'POST', + header: { + 'Authorization': `Bearer ${wx.getStorageSync('token')}`, + 'Content-Type': 'application/json' + }, + data: { + orderId, + prepayId, + status: 'success' + }, + success: (res) => { + console.log('支付通知成功', res) + }, + fail: (err) => { + console.error('支付通知失败', err) + } + }) +} + +/** + * 查询订单状态 + */ +function queryOrderStatus(orderId, callback) { + wx.request({ + url: `${app.globalData.apiBase}/api/payment/query`, + method: 'GET', + header: { + 'Authorization': `Bearer ${wx.getStorageSync('token')}` + }, + data: { orderId }, + success: (res) => { + if (res.statusCode === 200 && res.data.code === 200) { + callback && callback(true, res.data.data) + } else { + callback && callback(false, null) + } + }, + fail: () => { + callback && callback(false, null) + } + }) +} + +/** + * 按照微信文档建议:支付成功后,基于商户订单号轮询查询订单状态(本项目由后端代理查询) + * @param {String} orderId 商户订单号(如 FACE20260226151900001) + * @param {Number} maxAttempts 最大轮询次数 + * @param {Number} intervalMs 间隔毫秒 + * @param {Function} done 回调 (ok:boolean, order?:object) + */ +function pollOrderStatus(orderId, maxAttempts = 5, intervalMs = 1000, done) { + let attempts = 0 + + const tick = () => { + attempts += 1 + queryOrderStatus(orderId, (ok, order) => { + if (ok && order && (order.status === 'paid' || order.status === 'completed')) { + done && done(true, order) + return + } + if (attempts >= maxAttempts) { + done && done(false, order || null) + return + } + setTimeout(tick, intervalMs) + }) + } + + tick() +} + +/** + * 购买VIP会员 + * @param {String} vipType - 'month' | 'quarter' | 'year' | 'lifetime' + */ +function purchaseVIP(vipType, success, fail) { + const prices = { + month: 1990, // 19.9元 + quarter: 4990, // 49.9元 + year: 9900, // 99元 + lifetime: 19900, // 199元 + personal_insight: 19800 // 198元 - 个人深度洞察版 + } + + const names = { + month: '月度VIP会员', + quarter: '季度VIP会员', + year: '年度VIP会员', + lifetime: '终身VIP会员', + personal_insight: '个人深度洞察版' + } + + const orderId = generateOrderId('vip') + + wxPay({ + orderId, + amount: prices[vipType], + description: `MBTI性格测试 - ${names[vipType]}`, + productType: 'vip', + success: (res) => { + // 更新VIP状态 + updateVIPStatus(vipType) + success && success(res) + }, + fail + }) +} + +/** + * 购买测试次数 + * @param {Number} count - 购买次数 + */ +function purchaseTestCount(count, success, fail) { + // 单次价格 3.9元,10次29元,50次99元 + let price = count * 390 + if (count >= 10) price = Math.floor(count * 290) + if (count >= 50) price = Math.floor(count * 198) + + const orderId = generateOrderId('test_count') + + wxPay({ + orderId, + amount: price, + description: `MBTI性格测试 - ${count}次测试次数`, + productType: 'test_count', + success: (res) => { + // 更新测试次数 + addTestCount(count) + success && success(res) + }, + fail + }) +} + +/** + * 购买单次测试 + * @param {String} testType - 'mbti' | 'disc' | 'pdp' | 'ai' + */ +function purchaseSingleTest(testType, success, fail) { + const prices = { + mbti: 990, // 9.9元 + disc: 690, // 6.9元 + pdp: 690, // 6.9元 + ai: 1990 // 19.9元 + } + + const names = { + mbti: 'MBTI性格测试', + disc: 'DISC行为风格测试', + pdp: 'PDP动物性格测试', + ai: 'AI人脸性格分析' + } + + const orderId = generateOrderId(`single_${testType}`) + + wxPay({ + orderId, + amount: prices[testType], + description: names[testType], + productType: 'single_test', + success: (res) => { + // 解锁该测试 + unlockTest(testType) + success && success(res) + }, + fail + }) +} + +/** + * 使用后台定价购买单项测试/报告 + * 支持:人脸测试/MBTI测试/DISC测试/PDP测试/完整报告/团队分析 + * @param {String} productType - 'face' | 'mbti' | 'disc' | 'pdp' | 'report' | 'team_analysis' + * @param {String} description - 商品描述 + * @param {Function|Object} extra - 兼容两种调用方式: + * - purchaseByPricing('face', 'xxx', success, fail) + * - purchaseByPricing('face', 'xxx', { testResultId, success, fail }) + */ +function purchaseByPricing(productType, description, extra, maybeFail) { + let opts = {} + if (typeof extra === 'function' || extra === undefined) { + opts.success = extra + opts.fail = maybeFail + } else { + opts = extra || {} + } + + const { testResultId, success, fail } = opts + const orderId = generateOrderId(productType) + + wxPay({ + orderId, + amount: 0, // 金额交由后端根据定价配置计算(personal/enterprise) + description, + productType, + testResultId, + success, + fail + }) +} + +// 人脸测试完整报告 +// 兼容两种调用方式: +// - purchaseFaceTest(success, fail) +// - purchaseFaceTest({ testResultId, success, fail }) +function purchaseFaceTest(arg1, arg2) { + let opts = {} + if (typeof arg1 === 'function' || arg1 === undefined) { + opts.success = arg1 + opts.fail = arg2 + } else { + opts = arg1 || {} + } + + const { testResultId, success, fail } = opts + + purchaseByPricing('face', 'AI人脸性格分析完整报告', { testResultId, success, fail }) +} + +// MBTI测试付费版(支持 purchaseMbtiTest({ testResultId, success, fail })) +function purchaseMbtiTest(arg1, arg2) { + const opts = typeof arg1 === 'function' || arg1 == null ? { success: arg1, fail: arg2 } : (arg1 || {}) + const { testResultId, success, fail } = opts + purchaseByPricing('mbti', 'MBTI性格测试付费版', { testResultId, success, fail }) +} + +// DISC测试付费版 +function purchaseDiscTest(arg1, arg2) { + const opts = typeof arg1 === 'function' || arg1 == null ? { success: arg1, fail: arg2 } : (arg1 || {}) + const { testResultId, success, fail } = opts + purchaseByPricing('disc', 'DISC行为风格测试付费版', { testResultId, success, fail }) +} + +// PDP测试付费版 +function purchasePdpTest(arg1, arg2) { + const opts = typeof arg1 === 'function' || arg1 == null ? { success: arg1, fail: arg2 } : (arg1 || {}) + const { testResultId, success, fail } = opts + purchaseByPricing('pdp', 'PDP动物性格测试付费版', { testResultId, success, fail }) +} + +// 简历综合分析付费版(支持 purchaseResumeAnalysis({ testResultId, success, fail })) +function purchaseResumeAnalysis(arg1, arg2) { + const opts = typeof arg1 === 'function' || arg1 == null ? { success: arg1, fail: arg2 } : (arg1 || {}) + const { testResultId, success, fail } = opts + purchaseByPricing('resume', '简历综合分析付费版', { testResultId, success, fail }) +} + +// 完整报告(整合MBTI/DISC/PDP/AI人脸等深度解读) +function purchaseFullReport(success, fail) { + purchaseByPricing('report', '完整人格与职业发展报告', success, fail) +} + +// 团队分析服务 +function purchaseTeamAnalysis(success, fail) { + purchaseByPricing('team_analysis', '团队性格组合与冲突分析服务', success, fail) +} + +/** + * 企业充值 + * 支持两种调用: + * - recharge(100, success, fail) + * - recharge({ amountYuan: 100, enterpriseId: 6, success, fail }) + */ +function recharge(arg1, arg2, arg3) { + const opts = (typeof arg1 === 'object' && arg1 !== null) + ? arg1 + : { amountYuan: arg1, success: arg2, fail: arg3 } + const safeAmount = Number(opts.amountYuan) || 0 + const amount = Math.round(safeAmount * 100) + const orderId = generateOrderId('recharge') + + wxPay({ + orderId, + amount, + description: `账户充值 ¥${safeAmount.toFixed(2)}`, + productType: 'recharge', + enterpriseId: opts.enterpriseId || 0, + success: opts.success, + fail: opts.fail + }) +} + +/** + * 个人深度服务(价格从 PricingConfig deep_personal.categories 中读取) + * 支持两种调用方式: + * - purchasePersonalDeepService(deepProductId, success, fail) + * - purchasePersonalDeepService({ deepProductId, description, success, fail }) + * @param {String|Object} arg1 + * @param {Function} [arg2] + * @param {Function} [arg3] + */ +function purchasePersonalDeepService(arg1, arg2, arg3) { + let deepProductId = '' + let description = '' + let success + let fail + + if (typeof arg1 === 'object' && arg1 !== null) { + deepProductId = arg1.deepProductId || '' + description = arg1.description || '' + success = arg1.success + fail = arg1.fail + } else { + deepProductId = typeof arg1 === 'string' ? arg1 : '' + success = arg2 + fail = arg3 + } + + // 默认标题兜底 + const desc = description || '个人深度服务(1v1深度解读)' + const orderId = generateOrderId('deep_personal') + + wxPay({ + orderId, + amount: 0, + description: desc, + productType: 'deep_personal', + deepProductId, + success, + fail + }) +} + +/** + * 团队深度服务(价格从 PricingConfig.deep.team 读取) + */ +function purchaseTeamDeepService(success, fail) { + const orderId = generateOrderId('deep_team') + + wxPay({ + orderId, + amount: 0, + description: '团队深度服务(团队画像+策略)', + productType: 'deep_team', + success, + fail + }) +} + +/** + * 更新VIP状态 + */ +function updateVIPStatus(vipType) { + const durations = { + month: 30, + quarter: 90, + year: 365, + lifetime: 36500 // 100年 + } + + const expireDate = new Date() + expireDate.setDate(expireDate.getDate() + durations[vipType]) + + const vipInfo = { + isVIP: true, + vipType, + expireDate: expireDate.toISOString(), + purchaseDate: new Date().toISOString() + } + + wx.setStorageSync('vipInfo', vipInfo) + app.globalData.vipInfo = vipInfo +} + +/** + * 增加测试次数 + */ +function addTestCount(count) { + const currentCount = wx.getStorageSync('testCount') || 0 + const newCount = currentCount + count + wx.setStorageSync('testCount', newCount) + app.globalData.testCount = newCount +} + +/** + * 解锁单次测试 + */ +function unlockTest(testType) { + const unlockedTests = wx.getStorageSync('unlockedTests') || [] + if (!unlockedTests.includes(testType)) { + unlockedTests.push(testType) + wx.setStorageSync('unlockedTests', unlockedTests) + } + app.globalData.unlockedTests = unlockedTests +} + +/** + * 检查是否是VIP + */ +function checkVIP() { + const vipInfo = wx.getStorageSync('vipInfo') + if (!vipInfo || !vipInfo.isVIP) return false + + const expireDate = new Date(vipInfo.expireDate) + return expireDate > new Date() +} + +/** + * 检查测试是否可用(VIP或已解锁或有测试次数) + */ +function canTakeTest(testType) { + // 临时策略:所有测试免费开放,直接返回 true + // 保留原有支付与权益逻辑,后续若恢复收费可还原为 VIP/解锁/次数判断 + return true +} + +/** + * 消耗一次测试次数 + */ +function consumeTestCount() { + const testCount = wx.getStorageSync('testCount') || 0 + if (testCount > 0) { + wx.setStorageSync('testCount', testCount - 1) + app.globalData.testCount = testCount - 1 + return true + } + return false +} + +/** + * 获取用户权益信息 + */ +function getUserBenefits() { + return { + isVIP: checkVIP(), + vipInfo: wx.getStorageSync('vipInfo') || null, + testCount: wx.getStorageSync('testCount') || 0, + unlockedTests: wx.getStorageSync('unlockedTests') || [] + } +} + +module.exports = { + wxPay, + queryOrderStatus, + pollOrderStatus, + generateOrderId, + purchaseVIP, + purchaseTestCount, + purchaseSingleTest, + // 基于后台定价的购买入口(测试/报告/团队分析/深度服务/充值) + purchaseFaceTest, + purchaseMbtiTest, + purchaseDiscTest, + purchasePdpTest, + purchaseResumeAnalysis, + purchaseFullReport, + purchaseTeamAnalysis, + recharge, + purchasePersonalDeepService, + purchaseTeamDeepService, + checkVIP, + canTakeTest, + consumeTestCount, + getUserBenefits, + updateVIPStatus, + addTestCount, + unlockTest +} diff --git a/miniprogram/utils/phoneAuth.js b/miniprogram/utils/phoneAuth.js new file mode 100644 index 0000000..9108fd9 --- /dev/null +++ b/miniprogram/utils/phoneAuth.js @@ -0,0 +1,113 @@ +/** + * 手机号授权工具:基于微信 getPhoneNumber + 服务器端换取手机号接口 + * 个人资料完整性检查:头像、昵称、手机号为必填,生日和性别选填 + */ + +/** + * 个人资料是否已完善(头像、昵称、手机号必填,生日和性别选填) + * @returns {boolean} + */ +function isProfileComplete() { + const app = getApp() + const user = app.globalData.userInfo || wx.getStorageSync('userInfo') + if (!user) return false + const avatar = (user.avatar || user.avatarUrl || '').trim() + const nickname = (user.nickname || user.nickName || '').trim() + const phone = (user.phone || user.phoneNumber || '').trim() + return avatar.length > 0 && nickname.length > 0 && phone.length > 0 +} + +/** + * 若资料未完善则跳转到个人资料页,需登录 + * @returns {boolean} true=已完善可继续,false=已跳转 + */ +function ensureProfileCompleteAndRedirect() { + const app = getApp() + const token = app.globalData.token || wx.getStorageSync('token') + if (!token) return true + if (isProfileComplete()) return true + wx.showToast({ title: '请先完善个人资料', icon: 'none' }) + wx.navigateTo({ url: '/pages/user-profile/index' }) + return false +} + +/** + * 当前用户是否已有手机号(从 globalData.userInfo 或 storage 读取) + * @returns {boolean} + */ +function hasPhone() { + const app = getApp() + const user = app.globalData.userInfo || wx.getStorageSync('userInfo') + const phone = (user && (user.phone || user.phoneNumber)) ? String(user.phone || user.phoneNumber).trim() : '' + return phone.length > 0 +} + +/** + * 使用 getPhoneNumber 回调里的 code 调用后端接口换取手机号,并写回 userInfo + * @param {string} code + * @returns {Promise} resolve 为更新后的 userInfo + */ +function bindPhoneByCode(code) { + return new Promise((resolve, reject) => { + if (!code) { + wx.showToast({ title: '获取手机号失败', icon: 'none' }) + reject(new Error('empty code')) + return + } + + const app = getApp() + const token = app.globalData.token || wx.getStorageSync('token') + if (!token) { + wx.showToast({ title: '请先登录', icon: 'none' }) + reject(new Error('no token')) + return + } + + const apiBase = app.globalData.apiBase || '' + if (!apiBase) { + wx.showToast({ title: '服务未配置', icon: 'none' }) + reject(new Error('no api base')) + return + } + + wx.showLoading({ title: '处理中...', mask: true }) + wx.request({ + url: `${apiBase.replace(/\/$/, '')}/api/auth/wechat/phone`, + method: 'POST', + header: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json', + }, + data: { code }, + success: (res) => { + wx.hideLoading() + if (res.statusCode === 200 && res.data && res.data.code === 200) { + const data = res.data.data || {} + const user = data.user || app.globalData.userInfo || {} + const phone = data.phone || user.phone || '' + const newUser = { ...user, phone } + app.globalData.userInfo = newUser + wx.setStorageSync('userInfo', newUser) + wx.showToast({ title: '授权成功', icon: 'success' }) + resolve(newUser) + } else { + const msg = res.data && res.data.message ? res.data.message : '获取手机号失败' + wx.showToast({ title: msg, icon: 'none' }) + reject(new Error(msg)) + } + }, + fail: () => { + wx.hideLoading() + wx.showToast({ title: '网络请求失败', icon: 'none' }) + reject(new Error('network error')) + }, + }) + }) +} + +module.exports = { + hasPhone, + bindPhoneByCode, + isProfileComplete, + ensureProfileCompleteAndRedirect, +} diff --git a/miniprogram/utils/questions.js b/miniprogram/utils/questions.js new file mode 100644 index 0000000..161866d --- /dev/null +++ b/miniprogram/utils/questions.js @@ -0,0 +1,117 @@ +// utils/questions.js - 测试题库 + +// MBTI测试题(精简版30题,每个维度约7-8题) +const mbtiQuestions = [ + // E vs I 维度 (8题) + { id: 1, question: "在社交场合中,您通常会:", dimension: "EI", options: [{ value: "E", text: "认识新朋友,扩大社交圈" }, { value: "I", text: "与已认识的朋友交流" }] }, + { id: 2, question: "您更喜欢:", dimension: "EI", options: [{ value: "E", text: "在团队中工作" }, { value: "I", text: "独立工作" }] }, + { id: 3, question: "当您需要充电时,您会选择:", dimension: "EI", options: [{ value: "E", text: "与朋友聚会" }, { value: "I", text: "独处休息" }] }, + { id: 4, question: "在会议中,您通常:", dimension: "EI", options: [{ value: "E", text: "积极发言" }, { value: "I", text: "先思考再发表" }] }, + { id: 5, question: "您更倾向于:", dimension: "EI", options: [{ value: "E", text: "认识很多人" }, { value: "I", text: "与少数人深交" }] }, + { id: 6, question: "面对问题时,您更倾向于:", dimension: "EI", options: [{ value: "E", text: "与他人讨论" }, { value: "I", text: "独自思考" }] }, + { id: 7, question: "您更喜欢的休闲活动是:", dimension: "EI", options: [{ value: "E", text: "团体活动" }, { value: "I", text: "个人爱好" }] }, + { id: 8, question: "在陌生环境中,您通常会:", dimension: "EI", options: [{ value: "E", text: "主动与人交谈" }, { value: "I", text: "观察周围环境" }] }, + + // S vs N 维度 (7题) + { id: 9, question: "您更关注:", dimension: "SN", options: [{ value: "S", text: "具体的细节和事实" }, { value: "N", text: "整体概念和可能性" }] }, + { id: 10, question: "您更信任:", dimension: "SN", options: [{ value: "S", text: "实际经验" }, { value: "N", text: "直觉和灵感" }] }, + { id: 11, question: "您更喜欢:", dimension: "SN", options: [{ value: "S", text: "按步骤执行" }, { value: "N", text: "创造性解决" }] }, + { id: 12, question: "学习新事物时,您更喜欢:", dimension: "SN", options: [{ value: "S", text: "实际操作" }, { value: "N", text: "理论学习" }] }, + { id: 13, question: "您更喜欢的工作类型是:", dimension: "SN", options: [{ value: "S", text: "明确的任务" }, { value: "N", text: "创新的项目" }] }, + { id: 14, question: "您描述事物时更倾向于:", dimension: "SN", options: [{ value: "S", text: "具体描述" }, { value: "N", text: "使用比喻" }] }, + { id: 15, question: "您更关心:", dimension: "SN", options: [{ value: "S", text: "当下的现实" }, { value: "N", text: "未来的可能" }] }, + + // T vs F 维度 (8题) + { id: 16, question: "做决定时,您更依赖:", dimension: "TF", options: [{ value: "T", text: "逻辑分析" }, { value: "F", text: "个人价值" }] }, + { id: 17, question: "在争论中,您更看重:", dimension: "TF", options: [{ value: "T", text: "事实真相" }, { value: "F", text: "和谐关系" }] }, + { id: 18, question: "给予反馈时,您更注重:", dimension: "TF", options: [{ value: "T", text: "直接指出问题" }, { value: "F", text: "考虑对方感受" }] }, + { id: 19, question: "评估方案时,您更关注:", dimension: "TF", options: [{ value: "T", text: "效率和结果" }, { value: "F", text: "对人的影响" }] }, + { id: 20, question: "当朋友遇到困难时,您更倾向于:", dimension: "TF", options: [{ value: "T", text: "分析问题提供建议" }, { value: "F", text: "倾听并给予支持" }] }, + { id: 21, question: "您更欣赏的品质是:", dimension: "TF", options: [{ value: "T", text: "理性客观" }, { value: "F", text: "善解人意" }] }, + { id: 22, question: "您认为好的决定应该:", dimension: "TF", options: [{ value: "T", text: "基于客观分析" }, { value: "F", text: "考虑各方感受" }] }, + { id: 23, question: "当与他人意见不同时,您更倾向于:", dimension: "TF", options: [{ value: "T", text: "坚持正确观点" }, { value: "F", text: "寻求共识" }] }, + + // J vs P 维度 (7题) + { id: 24, question: "您更喜欢的工作方式是:", dimension: "JP", options: [{ value: "J", text: "有计划地进行" }, { value: "P", text: "随机应变" }] }, + { id: 25, question: "对于截止日期,您通常:", dimension: "JP", options: [{ value: "J", text: "提前完成" }, { value: "P", text: "在最后完成" }] }, + { id: 26, question: "您的生活方式更倾向于:", dimension: "JP", options: [{ value: "J", text: "有条理有规律" }, { value: "P", text: "灵活随意" }] }, + { id: 27, question: "面对选择时,您更倾向于:", dimension: "JP", options: [{ value: "J", text: "快速做出决定" }, { value: "P", text: "保持开放选项" }] }, + { id: 28, question: "您的桌面通常是:", dimension: "JP", options: [{ value: "J", text: "整洁有序" }, { value: "P", text: "创意性混乱" }] }, + { id: 29, question: "计划改变时,您的反应是:", dimension: "JP", options: [{ value: "J", text: "感到不安" }, { value: "P", text: "觉得有趣" }] }, + { id: 30, question: "您更喜欢:", dimension: "JP", options: [{ value: "J", text: "事先规划" }, { value: "P", text: "即兴发挥" }] } +] + +// DISC测试题 (20题) +const discQuestions = [ + { id: 1, question: "在团队中,您更倾向于:", options: [{ value: "D", text: "主导决策,带领团队" }, { value: "I", text: "活跃气氛,激励成员" }, { value: "S", text: "支持他人,确保和谐" }, { value: "C", text: "分析数据,确保质量" }] }, + { id: 2, question: "面对挑战时,您的第一反应是:", options: [{ value: "D", text: "立即行动" }, { value: "I", text: "寻找支持" }, { value: "S", text: "冷静思考" }, { value: "C", text: "收集信息" }] }, + { id: 3, question: "您在工作中最看重的是:", options: [{ value: "D", text: "成果和效率" }, { value: "I", text: "认可和赞赏" }, { value: "S", text: "稳定和安全" }, { value: "C", text: "准确和质量" }] }, + { id: 4, question: "与他人沟通时,您通常:", options: [{ value: "D", text: "直接了当" }, { value: "I", text: "热情友好" }, { value: "S", text: "耐心倾听" }, { value: "C", text: "逻辑清晰" }] }, + { id: 5, question: "压力之下,您会:", options: [{ value: "D", text: "更加强势" }, { value: "I", text: "寻求鼓励" }, { value: "S", text: "保持冷静" }, { value: "C", text: "更加谨慎" }] }, + { id: 6, question: "您认为自己的优势是:", options: [{ value: "D", text: "决断力强" }, { value: "I", text: "人际关系好" }, { value: "S", text: "可靠稳定" }, { value: "C", text: "分析能力强" }] }, + { id: 7, question: "在会议中,您通常扮演:", options: [{ value: "D", text: "主导者" }, { value: "I", text: "激励者" }, { value: "S", text: "调和者" }, { value: "C", text: "分析者" }] }, + { id: 8, question: "您最不喜欢的工作环境是:", options: [{ value: "D", text: "进展缓慢" }, { value: "I", text: "被孤立" }, { value: "S", text: "变化太快" }, { value: "C", text: "混乱无序" }] }, + { id: 9, question: "做决定时,您更依赖:", options: [{ value: "D", text: "直觉经验" }, { value: "I", text: "他人意见" }, { value: "S", text: "过去经验" }, { value: "C", text: "数据事实" }] }, + { id: 10, question: "您的工作风格是:", options: [{ value: "D", text: "快速高效" }, { value: "I", text: "灵活多变" }, { value: "S", text: "稳定持续" }, { value: "C", text: "严谨细致" }] }, + { id: 11, question: "遇到冲突时,您会:", options: [{ value: "D", text: "直面解决" }, { value: "I", text: "调解双方" }, { value: "S", text: "避免冲突" }, { value: "C", text: "分析原因" }] }, + { id: 12, question: "您期望的领导风格是:", options: [{ value: "D", text: "给予挑战" }, { value: "I", text: "认可表扬" }, { value: "S", text: "稳定支持" }, { value: "C", text: "明确指导" }] }, + { id: 13, question: "处理任务时,您更注重:", options: [{ value: "D", text: "速度效率" }, { value: "I", text: "创意新颖" }, { value: "S", text: "过程协作" }, { value: "C", text: "质量准确" }] }, + { id: 14, question: "您的社交方式是:", options: [{ value: "D", text: "目的明确" }, { value: "I", text: "广泛社交" }, { value: "S", text: "深度交往" }, { value: "C", text: "选择性社交" }] }, + { id: 15, question: "您理想的工作节奏是:", options: [{ value: "D", text: "快节奏" }, { value: "I", text: "灵活多变" }, { value: "S", text: "稳定有序" }, { value: "C", text: "有条理" }] }, + { id: 16, question: "面对变化,您的态度是:", options: [{ value: "D", text: "主动拥抱" }, { value: "I", text: "积极适应" }, { value: "S", text: "需要时间" }, { value: "C", text: "谨慎评估" }] }, + { id: 17, question: "您的时间管理风格是:", options: [{ value: "D", text: "高效利用" }, { value: "I", text: "灵活安排" }, { value: "S", text: "按部就班" }, { value: "C", text: "精确规划" }] }, + { id: 18, question: "激励您的是:", options: [{ value: "D", text: "成就控制" }, { value: "I", text: "认可社交" }, { value: "S", text: "稳定归属" }, { value: "C", text: "正确标准" }] }, + { id: 19, question: "您处理细节的方式是:", options: [{ value: "D", text: "关注大局" }, { value: "I", text: "可能忽略" }, { value: "S", text: "认真对待" }, { value: "C", text: "极度重视" }] }, + { id: 20, question: "您对规则的态度是:", options: [{ value: "D", text: "灵活打破" }, { value: "I", text: "灵活运用" }, { value: "S", text: "遵守维护" }, { value: "C", text: "严格遵守" }] } +] + +// PDP测试题 (20题) +const pdpQuestions = [ + { id: 1, question: "面对紧急任务,您的第一反应是:", options: [{ value: "Tiger", text: "立即行动" }, { value: "Peacock", text: "召集团队" }, { value: "Koala", text: "冷静分析" }, { value: "Owl", text: "仔细规划" }, { value: "Chameleon", text: "灵活应对" }] }, + { id: 2, question: "在社交场合,您通常会:", options: [{ value: "Tiger", text: "主导话题" }, { value: "Peacock", text: "活跃气氛" }, { value: "Koala", text: "安静倾听" }, { value: "Owl", text: "观察分析" }, { value: "Chameleon", text: "根据对象调整" }] }, + { id: 3, question: "您最看重工作中的:", options: [{ value: "Tiger", text: "权力和成就" }, { value: "Peacock", text: "认可和赞赏" }, { value: "Koala", text: "稳定和和谐" }, { value: "Owl", text: "准确和质量" }, { value: "Chameleon", text: "平衡和适应" }] }, + { id: 4, question: "处理冲突时,您倾向于:", options: [{ value: "Tiger", text: "直接解决" }, { value: "Peacock", text: "调解双方" }, { value: "Koala", text: "避免冲突" }, { value: "Owl", text: "理性处理" }, { value: "Chameleon", text: "视情况而定" }] }, + { id: 5, question: "您的决策风格是:", options: [{ value: "Tiger", text: "果断迅速" }, { value: "Peacock", text: "直觉判断" }, { value: "Koala", text: "深思熟虑" }, { value: "Owl", text: "数据分析" }, { value: "Chameleon", text: "灵活决策" }] }, + { id: 6, question: "面对压力,您会:", options: [{ value: "Tiger", text: "更加强势" }, { value: "Peacock", text: "寻求支持" }, { value: "Koala", text: "保持冷静" }, { value: "Owl", text: "更加谨慎" }, { value: "Chameleon", text: "调整策略" }] }, + { id: 7, question: "您的领导风格是:", options: [{ value: "Tiger", text: "指挥型" }, { value: "Peacock", text: "激励型" }, { value: "Koala", text: "支持型" }, { value: "Owl", text: "专家型" }, { value: "Chameleon", text: "教练型" }] }, + { id: 8, question: "您处理细节的方式是:", options: [{ value: "Tiger", text: "关注大局" }, { value: "Peacock", text: "可能忽略" }, { value: "Koala", text: "认真对待" }, { value: "Owl", text: "极度重视" }, { value: "Chameleon", text: "视情况决定" }] }, + { id: 9, question: "您的沟通方式是:", options: [{ value: "Tiger", text: "直接简短" }, { value: "Peacock", text: "热情生动" }, { value: "Koala", text: "温和耐心" }, { value: "Owl", text: "逻辑清晰" }, { value: "Chameleon", text: "根据对象调整" }] }, + { id: 10, question: "您对变化的态度是:", options: [{ value: "Tiger", text: "主动推动" }, { value: "Peacock", text: "积极拥抱" }, { value: "Koala", text: "需要适应" }, { value: "Owl", text: "谨慎评估" }, { value: "Chameleon", text: "随机应变" }] }, + { id: 11, question: "您的时间管理风格是:", options: [{ value: "Tiger", text: "追求速度" }, { value: "Peacock", text: "灵活安排" }, { value: "Koala", text: "稳定执行" }, { value: "Owl", text: "精确规划" }, { value: "Chameleon", text: "根据情况调整" }] }, + { id: 12, question: "您被什么激励:", options: [{ value: "Tiger", text: "成就权力" }, { value: "Peacock", text: "认可赞赏" }, { value: "Koala", text: "安全归属" }, { value: "Owl", text: "正确标准" }, { value: "Chameleon", text: "多样平衡" }] }, + { id: 13, question: "您的学习方式是:", options: [{ value: "Tiger", text: "边做边学" }, { value: "Peacock", text: "互动讨论" }, { value: "Koala", text: "循序渐进" }, { value: "Owl", text: "深入研究" }, { value: "Chameleon", text: "多种结合" }] }, + { id: 14, question: "您对规则的态度是:", options: [{ value: "Tiger", text: "灵活打破" }, { value: "Peacock", text: "不拘一格" }, { value: "Koala", text: "遵守维护" }, { value: "Owl", text: "严格遵守" }, { value: "Chameleon", text: "灵活处理" }] }, + { id: 15, question: "您在团队中的角色是:", options: [{ value: "Tiger", text: "领导者" }, { value: "Peacock", text: "激励者" }, { value: "Koala", text: "协调者" }, { value: "Owl", text: "专家" }, { value: "Chameleon", text: "多面手" }] }, + { id: 16, question: "您的工作节奏是:", options: [{ value: "Tiger", text: "快节奏" }, { value: "Peacock", text: "充满活力" }, { value: "Koala", text: "稳定有序" }, { value: "Owl", text: "有条理" }, { value: "Chameleon", text: "灵活调整" }] }, + { id: 17, question: "您最大的优势是:", options: [{ value: "Tiger", text: "执行力" }, { value: "Peacock", text: "影响力" }, { value: "Koala", text: "可靠性" }, { value: "Owl", text: "准确性" }, { value: "Chameleon", text: "适应力" }] }, + { id: 18, question: "您的人际关系特点是:", options: [{ value: "Tiger", text: "目标导向" }, { value: "Peacock", text: "朋友众多" }, { value: "Koala", text: "关系稳定" }, { value: "Owl", text: "志同道合" }, { value: "Chameleon", text: "灵活建立" }] }, + { id: 19, question: "面对批评,您的反应是:", options: [{ value: "Tiger", text: "可能反驳" }, { value: "Peacock", text: "可能受伤" }, { value: "Koala", text: "接受思考" }, { value: "Owl", text: "分析合理性" }, { value: "Chameleon", text: "灵活调整" }] }, + { id: 20, question: "您的理想工作环境是:", options: [{ value: "Tiger", text: "充满挑战" }, { value: "Peacock", text: "互动频繁" }, { value: "Koala", text: "稳定和谐" }, { value: "Owl", text: "有序规范" }, { value: "Chameleon", text: "灵活多变" }] } +] + +/** + * Fisher-Yates 洗牌:随机打乱题目顺序,同时随机打乱每题的选项顺序 + * 不修改原数组,返回深拷贝后的新数组 + * @param {Array} questions - 原题目数组 + * @returns {Array} 打乱后的题目数组 + */ +function shuffleQuestions(questions) { + // 深拷贝,避免污染原始数组 + const arr = questions.map(q => ({ + ...q, + options: q.options.slice().sort(() => Math.random() - 0.5) + })) + // Fisher-Yates 打乱题目顺序 + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]] + } + return arr +} + +module.exports = { + mbtiQuestions, + discQuestions, + pdpQuestions, + shuffleQuestions +} diff --git a/miniprogram/utils/request.js b/miniprogram/utils/request.js new file mode 100644 index 0000000..6c53b9b --- /dev/null +++ b/miniprogram/utils/request.js @@ -0,0 +1,109 @@ +/** + * 统一请求封装:自动拼接 baseURL、携带 token、处理 401 + * 使用前需已执行 App(),否则 getApp() 在 require 时可能未就绪,这里在请求时再取 app + */ +function getAppSafe() { + try { + return getApp() + } catch (e) { + return null + } +} + +function getToken() { + const app = getAppSafe() + return (app && app.globalData && app.globalData.token) || wx.getStorageSync('token') || '' +} + +function getApiBase() { + const app = getAppSafe() + return (app && app.globalData && app.globalData.apiBase) || '' +} + +/** + * 清除登录态(401 或主动退出时调用) + */ +function clearLoginState() { + const app = getAppSafe() + if (app && app.globalData) { + app.globalData.token = null + app.globalData.userInfo = null + app.globalData.openId = null + } + try { + wx.removeStorageSync('token') + wx.removeStorageSync('userInfo') + } catch (e) {} +} + +/** + * 发起请求 + * @param {Object} options - 同 wx.request,url 可为相对路径(自动加 apiBase) + * @param {boolean} options.needAuth - 是否携带 Authorization,默认 true + * @param {boolean} options.allow401 - 401 时是否静默清除登录态而不 fail,默认 true + */ +function request(options) { + const apiBase = getApiBase() + const url = options.url + const fullUrl = url.startsWith('http') ? url : `${apiBase.replace(/\/$/, '')}${url.startsWith('/') ? '' : '/'}${url}` + const needAuth = options.needAuth !== false + const allow401 = options.allow401 !== false + + const header = { + 'Content-Type': 'application/json', + ...(options.header || {}) + } + if (needAuth) { + const token = getToken() + if (token) header['Authorization'] = `Bearer ${token}` + } + + const success = options.success + const fail = options.fail + const complete = options.complete + + return wx.request({ + ...options, + url: fullUrl, + header, + success(res) { + if (res.statusCode === 401 && allow401) { + clearLoginState() + } + if (success) success(res) + }, + fail(err) { + if (fail) fail(err) + }, + complete(res) { + if (complete) complete(res) + } + }) +} + +/** + * Promise 版 request,便于 async/await + */ +function requestPromise(options) { + return new Promise((resolve, reject) => { + request({ + ...options, + success(res) { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(res) + } else { + reject(new Error(res.data && res.data.message ? res.data.message : '请求失败')) + } + }, + fail: reject + }) + }) +} + +module.exports = { + request, + requestPromise, + getToken, + getApiBase, + clearLoginState +} diff --git a/miniprogram/utils/resultFormat.js b/miniprogram/utils/resultFormat.js new file mode 100644 index 0000000..a8976fe --- /dev/null +++ b/miniprogram/utils/resultFormat.js @@ -0,0 +1,95 @@ +/** + * 测试结果摘要格式化:类型 + 整数百分比(与管理后台一致) + */ + +function toIntPercent(value) { + if (value == null) return 0 + const n = typeof value === 'number' ? value : Number(value) + return Number.isFinite(n) ? Math.round(n) : 0 +} + +/** + * 根据测试类型和原始结果,生成带整数百分比的摘要文案 + * @param {object} data - 单条测试结果(mbtiResult / discResult / pdpResult) + * @param {string} testType - 'mbti' | 'disc' | 'pdp' + * @returns {string} + */ +function formatTestSummary(data, testType) { + if (!data || typeof data !== 'object') return '' + const t = (testType || '').toLowerCase() + + if (t === 'mbti') { + const label = data.mbtiType || data.type || data.result || '' + const dims = data.dimensionScores + if (dims && typeof dims === 'object') { + const parts = [] + const order = ['EI', 'SN', 'TF', 'JP'] + for (const key of order) { + const pct = dims[key] && (dims[key].percentage != null ? dims[key].percentage : dims[key].dominant) + if (pct != null) parts.push(toIntPercent(pct) + '%') + } + if (parts.length) return label + ' (' + parts.join(' ') + ')' + } + return String(label) + } + + if (t === 'disc') { + const desc = data.description && data.description.type + const label = (typeof desc === 'string' && desc) ? desc : ((data.dominantType ? data.dominantType + '型' : '') || (data.disc || '')) + const pct = data.percentages + if (pct && typeof pct === 'object') { + const d = toIntPercent(pct.D != null ? pct.D : pct.d) + const i = toIntPercent(pct.I != null ? pct.I : pct.i) + const s = toIntPercent(pct.S != null ? pct.S : pct.s) + const c = toIntPercent(pct.C != null ? pct.C : pct.c) + return label + ' D:' + d + '% I:' + i + '% S:' + s + '% C:' + c + '%' + } + return label || '' + } + + if (t === 'pdp') { + const desc = data.description && data.description.type + const label = (typeof desc === 'string' && desc) ? desc : (data.dominantType || data.pdp || '') + const pct = data.percentages + if (pct && typeof pct === 'object') { + const names = { Tiger: '老虎', Peacock: '孔雀', Owl: '猫头鹰', Koala: '考拉', Chameleon: '变色龙' } + const parts = [] + for (const [key, name] of Object.entries(names)) { + const v = pct[key] != null ? pct[key] : pct[key.toLowerCase()] + if (v != null) parts.push(name + ':' + toIntPercent(v) + '%') + } + if (parts.length) return label + ' ' + parts.join(' ') + } + return label || '' + } + + return '' +} + +/** + * 仅返回类型标签(无百分比),用于列表、个人中心等 + */ +function getTypeOnly(data, testType) { + if (!data || typeof data !== 'object') return '' + const t = (testType || '').toLowerCase() + if (t === 'mbti') return String(data.mbtiType ?? data.type ?? data.result ?? '') + if (t === 'disc') { + const desc = data.description?.type + if (typeof desc === 'string' && desc) return desc + if (data.dominantType) return String(data.dominantType) + '型' + return String(data.disc ?? '') + } + if (t === 'pdp') { + const desc = data.description?.type + if (typeof desc === 'string' && desc) return desc + if (data.dominantType) return String(data.dominantType) + return String(data.pdp ?? '') + } + return '' +} + +module.exports = { + toIntPercent, + formatTestSummary, + getTypeOnly +} diff --git a/miniprogram/utils/share.js b/miniprogram/utils/share.js new file mode 100644 index 0000000..0be4071 --- /dev/null +++ b/miniprogram/utils/share.js @@ -0,0 +1,60 @@ +/** + * 分享参数工具 + * - uid:当前登录用户 ID(始终携带,用于邀请统计) + * - eid:企业 ID(仅 appScope=enterprise 时携带) + * + * 小程序好友分享(onShareAppMessage):path 带完整查询串 + * 朋友圈分享(onShareTimeline):返回 query 字符串(不含 ?) + * 接收方(落地页 onLoad options)直接读 options.uid / options.eid + */ + +function getApp_() { + try { return getApp() } catch (e) { return null } +} + +/** + * 构建分享查询串,例如 "uid=1&eid=6" 或 "uid=1" + */ +function buildShareQuery() { + try { + const app = getApp_() + const userInfo = (app && app.globalData && app.globalData.userInfo) || wx.getStorageSync('userInfo') || {} + const scope = (app && app.globalData && app.globalData.appScope) || 'personal' + const uid = userInfo.id || '' + const parts = [] + if (uid) parts.push('uid=' + uid) + if (scope === 'enterprise') { + const eid = (app && app.globalData && app.globalData.enterpriseIdFromScene) + || userInfo.enterpriseId + || '' + if (eid) parts.push('eid=' + eid) + } + return parts.join('&') + } catch (e) { + return '' + } +} + +/** + * 返回带查询参数的完整落地页路径 + * @param {string} basePath 例如 '/pages/index/index' + * @param {boolean} forceEnterprise 强制使用企业版落地页(传 true 时 basePath 无效) + */ +function getSharePath(basePath) { + const query = buildShareQuery() + return query ? basePath + '?' + query : basePath +} + +/** + * 根据当前 scope 决定落地页路径 + * - 企业版 → /pages/enterprise/index?uid=X&eid=Y + * - 个人版 → basePath?uid=X (basePath 默认 /pages/index/index) + */ +function getSharePathByScope(personalBasePath) { + const app = getApp_() + const scope = (app && app.globalData && app.globalData.appScope) || 'personal' + const base = scope === 'enterprise' ? '/pages/enterprise/index' : (personalBasePath || '/pages/index/index') + return getSharePath(base) +} + +module.exports = { buildShareQuery, getSharePath, getSharePathByScope }