# Sub-Plan C: uni-app 小程序开发 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development > **前置依赖:** HBuilderX 或 Vue CLI uni-app 插件、后端 API 就绪 **目标:** 基于 uni-app 实现「能量盘+聊天」一体式小程序 **参考代码:** `miniprogram/` 目录下现有微信原生代码的 UI 设计和计算逻辑 --- ## 项目初始化 ``` client/ ├── pages/ │ ├── index/ # 首页(输入 + 能量盘 + 聊天) │ ├── records/ # 历史能量盘列表 │ ├── profile/ # 个人中心(信息/配额/佣金) │ └── subscribe/ # 付费页面 ├── components/ │ ├── triangle/ # Classic A Canvas 渲染 │ ├── zone-card/ # 区域解读卡片 │ └── chart-input/ # 生日输入表单 ├── api/ │ ├── request.js # 统一请求封装(JWT 注入) │ ├── auth.js │ ├── chart.js │ ├── chat.js │ └── payment.js ├── store/ │ └── index.js # Pinia 状态 ├── utils/ │ └── calculator.js # Classic A 计算引擎 ├── App.vue ├── main.js ├── pages.json ├── manifest.json └── uni.scss ``` --- ### Task 1: 项目骨架搭建 **Files:** - Create: `client/` 整个目录结构 - Create: `manifest.json`(配置 AppID 为 uni-app 格式) - Create: `pages.json`(页面路由 + TabBar) - [ ] **Step 1: 创建 uni-app 项目** Run: `npx degit dcloudio/uni-preset-vue#vite client` 或 HBuilderX 创建 - [ ] **Step 2: pages.json** ```json { "pages": [ {"path": "pages/index/index", "style": {"navigationBarTitleText": "数字能量盘"}}, {"path": "pages/records/records", "style": {"navigationBarTitleText": "能量盘记录"}}, {"path": "pages/profile/profile", "style": {"navigationBarTitleText": "我的"}}, {"path": "pages/subscribe/subscribe", "style": {"navigationBarTitleText": "开通能量师"}} ], "globalStyle": { "navigationBarTextStyle": "white", "navigationBarBackgroundColor": "#4A148C", "backgroundColor": "#F5F5F5" }, "tabBar": { "color": "#999", "selectedColor": "#4A148C", "list": [ {"pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tab/home.png"}, {"pagePath": "pages/records/records", "text": "记录", "iconPath": "static/tab/records.png"}, {"pagePath": "pages/profile/profile", "text": "我的", "iconPath": "static/tab/profile.png"} ] } } ``` - [ ] **Step 3: Commit** --- ### Task 2: Classic A 计算引擎(迁移适配) **Files:** - Create: `utils/calculator.js` - [ ] **Step 1: 创建 calculator.js** ```javascript export function reduceToDigit(n) { if (n === 11 || n === 22 || n === 33) return n while (n > 9) { let sum = 0 while (n > 0) { sum += n % 10; n = Math.floor(n / 10) } if (sum === 11 || sum === 22 || sum === 33) return sum n = sum } return n } export function calculateTriangle(birthYear, birthMonth, birthDay) { const y = String(birthYear).padStart(4, '0') const m = String(birthMonth).padStart(2, '0') const d = String(birthDay).padStart(2, '0') const A = parseInt(y[0]), B = parseInt(y[1]) const C = reduceToDigit(parseInt(y[2]) + parseInt(y[3])) const D = reduceToDigit(parseInt(m[0]) + parseInt(m[1])) const E = reduceToDigit(parseInt(d[0]) + parseInt(d[1])) // Row 2 const F = reduceToDigit(A + B), G = reduceToDigit(B + C) const N = reduceToDigit(C + D), H = reduceToDigit(D + E) // Row 3 const J = reduceToDigit(F + G), M = reduceToDigit(G + N), L = reduceToDigit(N + H) // Row 4 const I = reduceToDigit(J + M), K = reduceToDigit(M + L) // Top const O = reduceToDigit(I + K) const positions = { A, B, C, D, E, F, G, N, H, J, M, L, I, K, O } const zones = { mainCharacter: { name: '主性格', positions: ['O'], values: [O] }, fatherSource: { name: '父源区', positions: ['I', 'J'], values: [I, J] }, motherSource: { name: '母源区', positions: ['K', 'L'], values: [K, L] }, leftZone: { name: '左区(0-20岁)', positions: ['A', 'F', 'I', 'O'], values: [A, F, I, O] }, middleZone: { name: '中区(20-40岁)', positions: ['B', 'G', 'J', 'M', 'N', 'O'], values: [B, G, J, M, N, O] }, rightZone: { name: '右区(40-60岁)', positions: ['C', 'D', 'E', 'H', 'K', 'L', 'O'], values: [C, D, E, H, K, L, O] }, } return { positions, zones, mainCharacter: O, isMasterNumber: [11, 22, 33].includes(O) } } export const NUMBER_MEANINGS = { 1: { title: '开创者', trait: '独立、创新、领导力', color: '#E53935' }, 2: { title: '合作者', trait: '细腻、敏感、善于沟通', color: '#FB8C00' }, 3: { title: '表达者', trait: '创意、社交、乐观向上', color: '#FDD835' }, 4: { title: '建设者', trait: '务实、稳定、规则意识', color: '#43A047' }, 5: { title: '自由者', trait: '冒险、变化、适应力强', color: '#1E88E5' }, 6: { title: '守护者', trait: '责任、关爱、追求完美', color: '#8E24AA' }, 7: { title: '探索者', trait: '深度思考、求知、内省', color: '#00ACC1' }, 8: { title: '掌控者', trait: '权威、商业头脑、决策力', color: '#F4511E' }, 9: { title: '智慧者', trait: '博爱、理想主义、包容', color: '#3949AB' }, 11: { title: '启迪者', trait: '直觉力强、灵感丰富', color: '#FFD700' }, 22: { title: '建造大师', trait: '将理想变为现实', color: '#FFD700' }, 33: { title: '大爱者', trait: '无私奉献、疗愈他人', color: '#FFD700' }, } export const POSITION_NAMES = { A: '年位一', B: '年位二', C: '年位三', D: '月位', E: '日位', F: '父源一', G: '中位一', N: '中位二', H: '母源一', J: '父源二', M: '高位', L: '母源二', I: '父源三', K: '母源三', O: '主性格', } ``` - [ ] **Step 2: 验证** ```javascript const r = calculateTriangle(1990, 1, 1) console.log(r.mainCharacter) // 应输出 7 console.log(r.positions) // 15格数据 ``` - [ ] **Step 3: Commit** --- ### Task 3: API 层封装 **Files:** - Create: `api/request.js` - Create: `api/auth.js` - Create: `api/chart.js` - Create: `api/chat.js` - Create: `api/payment.js` - [ ] **Step 1: request.js** ```javascript const BASE_URL = 'https://your-api.com' function request(path, data) { const token = uni.getStorageSync('token') return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + path, method: 'POST', data, header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' }, success: (res) => { if (res.data.code === 0) resolve(res.data.data) else if (res.data.code === 1001) { uni.removeStorageSync('token') uni.navigateTo({ url: '/pages/profile/profile' }) reject(res.data) } else reject(res.data) }, fail: reject }) }) } export default request ``` - [ ] **Step 2: API 模块** ```javascript // api/auth.js import request from './request' export const login = (code, referrerCode) => request('/api/auth/login', { code, referrerCode }) // api/chart.js export const createChart = (data) => request('/api/charts/create', data) export const chartList = (page, size) => request('/api/charts/list', { page, size }) export const chartDetail = (id) => request('/api/charts/detail', { id }) export const deleteChart = (id) => request('/api/charts/delete', { id }) // api/chat.js export const sendMessage = (chartId, message) => request('/api/chat/send', { chartId, message }) export const chatHistory = (chartId) => request('/api/chat/history', { chartId }) // api/payment.js export const unifiedOrder = (productType) => request('/api/pay/unified-order', { productType }) ``` - [ ] **Step 3: Commit** --- ### Task 4: 首页 — 输入组件 **Files:** - Create: `components/chart-input/index.vue` - Modify: `pages/index/index.vue` - [ ] **Step 1: chart-input/index.vue** ```vue ``` - [ ] **Step 2: Commit** --- ### Task 5: 首页 — Class A Canvas 三角组件 **Files:** - Create: `components/triangle/index.vue` - [ ] **Step 1: triangle/index.vue** ```vue ``` - [ ] **Step 2: Commit** --- ### Task 6: 首页 — 聊天窗口 **Files:** - Modify: `pages/index/index.vue`(集成输入+三角+聊天一体化) - Create: `store/index.js` - [ ] **Step 1: 首页一体化** ```vue ``` - [ ] **Step 2: Commit** --- ### Task 7: 个人中心 + 历史记录 **Files:** - Create: `pages/profile/profile.vue` - Create: `pages/records/records.vue`(简化版,在 profile 中已含列表) - [ ] **Step 1: profile/profile.vue(参考现有 miniprogram/pages/profile/ 的 UI)** ```vue ``` - [ ] **Step 2: 历史记录列表(profile 中内联),records 页可作为单独的 Tab 页显示全部** - [ ] **Step 3: Commit** --- ### Task 8: 付费订阅页 **Files:** - Create: `pages/subscribe/subscribe.vue` - [ ] **Step 1: subscribe.vue(参考现有 miniprogram/pages/subscribe/ UI)** ```vue ``` - [ ] **Step 2: Commit**