docs(readme): 重构项目文档结构
- 优化 CLAUDE.md 文档,添加设计原则说明 - 新增 CHANGELOG.md 版本更新日志 - 新增最佳实践指南 - 新增调试指南 - 新增 API 集成、导航、页面开发指南 - 新增参考文档目录 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Showing
10 changed files
with
2055 additions
and
0 deletions
CHANGELOG.md
0 → 100644
This diff is collapsed. Click to expand it.
docs/best-practices.md
0 → 100644
| 1 | +# 最佳实践 | ||
| 2 | + | ||
| 3 | +本文档列出项目开发中的最佳实践。 | ||
| 4 | + | ||
| 5 | +## 组件开发 | ||
| 6 | + | ||
| 7 | +### ✅ 推荐做法 | ||
| 8 | + | ||
| 9 | +```vue | ||
| 10 | +<script setup> | ||
| 11 | +// 1. 使用 <script setup> 语法 | ||
| 12 | +import { ref, computed } from 'vue' | ||
| 13 | + | ||
| 14 | +// 2. Props 应该有类型定义 | ||
| 15 | +const props = defineProps({ | ||
| 16 | + title: { | ||
| 17 | + type: String, | ||
| 18 | + required: true | ||
| 19 | + }, | ||
| 20 | + count: { | ||
| 21 | + type: Number, | ||
| 22 | + default: 0 | ||
| 23 | + } | ||
| 24 | +}) | ||
| 25 | + | ||
| 26 | +// 3. 使用 emit 进行子到父通信 | ||
| 27 | +const emit = defineEmits(['update', 'delete']) | ||
| 28 | + | ||
| 29 | +// 4. 使用 Composables 处理可复用逻辑 | ||
| 30 | +const { data, loading } = use fetchData() | ||
| 31 | +</script> | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +### ❌ 避免做法 | ||
| 35 | + | ||
| 36 | +```vue | ||
| 37 | +<script> | ||
| 38 | +// ❌ 不要使用 Options API | ||
| 39 | +export default { | ||
| 40 | + data() { | ||
| 41 | + return { } | ||
| 42 | + } | ||
| 43 | +} | ||
| 44 | +</script> | ||
| 45 | + | ||
| 46 | +<script setup> | ||
| 47 | +// ❌ 不要省略 Props 类型 | ||
| 48 | +const props = defineProps(['title']) | ||
| 49 | + | ||
| 50 | +// ❌ 不要直接修改 props | ||
| 51 | +props.title = 'new title' | ||
| 52 | +</script> | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +--- | ||
| 56 | + | ||
| 57 | +## 样式开发 | ||
| 58 | + | ||
| 59 | +### TailwindCSS vs Less | ||
| 60 | + | ||
| 61 | +| 场景 | 使用 | 比例 | | ||
| 62 | +|------|------|------| | ||
| 63 | +| 布局(flex、grid、absolute) | TailwindCSS | 80% | | ||
| 64 | +| 间距(padding、margin、gap) | TailwindCSS | | | ||
| 65 | +| 排版(font-size、text-align) | TailwindCSS | | | ||
| 66 | +| 颜色(bg-*、text-*、border-*) | TailwindCSS | | | ||
| 67 | +| 响应式设计(sm:、md:、lg:) | TailwindCSS | | | ||
| 68 | +| 组件特定样式(需要 scoped) | Less | 20% | | ||
| 69 | +| 深度选择器(`:deep()`) | Less | | | ||
| 70 | +| 动画和过渡 | Less | | | ||
| 71 | +| 伪元素(`::before`、`::after`) | Less | | | ||
| 72 | + | ||
| 73 | +### ✅ 优先使用 TailwindCSS | ||
| 74 | + | ||
| 75 | +```vue | ||
| 76 | +<template> | ||
| 77 | + <!-- ✅ 使用 TailwindCSS --> | ||
| 78 | + <view class="flex items-center gap-4 p-4 bg-white rounded-lg"> | ||
| 79 | + <text class="text-lg font-semibold">标题</text> | ||
| 80 | + </view> | ||
| 81 | +</template> | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +### ❌ 避免过度使用 Less | ||
| 85 | + | ||
| 86 | +```vue | ||
| 87 | +<style lang="less" scoped> | ||
| 88 | +// ❌ 能用 TailwindCSS 的就不要用 Less | ||
| 89 | +.custom-container { | ||
| 90 | + display: flex; | ||
| 91 | + align-items: center; | ||
| 92 | + padding: 16px; | ||
| 93 | +} | ||
| 94 | +</style> | ||
| 95 | +``` | ||
| 96 | + | ||
| 97 | +--- | ||
| 98 | + | ||
| 99 | +## API 集成 | ||
| 100 | + | ||
| 101 | +### ✅ 推荐做法 | ||
| 102 | + | ||
| 103 | +```javascript | ||
| 104 | +// 1. 始终检查 res.code === 1 | ||
| 105 | +const res = await fn(yourAPI(params)) | ||
| 106 | +if (res.code === 1) { | ||
| 107 | + // 成功处理 | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +// 2. 使用 try/catch 进行错误处理 | ||
| 111 | +try { | ||
| 112 | + const res = await fn(yourAPI(params)) | ||
| 113 | +} catch (err) { | ||
| 114 | + console.error('API 错误:', err) | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +// 3. 请求期间显示加载状态 | ||
| 118 | +loading.value = true | ||
| 119 | +await fn(yourAPI(params)) | ||
| 120 | +loading.value = false | ||
| 121 | + | ||
| 122 | +// 4. 优雅地处理网络错误 | ||
| 123 | +try { | ||
| 124 | + await fn(yourAPI(params)) | ||
| 125 | +} catch (err) { | ||
| 126 | + if (is_network_error(err)) { | ||
| 127 | + Taro.showToast({ title: '网络错误,请重试', icon: 'none' }) | ||
| 128 | + } | ||
| 129 | +} | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### ❌ 避免做法 | ||
| 133 | + | ||
| 134 | +```javascript | ||
| 135 | +// ❌ 不要只检查 res.code | ||
| 136 | +if (res.code) { } | ||
| 137 | + | ||
| 138 | +// ❌ 不要忽略错误处理 | ||
| 139 | +await fn(yourAPI(params)) | ||
| 140 | + | ||
| 141 | +// ❌ 不要不显示加载状态 | ||
| 142 | +await fn(yourAPI(params)) // 用户不知道发生了什么 | ||
| 143 | +``` | ||
| 144 | + | ||
| 145 | +--- | ||
| 146 | + | ||
| 147 | +## 性能优化 | ||
| 148 | + | ||
| 149 | +### ✅ 推荐做法 | ||
| 150 | + | ||
| 151 | +```javascript | ||
| 152 | +// 1. 使用页面懒加载(分包) | ||
| 153 | +// 在 app.config.js 中配置分包 | ||
| 154 | +subPackages: [ | ||
| 155 | + { | ||
| 156 | + root: 'pages/business', | ||
| 157 | + pages: ['/*'] | ||
| 158 | + } | ||
| 159 | +] | ||
| 160 | + | ||
| 161 | +// 2. 使用 CDN 参数优化图片 | ||
| 162 | +const imageUrl = 'https://cdn.example.com/image.jpg?w=750&q=80' | ||
| 163 | + | ||
| 164 | +// 3. 避免无分页的大数据集 | ||
| 165 | +const res = await fn(getListAPI({ page: 1, limit: 20 })) | ||
| 166 | + | ||
| 167 | +// 4. 在 onUnmounted 中清理 | ||
| 168 | +onUnmounted(() => { | ||
| 169 | + timer && clearInterval(timer) | ||
| 170 | +}) | ||
| 171 | + | ||
| 172 | +// 5. 使用 shallowRef + markRaw 处理组件对象 | ||
| 173 | +import { shallowRef, markRaw } from 'vue' | ||
| 174 | + | ||
| 175 | +const menuItems = shallowRef([ | ||
| 176 | + { icon: markRaw(IconFont), name: 'heart' } | ||
| 177 | +]) | ||
| 178 | +``` | ||
| 179 | + | ||
| 180 | +### ❌ 避免做法 | ||
| 181 | + | ||
| 182 | +```javascript | ||
| 183 | +// ❌ 不要一次性加载所有数据 | ||
| 184 | +const allData = await fn(getAllDataAPI()) | ||
| 185 | + | ||
| 186 | +// ❌ 不要忘记清理定时器 | ||
| 187 | +const timer = setInterval(() => { }, 1000) | ||
| 188 | +// 没有清理 | ||
| 189 | + | ||
| 190 | +// ❌ 不要深度代理组件对象 | ||
| 191 | +const menuItems = ref([ | ||
| 192 | + { icon: IconFont, name: 'heart' } // Vue 会深度代理 | ||
| 193 | +]) | ||
| 194 | +``` | ||
| 195 | + | ||
| 196 | +--- | ||
| 197 | + | ||
| 198 | +## 代码质量 | ||
| 199 | + | ||
| 200 | +### ✅ 推荐做法 | ||
| 201 | + | ||
| 202 | +```javascript | ||
| 203 | +// 1. 遵循 Vue 3 Composition API 模式 | ||
| 204 | +<script setup> | ||
| 205 | +// ... | ||
| 206 | +</script> | ||
| 207 | + | ||
| 208 | +// 2. 使用描述性变量名 | ||
| 209 | +const isLoadingUserFavorites = ref(false) | ||
| 210 | + | ||
| 211 | +// 3. 保持函数聚焦且简短(< 50 行) | ||
| 212 | +const fetchUserData = async () => { | ||
| 213 | + // 单一职责 | ||
| 214 | +} | ||
| 215 | + | ||
| 216 | +// 4. 所有函数必须有 JSDoc 注释 | ||
| 217 | +/** | ||
| 218 | + * 获取用户数据 | ||
| 219 | + * @param {number} userId - 用户 ID | ||
| 220 | + * @returns {Promise<User>} 用户数据 | ||
| 221 | + */ | ||
| 222 | +async function getUserData(userId) { } | ||
| 223 | + | ||
| 224 | +// 5. 提交前运行 pnpm lint | ||
| 225 | +``` | ||
| 226 | + | ||
| 227 | +### ❌ 避免做法 | ||
| 228 | + | ||
| 229 | +```javascript | ||
| 230 | +// ❌ 不要使用无意义的变量名 | ||
| 231 | +const a = ref(false) | ||
| 232 | +const temp = ref(null) | ||
| 233 | + | ||
| 234 | +// ❌ 不要写超长函数(> 50 行) | ||
| 235 | +const doEverything = async () => { | ||
| 236 | + // 100+ 行代码 | ||
| 237 | +} | ||
| 238 | + | ||
| 239 | +// ❌ 不要省略函数注释 | ||
| 240 | +function process(data) { } | ||
| 241 | +``` | ||
| 242 | + | ||
| 243 | +--- | ||
| 244 | + | ||
| 245 | +## 代码复用 | ||
| 246 | + | ||
| 247 | +### 第 3 次出现原则 | ||
| 248 | + | ||
| 249 | +当相同代码模式出现 3 次时,**必须**抽取为 Composable 或组件。 | ||
| 250 | + | ||
| 251 | +### ✅ 推荐做法 | ||
| 252 | + | ||
| 253 | +```javascript | ||
| 254 | +// 抽取为 Composable | ||
| 255 | +// src/composables/useUserData.js | ||
| 256 | +export function useUserData() { | ||
| 257 | + const user = ref(null) | ||
| 258 | + const loading = ref(false) | ||
| 259 | + | ||
| 260 | + const fetchUser = async () => { | ||
| 261 | + loading.value = true | ||
| 262 | + // ... | ||
| 263 | + } | ||
| 264 | + | ||
| 265 | + return { user, loading, fetchUser } | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +// 在组件中使用 | ||
| 269 | +const { user, loading, fetchUser } = useUserData() | ||
| 270 | +``` | ||
| 271 | + | ||
| 272 | +### 组件自包含原则 | ||
| 273 | + | ||
| 274 | +对于重复的UI结构,抽取为可复用组件: | ||
| 275 | + | ||
| 276 | +```vue | ||
| 277 | +<!-- MaterialCard.vue - 自包含业务逻辑 --> | ||
| 278 | +<script setup> | ||
| 279 | +const props = defineProps(['item']) | ||
| 280 | +const emit = defineEmits(['view', 'collect']) | ||
| 281 | + | ||
| 282 | +// 组件内部处理业务逻辑 | ||
| 283 | +const handleView = () => { | ||
| 284 | + emit('view', props.item.id) | ||
| 285 | +} | ||
| 286 | +</script> | ||
| 287 | + | ||
| 288 | +<!-- 父组件只需要传递数据 --> | ||
| 289 | +<MaterialCard :item="material" @view="handleView" /> | ||
| 290 | +``` | ||
| 291 | + | ||
| 292 | +--- | ||
| 293 | + | ||
| 294 | +## 安全性 | ||
| 295 | + | ||
| 296 | +### ✅ 推荐做法 | ||
| 297 | + | ||
| 298 | +```javascript | ||
| 299 | +// 1. 用户输入验证 | ||
| 300 | +const validateInput = (input) => { | ||
| 301 | + if (!input || input.length > 100) { | ||
| 302 | + return false | ||
| 303 | + } | ||
| 304 | + return true | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +// 2. XSS 防护(使用 v-html 时净化) | ||
| 308 | +import DOMPurify from 'dompurify' | ||
| 309 | +const sanitizedHtml = DOMPurify.sanitize(rawHtml) | ||
| 310 | + | ||
| 311 | +// 3. 敏感数据不存储在 localStorage | ||
| 312 | +// ❌ 不要存储 | ||
| 313 | +localStorage.setItem('password', password) | ||
| 314 | + | ||
| 315 | +// ✅ 使用 Pinia(内存存储) | ||
| 316 | +const authStore = useAuthStore() | ||
| 317 | +authStore.setToken(token) | ||
| 318 | +``` | ||
| 319 | + | ||
| 320 | +--- | ||
| 321 | + | ||
| 322 | +## 相关文档 | ||
| 323 | + | ||
| 324 | +- **[经验教训总结](lessons-learned.md)** - 常见陷阱和解决方案 | ||
| 325 | +- **[调试指南](debugging.md)** - 调试技巧 |
docs/debugging.md
0 → 100644
| 1 | +# 调试指南 | ||
| 2 | + | ||
| 3 | +本文档介绍项目调试的常用技巧和工具。 | ||
| 4 | + | ||
| 5 | +## 检查环境配置 | ||
| 6 | + | ||
| 7 | +### 验证 BASE_URL | ||
| 8 | + | ||
| 9 | +```bash | ||
| 10 | +# 查看 src/utils/config.js 中的 BASE_URL 配置 | ||
| 11 | +cat src/utils/config.js | grep BASE_URL | ||
| 12 | +``` | ||
| 13 | + | ||
| 14 | +确保: | ||
| 15 | +- 开发环境指向测试服务器 | ||
| 16 | +- 生产环境指向正式服务器 | ||
| 17 | + | ||
| 18 | +### 检查业务参数 | ||
| 19 | + | ||
| 20 | +```javascript | ||
| 21 | +// src/utils/config.js | ||
| 22 | +REQUEST_DEFAULT_PARAMS: { | ||
| 23 | + f: 'your_module', // 业务模块标识符 | ||
| 24 | + client_name: 'your_app' // 应用名称 | ||
| 25 | +} | ||
| 26 | +``` | ||
| 27 | + | ||
| 28 | +--- | ||
| 29 | + | ||
| 30 | +## 验证身份认证 | ||
| 31 | + | ||
| 32 | +### 检查 sessionid | ||
| 33 | + | ||
| 34 | +```javascript | ||
| 35 | +// 在浏览器控制台或微信开发者工具中执行 | ||
| 36 | +console.log(localStorage.sessionid) | ||
| 37 | +``` | ||
| 38 | + | ||
| 39 | +### 启用详细日志 | ||
| 40 | + | ||
| 41 | +在 `src/utils/request.js` 拦截器中启用详细日志: | ||
| 42 | + | ||
| 43 | +```javascript | ||
| 44 | +// 请求拦截器 | ||
| 45 | +config.headers = { | ||
| 46 | + 'cookie': `sessionid=${localStorage.sessionid}`, | ||
| 47 | + // 添加调试日志 | ||
| 48 | + 'X-Debug': 'true' | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +console.log('[Request]', config.url, config.data) | ||
| 52 | +``` | ||
| 53 | + | ||
| 54 | +### 测试 401 刷新流程 | ||
| 55 | + | ||
| 56 | +```javascript | ||
| 57 | +// 1. 清除 sessionid 模拟过期 | ||
| 58 | +localStorage.removeItem('sessionid') | ||
| 59 | + | ||
| 60 | +// 2. 发送请求触发 401 | ||
| 61 | +await fn(someAPI()) | ||
| 62 | + | ||
| 63 | +// 3. 观察是否自动刷新并重试 | ||
| 64 | +``` | ||
| 65 | + | ||
| 66 | +--- | ||
| 67 | + | ||
| 68 | +## 网络问题调试 | ||
| 69 | + | ||
| 70 | +### 检查网络状态 | ||
| 71 | + | ||
| 72 | +```javascript | ||
| 73 | +import Taro from '@tarojs/taro' | ||
| 74 | + | ||
| 75 | +// 获取网络类型 | ||
| 76 | +Taro.getNetworkType({ | ||
| 77 | + success: (res) => { | ||
| 78 | + console.log('网络类型:', res.networkType) | ||
| 79 | + // wifi, 4g, 5g, none | ||
| 80 | + } | ||
| 81 | +}) | ||
| 82 | + | ||
| 83 | +// 监听网络变化 | ||
| 84 | +Taro.onNetworkStatusChange((result) => { | ||
| 85 | + console.log('网络状态变化:', result) | ||
| 86 | +}) | ||
| 87 | +``` | ||
| 88 | + | ||
| 89 | +### 检查弱网络场景 | ||
| 90 | + | ||
| 91 | +项目支持弱网络降级,通过以下方式检测: | ||
| 92 | + | ||
| 93 | +```javascript | ||
| 94 | +// src/utils/request.js | ||
| 95 | +import { is_timeout_error, is_network_error } from '@/utils/network' | ||
| 96 | + | ||
| 97 | +try { | ||
| 98 | + const res = await fn(yourAPI()) | ||
| 99 | +} catch (err) { | ||
| 100 | + if (is_timeout_error(err)) { | ||
| 101 | + console.log('请求超时') | ||
| 102 | + } else if (is_network_error(err)) { | ||
| 103 | + console.log('网络错误') | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | +``` | ||
| 107 | + | ||
| 108 | +--- | ||
| 109 | + | ||
| 110 | +## 样式问题调试 | ||
| 111 | + | ||
| 112 | +### 确认设计宽度 | ||
| 113 | + | ||
| 114 | +项目中使用双设计宽度系统: | ||
| 115 | +- **NutUI 组件**:375px | ||
| 116 | +- **其他组件**:750px | ||
| 117 | + | ||
| 118 | +```javascript | ||
| 119 | +// config/index.js | ||
| 120 | +designWidth: 750, // 默认 | ||
| 121 | + | ||
| 122 | +// NutUI 特殊配置 | ||
| 123 | +nutui: { | ||
| 124 | + designWidth: 375 | ||
| 125 | +} | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +### 检查样式应用 | ||
| 129 | + | ||
| 130 | +```vue | ||
| 131 | +<template> | ||
| 132 | + <!-- 添加调试类名 --> | ||
| 133 | + <view class="debug-component"> | ||
| 134 | + 内容 | ||
| 135 | + </view> | ||
| 136 | +</template> | ||
| 137 | + | ||
| 138 | +<style lang="less" scoped> | ||
| 139 | +.debug-component { | ||
| 140 | + /* 调试边框 */ | ||
| 141 | + border: 1px solid red; | ||
| 142 | +} | ||
| 143 | +</style> | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +### 验证 TailwindCSS 类 | ||
| 147 | + | ||
| 148 | +```bash | ||
| 149 | +# 检查 TailwindCSS 配置 | ||
| 150 | +cat tailwind.config.js | ||
| 151 | + | ||
| 152 | +# 检查类是否生效 | ||
| 153 | +# 在微信开发者工具中查看元素的 computed styles | ||
| 154 | +``` | ||
| 155 | + | ||
| 156 | +--- | ||
| 157 | + | ||
| 158 | +## 导航问题调试 | ||
| 159 | + | ||
| 160 | +### 检查路由注册 | ||
| 161 | + | ||
| 162 | +```javascript | ||
| 163 | +// src/app.config.js | ||
| 164 | +export default { | ||
| 165 | + pages: [ | ||
| 166 | + 'pages/your-page/index', // 确认已注册 | ||
| 167 | + ] | ||
| 168 | +} | ||
| 169 | +``` | ||
| 170 | + | ||
| 171 | +### 验证页面目录结构 | ||
| 172 | + | ||
| 173 | +```bash | ||
| 174 | +# 确认页面文件存在 | ||
| 175 | +ls -la src/pages/your-page/ | ||
| 176 | + | ||
| 177 | +# 应该包含: | ||
| 178 | +# - index.vue | ||
| 179 | +# - index.config.js | ||
| 180 | +``` | ||
| 181 | + | ||
| 182 | +### 调试导航参数 | ||
| 183 | + | ||
| 184 | +```javascript | ||
| 185 | +// 发送页面 | ||
| 186 | +go('/pages/detail/index', { id: 123 }) | ||
| 187 | + | ||
| 188 | +// 接收页面 | ||
| 189 | +useLoad((options) => { | ||
| 190 | + console.log('接收到的参数:', options) | ||
| 191 | + console.log('ID:', options.id) // 应该输出 123 | ||
| 192 | +}) | ||
| 193 | +``` | ||
| 194 | + | ||
| 195 | +--- | ||
| 196 | + | ||
| 197 | +## API 问题调试 | ||
| 198 | + | ||
| 199 | +### 查看 API 请求 | ||
| 200 | + | ||
| 201 | +```javascript | ||
| 202 | +// src/utils/request.js | ||
| 203 | +// 在拦截器中添加日志 | ||
| 204 | +interceptors.request.use((config) => { | ||
| 205 | + console.log('[API Request]', { | ||
| 206 | + url: config.url, | ||
| 207 | + method: config.method, | ||
| 208 | + data: config.data, | ||
| 209 | + headers: config.headers | ||
| 210 | + }) | ||
| 211 | + return config | ||
| 212 | +}) | ||
| 213 | + | ||
| 214 | +interceptors.response.use((response) => { | ||
| 215 | + console.log('[API Response]', { | ||
| 216 | + status: response.status, | ||
| 217 | + data: response.data | ||
| 218 | + }) | ||
| 219 | + return response | ||
| 220 | +}) | ||
| 221 | +``` | ||
| 222 | + | ||
| 223 | +### 测试 API 调用 | ||
| 224 | + | ||
| 225 | +```javascript | ||
| 226 | +// 独立测试 API | ||
| 227 | +import { yourAPI } from '@/api' | ||
| 228 | +import { fn } from '@/api/fn' | ||
| 229 | + | ||
| 230 | +async function testAPI() { | ||
| 231 | + try { | ||
| 232 | + const url = yourAPI({ id: 123 }) | ||
| 233 | + console.log('API URL:', url) | ||
| 234 | + | ||
| 235 | + const res = await fn(url) | ||
| 236 | + console.log('API Response:', res) | ||
| 237 | + | ||
| 238 | + if (res.code === 1) { | ||
| 239 | + console.log('API 调用成功') | ||
| 240 | + } else { | ||
| 241 | + console.log('API 业务错误:', res.msg) | ||
| 242 | + } | ||
| 243 | + } catch (err) { | ||
| 244 | + console.error('API 网络错误:', err) | ||
| 245 | + } | ||
| 246 | +} | ||
| 247 | + | ||
| 248 | +testAPI() | ||
| 249 | +``` | ||
| 250 | + | ||
| 251 | +--- | ||
| 252 | + | ||
| 253 | +## 常见问题 | ||
| 254 | + | ||
| 255 | +### Q: NutUI 组件样式不生效 | ||
| 256 | + | ||
| 257 | +A: 检查设计宽度配置,NutUI 使用 375px 设计宽度: | ||
| 258 | + | ||
| 259 | +```javascript | ||
| 260 | +// config/index.js | ||
| 261 | +nutui: { | ||
| 262 | + designWidth: 375 // 确认配置正确 | ||
| 263 | +} | ||
| 264 | +``` | ||
| 265 | + | ||
| 266 | +### Q: 页面参数接收不到 | ||
| 267 | + | ||
| 268 | +A: 检查以下几点: | ||
| 269 | + | ||
| 270 | +1. 确认使用 `useLoad` 接收参数 | ||
| 271 | +2. 确认参数名称正确 | ||
| 272 | +3. 确认参数类型(数字 vs 字符串) | ||
| 273 | + | ||
| 274 | +```javascript | ||
| 275 | +useLoad((options) => { | ||
| 276 | + console.log(options) // 先打印看看有什么 | ||
| 277 | +}) | ||
| 278 | +``` | ||
| 279 | + | ||
| 280 | +### Q: API 请求不发送 | ||
| 281 | + | ||
| 282 | +A: 检查以下几点: | ||
| 283 | + | ||
| 284 | +1. 确认 BASE_URL 配置正确 | ||
| 285 | +2. 确认网络权限 | ||
| 286 | +3. 查看控制台是否有错误 | ||
| 287 | +4. 检查 request.js 拦截器 | ||
| 288 | + | ||
| 289 | +### Q: 组件不更新 | ||
| 290 | + | ||
| 291 | +A: 可能的原因: | ||
| 292 | + | ||
| 293 | +1. 响应式数据未正确声明 | ||
| 294 | +2. 使用了深度嵌套的对象 | ||
| 295 | +3. Key 值未正确设置 | ||
| 296 | + | ||
| 297 | +```javascript | ||
| 298 | +// 确保使用 ref 或 reactive | ||
| 299 | +const data = ref(null) // ✅ | ||
| 300 | +const data = null // ❌ | ||
| 301 | + | ||
| 302 | +// 列表使用 key | ||
| 303 | +<view v-for="item in list" :key="item.id"> // ✅ | ||
| 304 | +<view v-for="item in list"> // ❌ | ||
| 305 | +``` | ||
| 306 | + | ||
| 307 | +--- | ||
| 308 | + | ||
| 309 | +## 调试工具 | ||
| 310 | + | ||
| 311 | +### 微信开发者工具 | ||
| 312 | + | ||
| 313 | +- **Console** - 查看日志输出 | ||
| 314 | +- **Network** - 查看网络请求 | ||
| 315 | +- **AppData** - 查看 AppData 数据 | ||
| 316 | +- **Storage** - 查看 localStorage | ||
| 317 | +- **Wxml** - 查看 DOM 结构 | ||
| 318 | + | ||
| 319 | +### Vue DevTools | ||
| 320 | + | ||
| 321 | +Taro 支持 Vue DevTools,可以: | ||
| 322 | +- 查看组件树 | ||
| 323 | +- 查看 Vuex/Pinia 状态 | ||
| 324 | +- 查看事件监听器 | ||
| 325 | + | ||
| 326 | +--- | ||
| 327 | + | ||
| 328 | +## 相关文档 | ||
| 329 | + | ||
| 330 | +- **[API 集成指南](guides/api-integration.md)** - API 调用详解 | ||
| 331 | +- **[最佳实践](best-practices.md)** - 代码质量建议 |
docs/guides/api-integration.md
0 → 100644
| 1 | +# API 集成指南 | ||
| 2 | + | ||
| 3 | +本文档介绍如何在项目中添加和调用 API。 | ||
| 4 | + | ||
| 5 | +## API 定义模式 | ||
| 6 | + | ||
| 7 | +### 步骤 1:在 src/api/index.js 中定义 API | ||
| 8 | + | ||
| 9 | +```javascript | ||
| 10 | +export const getProductListAPI = (params) => { | ||
| 11 | + return buildApiUrl('product_list', params) | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +export const getProductDetailAPI = (params) => { | ||
| 15 | + return buildApiUrl('product_detail', params) | ||
| 16 | +} | ||
| 17 | +``` | ||
| 18 | + | ||
| 19 | +### 步骤 2:在页面中使用 | ||
| 20 | + | ||
| 21 | +```javascript | ||
| 22 | +import { getProductListAPI } from '@/api' | ||
| 23 | +import { fn } from '@/api/fn' | ||
| 24 | + | ||
| 25 | +const fetchProducts = async () => { | ||
| 26 | + try { | ||
| 27 | + const res = await fn(getProductListAPI({ page: 1 })) | ||
| 28 | + if (res.code === 1) { | ||
| 29 | + products.value = res.data | ||
| 30 | + } | ||
| 31 | + } catch (err) { | ||
| 32 | + console.error('获取产品失败:', err) | ||
| 33 | + } | ||
| 34 | +} | ||
| 35 | +``` | ||
| 36 | + | ||
| 37 | +## 请求包装器(fn.js) | ||
| 38 | + | ||
| 39 | +所有 API 调用都应通过 `src/api/fn.js` 的包装器: | ||
| 40 | + | ||
| 41 | +- ✅ 处理常见错误场景 | ||
| 42 | +- ✅ 统一错误提示 | ||
| 43 | +- ✅ **始终检查 `res.code === 1` 判断成功** | ||
| 44 | + | ||
| 45 | +## 完整示例 | ||
| 46 | + | ||
| 47 | +### 带加载状态的 API 调用 | ||
| 48 | + | ||
| 49 | +```javascript | ||
| 50 | +import { ref } from 'vue' | ||
| 51 | +import { getProductListAPI } from '@/api' | ||
| 52 | +import { fn } from '@/api/fn' | ||
| 53 | + | ||
| 54 | +const products = ref([]) | ||
| 55 | +const loading = ref(false) | ||
| 56 | +const error = ref(null) | ||
| 57 | + | ||
| 58 | +const fetchProducts = async (page = 1) => { | ||
| 59 | + loading.value = true | ||
| 60 | + error.value = null | ||
| 61 | + | ||
| 62 | + try { | ||
| 63 | + const res = await fn(getProductListAPI({ page, limit: 20 })) | ||
| 64 | + | ||
| 65 | + if (res.code === 1) { | ||
| 66 | + products.value = res.data.list | ||
| 67 | + } else { | ||
| 68 | + error.value = res.msg || '获取失败' | ||
| 69 | + } | ||
| 70 | + } catch (err) { | ||
| 71 | + console.error('API 错误:', err) | ||
| 72 | + error.value = '网络错误,请重试' | ||
| 73 | + } finally { | ||
| 74 | + loading.value = false | ||
| 75 | + } | ||
| 76 | +} | ||
| 77 | +``` | ||
| 78 | + | ||
| 79 | +### 带分页的 API 调用 | ||
| 80 | + | ||
| 81 | +```javascript | ||
| 82 | +const page = ref(0) | ||
| 83 | +const hasMore = ref(true) | ||
| 84 | +const loading = ref(false) | ||
| 85 | + | ||
| 86 | +const loadMore = async () => { | ||
| 87 | + if (loading.value || !hasMore.value) return | ||
| 88 | + | ||
| 89 | + loading.value = true | ||
| 90 | + try { | ||
| 91 | + const res = await fn(getProductListAPI({ page: page.value, limit: 20 })) | ||
| 92 | + | ||
| 93 | + if (res.code === 1) { | ||
| 94 | + products.value.push(...res.data.list) | ||
| 95 | + page.value++ | ||
| 96 | + hasMore.value = res.data.list.length >= 20 | ||
| 97 | + } | ||
| 98 | + } catch (err) { | ||
| 99 | + console.error('加载更多失败:', err) | ||
| 100 | + } finally { | ||
| 101 | + loading.value = false | ||
| 102 | + } | ||
| 103 | +} | ||
| 104 | +``` | ||
| 105 | + | ||
| 106 | +## 错误处理 | ||
| 107 | + | ||
| 108 | +### 网络错误 | ||
| 109 | + | ||
| 110 | +```javascript | ||
| 111 | +try { | ||
| 112 | + const res = await fn(yourAPI(params)) | ||
| 113 | + // ... | ||
| 114 | +} catch (err) { | ||
| 115 | + if (is_network_error(err)) { | ||
| 116 | + // 网络错误 | ||
| 117 | + Taro.showToast({ title: '网络错误', icon: 'none' }) | ||
| 118 | + } else if (is_timeout_error(err)) { | ||
| 119 | + // 超时 | ||
| 120 | + Taro.showToast({ title: '请求超时', icon: 'none' }) | ||
| 121 | + } else { | ||
| 122 | + // 其他错误 | ||
| 123 | + Taro.showToast({ title: '请求失败', icon: 'none' }) | ||
| 124 | + } | ||
| 125 | +} | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +### 业务错误 | ||
| 129 | + | ||
| 130 | +```javascript | ||
| 131 | +const res = await fn(yourAPI(params)) | ||
| 132 | + | ||
| 133 | +if (res.code === 1) { | ||
| 134 | + // 成功 | ||
| 135 | +} else if (res.code === 401) { | ||
| 136 | + // 未登录(通常会被拦截器自动处理) | ||
| 137 | +} else { | ||
| 138 | + // 业务错误 | ||
| 139 | + Taro.showToast({ title: res.msg || '操作失败', icon: 'none' }) | ||
| 140 | +} | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +## API 规范 | ||
| 144 | + | ||
| 145 | +### 请求格式 | ||
| 146 | + | ||
| 147 | +```javascript | ||
| 148 | +// 查询列表 | ||
| 149 | +yourAPI({ page: 1, limit: 20, keyword: '搜索词' }) | ||
| 150 | + | ||
| 151 | +// 获取详情 | ||
| 152 | +detailAPI({ id: 123 }) | ||
| 153 | + | ||
| 154 | +// 提交表单 | ||
| 155 | +submitAPI({ field1: 'value1', field2: 'value2' }) | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +### 响应格式 | ||
| 159 | + | ||
| 160 | +```javascript | ||
| 161 | +// 成功 | ||
| 162 | +{ | ||
| 163 | + code: 1, | ||
| 164 | + msg: 'success', | ||
| 165 | + data: { /* 业务数据 */ } | ||
| 166 | +} | ||
| 167 | + | ||
| 168 | +// 失败 | ||
| 169 | +{ | ||
| 170 | + code: 0, // 或其他错误码 | ||
| 171 | + msg: '错误信息', | ||
| 172 | + data: null | ||
| 173 | +} | ||
| 174 | +``` | ||
| 175 | + | ||
| 176 | +## 最佳实践 | ||
| 177 | + | ||
| 178 | +### ✅ 推荐做法 | ||
| 179 | + | ||
| 180 | +```javascript | ||
| 181 | +// 1. 使用 async/await | ||
| 182 | +const res = await fn(yourAPI(params)) | ||
| 183 | + | ||
| 184 | +// 2. 检查 res.code === 1 | ||
| 185 | +if (res.code === 1) { | ||
| 186 | + // 成功处理 | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +// 3. 使用 try/catch | ||
| 190 | +try { | ||
| 191 | + const res = await fn(yourAPI(params)) | ||
| 192 | +} catch (err) { | ||
| 193 | + // 错误处理 | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +// 4. 显示加载状态 | ||
| 197 | +loading.value = true | ||
| 198 | +// ... API 调用 | ||
| 199 | +loading.value = false | ||
| 200 | +``` | ||
| 201 | + | ||
| 202 | +### ❌ 避免做法 | ||
| 203 | + | ||
| 204 | +```javascript | ||
| 205 | +// 1. 不要只检查 res.code | ||
| 206 | +if (res.code) { } // ❌ | ||
| 207 | + | ||
| 208 | +// 2. 不要忽略错误 | ||
| 209 | +const res = await fn(yourAPI(params)) // ❌ 无 try/catch | ||
| 210 | + | ||
| 211 | +// 3. 不要硬编码 API URL | ||
| 212 | +fetch('/srv/?a=your_action') // ❌ | ||
| 213 | +``` | ||
| 214 | + | ||
| 215 | +## 参考文档 | ||
| 216 | + | ||
| 217 | +- **[API 接口文档](docs/api-specs/)** - 完整的 API 接口规范 | ||
| 218 | +- **[接口联调日志](docs/api-integration-log.md)** - API 集成状态 |
docs/guides/navigation.md
0 → 100644
| 1 | +# 导航系统指南 | ||
| 2 | + | ||
| 3 | +本文档介绍项目中的导航系统使用方法。 | ||
| 4 | + | ||
| 5 | +## useGo Hook(推荐) | ||
| 6 | + | ||
| 7 | +`useGo` 是增强的导航 Hook,提供自动路径补全和便捷方法。 | ||
| 8 | + | ||
| 9 | +### 基础用法 | ||
| 10 | + | ||
| 11 | +```javascript | ||
| 12 | +import { useGo } from '@/hooks/useGo' | ||
| 13 | + | ||
| 14 | +const go = useGo() | ||
| 15 | + | ||
| 16 | +// 导航到页面 | ||
| 17 | +go('/pages/detail/index') | ||
| 18 | + | ||
| 19 | +// 带参数导航(例如产品 ID) | ||
| 20 | +go('/pages/product-detail/index', { id: 123 }) | ||
| 21 | + | ||
| 22 | +// 带多个参数导航 | ||
| 23 | +go('/pages/material-list/index', { category: 'insurance', page: 1 }) | ||
| 24 | + | ||
| 25 | +// 返回上一页 | ||
| 26 | +go.back() | ||
| 27 | +``` | ||
| 28 | + | ||
| 29 | +### 路径自动补全 | ||
| 30 | + | ||
| 31 | +`useGo` 会自动补全相对路径为绝对路径: | ||
| 32 | + | ||
| 33 | +```javascript | ||
| 34 | +go('detail') // → /pages/detail/index | ||
| 35 | +go('product-detail') // → /pages/product-detail/index | ||
| 36 | +``` | ||
| 37 | + | ||
| 38 | +### 返回导航 | ||
| 39 | + | ||
| 40 | +```javascript | ||
| 41 | +// 返回上一页 | ||
| 42 | +go.back() | ||
| 43 | + | ||
| 44 | +// 返回多页 | ||
| 45 | +go.back(2) | ||
| 46 | + | ||
| 47 | +// 返回首页 | ||
| 48 | +go('/pages/index/index') | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +## 在目标页面接收参数 | ||
| 52 | + | ||
| 53 | +```javascript | ||
| 54 | +import { useLoad } from '@tarojs/taro' | ||
| 55 | +import { ref } from 'vue' | ||
| 56 | + | ||
| 57 | +const productId = ref(null) | ||
| 58 | + | ||
| 59 | +useLoad((options) => { | ||
| 60 | + // 访问导航参数 | ||
| 61 | + console.log('接收到的参数:', options) | ||
| 62 | + productId.value = options.id | ||
| 63 | + | ||
| 64 | + // 根据参数获取数据 | ||
| 65 | + fetchProductDetail(options.id) | ||
| 66 | +}) | ||
| 67 | +``` | ||
| 68 | + | ||
| 69 | +## Taro 内置导航(备选方案) | ||
| 70 | + | ||
| 71 | +如果需要更底层的控制,可以直接使用 Taro 导航 API: | ||
| 72 | + | ||
| 73 | +### navigateTo - 保留当前页 | ||
| 74 | + | ||
| 75 | +```javascript | ||
| 76 | +import Taro from '@tarojs/taro' | ||
| 77 | + | ||
| 78 | +Taro.navigateTo({ | ||
| 79 | + url: '/pages/detail/index?id=123' | ||
| 80 | +}) | ||
| 81 | +``` | ||
| 82 | + | ||
| 83 | +### redirectTo - 不保留当前页 | ||
| 84 | + | ||
| 85 | +```javascript | ||
| 86 | +Taro.redirectTo({ | ||
| 87 | + url: '/pages/login/index' | ||
| 88 | +}) | ||
| 89 | +``` | ||
| 90 | + | ||
| 91 | +### switchTab - 切换 Tab | ||
| 92 | + | ||
| 93 | +```javascript | ||
| 94 | +Taro.switchTab({ | ||
| 95 | + url: '/pages/index/index' | ||
| 96 | +}) | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +### reLaunch - 重启应用 | ||
| 100 | + | ||
| 101 | +```javascript | ||
| 102 | +Taro.reLaunch({ | ||
| 103 | + url: '/pages/index/index' | ||
| 104 | +}) | ||
| 105 | +``` | ||
| 106 | + | ||
| 107 | +### navigateBack - 返回 | ||
| 108 | + | ||
| 109 | +```javascript | ||
| 110 | +// 返回上一页 | ||
| 111 | +Taro.navigateBack() | ||
| 112 | + | ||
| 113 | +// 返回多页 | ||
| 114 | +Taro.navigateBack({ delta: 2 }) | ||
| 115 | +``` | ||
| 116 | + | ||
| 117 | +## 路由状态管理 | ||
| 118 | + | ||
| 119 | +### router Store | ||
| 120 | + | ||
| 121 | +项目使用 `src/stores/router.js` 维护已访问路由的栈,主要用于认证回调导航。 | ||
| 122 | + | ||
| 123 | +```javascript | ||
| 124 | +import { useRouterStore } from '@/stores/router' | ||
| 125 | + | ||
| 126 | +const routerStore = useRouterStore() | ||
| 127 | + | ||
| 128 | +// 获取上一页路径 | ||
| 129 | +const previousPage = routerStore.previousRoute | ||
| 130 | +``` | ||
| 131 | + | ||
| 132 | +### 导航钩子 | ||
| 133 | + | ||
| 134 | +```javascript | ||
| 135 | +import { useLoad, useShow, useHide, useUnload } from '@tarojs/taro' | ||
| 136 | + | ||
| 137 | +// 页面加载时(只执行一次) | ||
| 138 | +useLoad((options) => { | ||
| 139 | + console.log('页面加载', options) | ||
| 140 | +}) | ||
| 141 | + | ||
| 142 | +// 页面显示时(每次返回都会执行) | ||
| 143 | +useShow(() => { | ||
| 144 | + console.log('页面显示') | ||
| 145 | +}) | ||
| 146 | + | ||
| 147 | +// 页面隐藏时 | ||
| 148 | +useHide(() => { | ||
| 149 | + console.log('页面隐藏') | ||
| 150 | +}) | ||
| 151 | + | ||
| 152 | +// 页面卸载时 | ||
| 153 | +useUnload(() => { | ||
| 154 | + console.log('页面卸载') | ||
| 155 | +}) | ||
| 156 | +``` | ||
| 157 | + | ||
| 158 | +## 认证场景导航 | ||
| 159 | + | ||
| 160 | +### 登录后返回原页面 | ||
| 161 | + | ||
| 162 | +```javascript | ||
| 163 | +import { useGo } from '@/hooks/useGo' | ||
| 164 | +import { useRouterStore } from '@/stores/router' | ||
| 165 | + | ||
| 166 | +const go = useGo() | ||
| 167 | +const routerStore = useRouterStore() | ||
| 168 | + | ||
| 169 | +// 1. 保存当前路径 | ||
| 170 | +routerStore.setRedirect('/pages/some-page/index') | ||
| 171 | + | ||
| 172 | +// 2. 跳转登录页 | ||
| 173 | +go('/pages/login/index') | ||
| 174 | + | ||
| 175 | +// 3. 登录成功后返回 | ||
| 176 | +const redirectUrl = routerStore.redirect | ||
| 177 | +if (redirectUrl) { | ||
| 178 | + go(redirectUrl) | ||
| 179 | + routerStore.setRedirect(null) | ||
| 180 | +} | ||
| 181 | +``` | ||
| 182 | + | ||
| 183 | +## 常见问题 | ||
| 184 | + | ||
| 185 | +### Q: 导航后页面不刷新? | ||
| 186 | + | ||
| 187 | +A: 使用 `useShow` 钩子,每次页面显示时都会执行: | ||
| 188 | + | ||
| 189 | +```javascript | ||
| 190 | +useShow(() => { | ||
| 191 | + // 重新加载数据 | ||
| 192 | + fetchData() | ||
| 193 | +}) | ||
| 194 | +``` | ||
| 195 | + | ||
| 196 | +### Q: 参数传递丢失? | ||
| 197 | + | ||
| 198 | +A: 确保参数类型正确: | ||
| 199 | + | ||
| 200 | +```javascript | ||
| 201 | +// ❌ 错误:对象作为参数 | ||
| 202 | +go('/pages/detail/index', { data: { id: 1 } }) | ||
| 203 | + | ||
| 204 | +// ✅ 正确:扁平参数 | ||
| 205 | +go('/pages/detail/index', { id: 1, type: 'insurance' }) | ||
| 206 | +``` | ||
| 207 | + | ||
| 208 | +### Q: 如何清除页面栈? | ||
| 209 | + | ||
| 210 | +A: 使用 `redirectTo` 或 `reLaunch`: | ||
| 211 | + | ||
| 212 | +```javascript | ||
| 213 | +// 不保留当前页 | ||
| 214 | +Taro.redirectTo({ url: '/pages/login/index' }) | ||
| 215 | + | ||
| 216 | +// 清空所有页面栈 | ||
| 217 | +Taro.reLaunch({ url: '/pages/index/index' }) | ||
| 218 | +``` | ||
| 219 | + | ||
| 220 | +## 参考文档 | ||
| 221 | + | ||
| 222 | +- **[Taro 导航文档](https://docs.taro.zone/docs/vue-navigation)** - 官方文档 | ||
| 223 | +- **[页面开发指南](guides/page-development.md)** - 页面创建和路由注册 |
docs/guides/page-development.md
0 → 100644
| 1 | +# 页面开发指南 | ||
| 2 | + | ||
| 3 | +本文档介绍如何在项目中添加新页面。 | ||
| 4 | + | ||
| 5 | +## 目录结构 | ||
| 6 | + | ||
| 7 | +所有页面遵循以下目录结构: | ||
| 8 | +``` | ||
| 9 | +src/pages/your-page/ | ||
| 10 | +├── index.vue # 页面组件(必须使用 <script setup>) | ||
| 11 | +├── index.config.js # 页面配置(navigationBarTitleText 等) | ||
| 12 | +└── assets/ # 页面特定资源(可选) | ||
| 13 | +``` | ||
| 14 | + | ||
| 15 | +## 步骤 1:创建页面目录和文件 | ||
| 16 | + | ||
| 17 | +```bash | ||
| 18 | +mkdir -p src/pages/your-page | ||
| 19 | +touch src/pages/your-page/index.vue | ||
| 20 | +touch src/pages/your-page/index.config.js | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +## 步骤 2:配置页面 | ||
| 24 | + | ||
| 25 | +**`index.config.js`**: | ||
| 26 | +```javascript | ||
| 27 | +export default { | ||
| 28 | + navigationBarTitleText: '您的页面标题', | ||
| 29 | + enablePullDownRefresh: true, | ||
| 30 | + backgroundColor: '#f5f5f5' | ||
| 31 | +} | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +## 步骤 3:编写页面组件 | ||
| 35 | + | ||
| 36 | +**`index.vue`**: | ||
| 37 | +```vue | ||
| 38 | +<script setup> | ||
| 39 | +import { ref } from 'vue' | ||
| 40 | +import { useLoad, useShow } from '@tarojs/taro' | ||
| 41 | + | ||
| 42 | +const pageId = ref(null) | ||
| 43 | + | ||
| 44 | +useLoad((options) => { | ||
| 45 | + console.log('页面加载,参数:', options) | ||
| 46 | + // 接收导航参数 | ||
| 47 | + if (options.id) { | ||
| 48 | + pageId.value = options.id | ||
| 49 | + // 根据 ID 获取数据 | ||
| 50 | + } | ||
| 51 | +}) | ||
| 52 | + | ||
| 53 | +useShow(() => { | ||
| 54 | + console.log('页面显示') | ||
| 55 | +}) | ||
| 56 | + | ||
| 57 | +// 您的组件逻辑 | ||
| 58 | +</script> | ||
| 59 | + | ||
| 60 | +<template> | ||
| 61 | + <view class="page"> | ||
| 62 | + <!-- 页面内容 --> | ||
| 63 | + </view> | ||
| 64 | +</template> | ||
| 65 | + | ||
| 66 | +<style lang="less" scoped> | ||
| 67 | +.page { | ||
| 68 | + padding: 20px; | ||
| 69 | +} | ||
| 70 | +</style> | ||
| 71 | +``` | ||
| 72 | + | ||
| 73 | +## 步骤 4:注册路由 | ||
| 74 | + | ||
| 75 | +在 **`src/app.config.js`** 中注册路由: | ||
| 76 | + | ||
| 77 | +```javascript | ||
| 78 | +export default { | ||
| 79 | + pages: [ | ||
| 80 | + 'pages/your-page/index', | ||
| 81 | + // ... 其他页面 | ||
| 82 | + ], | ||
| 83 | + // ... | ||
| 84 | +} | ||
| 85 | +``` | ||
| 86 | + | ||
| 87 | +## 步骤 5:添加导航(可选) | ||
| 88 | + | ||
| 89 | +### 使用 useGo Hook(推荐) | ||
| 90 | + | ||
| 91 | +```javascript | ||
| 92 | +import { useGo } from '@/hooks/useGo' | ||
| 93 | +const go = useGo() | ||
| 94 | + | ||
| 95 | +// 带查询参数导航 | ||
| 96 | +go('/pages/your-page/index', { id: 123, type: 'insurance' }) | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +### 使用 Taro 内置导航 | ||
| 100 | + | ||
| 101 | +```javascript | ||
| 102 | +import Taro from '@tarojs/taro' | ||
| 103 | + | ||
| 104 | +Taro.navigateTo({ | ||
| 105 | + url: '/pages/your-page/index?id=123' | ||
| 106 | +}) | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +## 接收导航参数 | ||
| 110 | + | ||
| 111 | +在目标页面的 `useLoad` 中接收参数: | ||
| 112 | + | ||
| 113 | +```javascript | ||
| 114 | +useLoad((options) => { | ||
| 115 | + console.log('接收到的参数:', options) | ||
| 116 | + const { id, type } = options | ||
| 117 | + | ||
| 118 | + // 根据参数获取数据 | ||
| 119 | + fetchData(id, type) | ||
| 120 | +}) | ||
| 121 | +``` | ||
| 122 | + | ||
| 123 | +## TabBar 集成(可选) | ||
| 124 | + | ||
| 125 | +如果页面需要底部导航栏: | ||
| 126 | + | ||
| 127 | +1. 导入 `TabBar` 组件 | ||
| 128 | +2. 根据路由配置激活状态 | ||
| 129 | + | ||
| 130 | +```vue | ||
| 131 | +<script setup> | ||
| 132 | +import TabBar from '@/components/TabBar.vue' | ||
| 133 | +</script> | ||
| 134 | + | ||
| 135 | +<template> | ||
| 136 | + <view class="page"> | ||
| 137 | + <!-- 页面内容 --> | ||
| 138 | + <TabBar /> | ||
| 139 | + </view> | ||
| 140 | +</template> | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +## 常见问题 | ||
| 144 | + | ||
| 145 | +### Q: 页面注册后还是 404? | ||
| 146 | + | ||
| 147 | +A: 检查以下几点: | ||
| 148 | +1. 路由路径是否正确(`pages/your-page/index`) | ||
| 149 | +2. 是否重启了开发服务器 | ||
| 150 | +3. `index.config.js` 是否存在 | ||
| 151 | + | ||
| 152 | +### Q: 如何隐藏原生导航栏? | ||
| 153 | + | ||
| 154 | +A: 在 `index.config.js` 中设置: | ||
| 155 | +```javascript | ||
| 156 | +export default { | ||
| 157 | + navigationStyle: 'custom' | ||
| 158 | +} | ||
| 159 | +``` | ||
| 160 | + | ||
| 161 | +### Q: 页面参数丢失? | ||
| 162 | + | ||
| 163 | +A: 检查参数是否正确编码: | ||
| 164 | +```javascript | ||
| 165 | +// ❌ 错误 | ||
| 166 | +go('/pages/detail/index', { id: '123' }) | ||
| 167 | + | ||
| 168 | +// ✅ 正确 | ||
| 169 | +go('/pages/detail/index', { id: 123 }) // 数字类型 | ||
| 170 | +// 或 | ||
| 171 | +go('/pages/detail/index?id=123') // 字符串类型 | ||
| 172 | +``` |
docs/reference/components.md
0 → 100644
| 1 | +# 组件库参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有组件的详细说明。 | ||
| 4 | + | ||
| 5 | +## 导航与布局组件 | ||
| 6 | + | ||
| 7 | +### TabBar.vue - 底部导航栏 | ||
| 8 | + | ||
| 9 | +**位置**:`src/components/TabBar.vue` | ||
| 10 | + | ||
| 11 | +**功能**:固定底部导航栏,自动适配安全区域 | ||
| 12 | + | ||
| 13 | +**关键特性**: | ||
| 14 | +- 支持图标 + 文字布局 | ||
| 15 | +- 激活状态高亮 | ||
| 16 | + | ||
| 17 | +**使用页面**:首页、我的、家办、知识库、签单页面 | ||
| 18 | + | ||
| 19 | +--- | ||
| 20 | + | ||
| 21 | +### NavHeader.vue - 自定义导航头 | ||
| 22 | + | ||
| 23 | +**位置**:`src/components/NavHeader.vue` | ||
| 24 | + | ||
| 25 | +**功能**:带返回按钮的自定义导航头 | ||
| 26 | + | ||
| 27 | +**关键特性**: | ||
| 28 | +- 透明/背景变体 | ||
| 29 | +- 刘海屏设备的安全区域内边距 | ||
| 30 | + | ||
| 31 | +--- | ||
| 32 | + | ||
| 33 | +### indexNav.vue - 首页网格导航 | ||
| 34 | + | ||
| 35 | +**位置**:`src/components/indexNav.vue` | ||
| 36 | + | ||
| 37 | +**功能**:首页网格导航 | ||
| 38 | + | ||
| 39 | +--- | ||
| 40 | + | ||
| 41 | +## 图标与媒体组件 | ||
| 42 | + | ||
| 43 | +### IconFont.vue - 图标字体包装器 | ||
| 44 | + | ||
| 45 | +**位置**:`src/components/IconFont.vue` | ||
| 46 | + | ||
| 47 | +**功能**:自定义图标的图标字体包装器 | ||
| 48 | + | ||
| 49 | +**⚠️ 注意**:动态切换时需添加 `:key="name"` | ||
| 50 | + | ||
| 51 | +```vue | ||
| 52 | +<IconFont :name="iconName" :key="iconName" /> | ||
| 53 | +``` | ||
| 54 | + | ||
| 55 | +--- | ||
| 56 | + | ||
| 57 | +### qrCode.vue - 二维码显示 | ||
| 58 | + | ||
| 59 | +**位置**:`src/components/qrCode.vue` | ||
| 60 | + | ||
| 61 | +**功能**:二维码显示 | ||
| 62 | + | ||
| 63 | +--- | ||
| 64 | + | ||
| 65 | +### qrCodeSearch.vue - 二维码扫描 | ||
| 66 | + | ||
| 67 | +**位置**:`src/components/qrCodeSearch.vue` | ||
| 68 | + | ||
| 69 | +**功能**:二维码扫描 | ||
| 70 | + | ||
| 71 | +--- | ||
| 72 | + | ||
| 73 | +## 列表与展示组件 | ||
| 74 | + | ||
| 75 | +### SectionCard.vue - 分组卡片 | ||
| 76 | + | ||
| 77 | +**位置**:`src/components/SectionCard.vue` | ||
| 78 | + | ||
| 79 | +**功能**:分组卡片组件 | ||
| 80 | + | ||
| 81 | +--- | ||
| 82 | + | ||
| 83 | +### SectionItem.vue - 分组列表项 | ||
| 84 | + | ||
| 85 | +**位置**:`src/components/SectionItem.vue` | ||
| 86 | + | ||
| 87 | +**功能**:分组列表项组件 | ||
| 88 | + | ||
| 89 | +--- | ||
| 90 | + | ||
| 91 | +### ListItemActions/ - 列表项操作按钮 | ||
| 92 | + | ||
| 93 | +**位置**:`src/components/list/ListItemActions/` | ||
| 94 | + | ||
| 95 | +**功能**:列表项操作按钮组 | ||
| 96 | + | ||
| 97 | +**关键特性**: | ||
| 98 | +- 权限检查 | ||
| 99 | +- 自动埋点上报 | ||
| 100 | +- 查看按钮、收藏按钮等 | ||
| 101 | + | ||
| 102 | +--- | ||
| 103 | + | ||
| 104 | +### MaterialCard.vue - 资料卡片(可复用) | ||
| 105 | + | ||
| 106 | +**位置**:`src/components/MaterialCard.vue` | ||
| 107 | + | ||
| 108 | +**功能**:资料展示卡片 | ||
| 109 | + | ||
| 110 | +**关键特性**: | ||
| 111 | +- 自包含业务逻辑:查看、收藏 | ||
| 112 | +- 支持动态标签 | ||
| 113 | +- 文件大小格式化 | ||
| 114 | +- 学习人数显示 | ||
| 115 | +- 支持图片文件预览(使用 Taro.previewImage) | ||
| 116 | + | ||
| 117 | +**使用页面**:首页、搜索页、周热门资料页 | ||
| 118 | + | ||
| 119 | +**使用的 Composable**: | ||
| 120 | +- useCollectOperation | ||
| 121 | +- useListItemClick | ||
| 122 | + | ||
| 123 | +--- | ||
| 124 | + | ||
| 125 | +### ProductCard.vue - 产品卡片(可复用) | ||
| 126 | + | ||
| 127 | +**位置**:`src/components/ProductCard.vue` | ||
| 128 | + | ||
| 129 | +**功能**:产品展示卡片 | ||
| 130 | + | ||
| 131 | +**关键特性**: | ||
| 132 | +- 自定义样式:动态标签、封面图 | ||
| 133 | +- 支持产品详情查看 | ||
| 134 | +- 支持计划书功能 | ||
| 135 | + | ||
| 136 | +**使用页面**:首页、搜索页、产品中心页 | ||
| 137 | + | ||
| 138 | +--- | ||
| 139 | + | ||
| 140 | +## 表单与输入组件 | ||
| 141 | + | ||
| 142 | +### FilterTabs.vue - 过滤标签 | ||
| 143 | + | ||
| 144 | +**位置**:`src/components/FilterTabs.vue` | ||
| 145 | + | ||
| 146 | +**功能**:过滤标签组件 | ||
| 147 | + | ||
| 148 | +--- | ||
| 149 | + | ||
| 150 | +### SearchBar.vue - 搜索栏 | ||
| 151 | + | ||
| 152 | +**位置**:`src/components/SearchBar.vue` | ||
| 153 | + | ||
| 154 | +**功能**:搜索栏组件 | ||
| 155 | + | ||
| 156 | +--- | ||
| 157 | + | ||
| 158 | +## 文档预览组件 | ||
| 159 | + | ||
| 160 | +### DocumentPreview/ - 文档预览 | ||
| 161 | + | ||
| 162 | +**位置**:`src/components/DocumentPreview/` | ||
| 163 | + | ||
| 164 | +**功能**:文档预览组件 | ||
| 165 | + | ||
| 166 | +--- | ||
| 167 | + | ||
| 168 | +### PdfPreview.vue - PDF 预览 | ||
| 169 | + | ||
| 170 | +**位置**:`src/components/PdfPreview.vue` | ||
| 171 | + | ||
| 172 | +**功能**:PDF 文件预览 | ||
| 173 | + | ||
| 174 | +--- | ||
| 175 | + | ||
| 176 | +### OfficeViewer.vue - Office 文档查看器 | ||
| 177 | + | ||
| 178 | +**位置**:`src/components/OfficeViewer.vue` | ||
| 179 | + | ||
| 180 | +**功能**:Office 文档查看 | ||
| 181 | + | ||
| 182 | +--- | ||
| 183 | + | ||
| 184 | +## 业务组件 | ||
| 185 | + | ||
| 186 | +### PlanSchemes/ - 计划方案 | ||
| 187 | + | ||
| 188 | +**位置**:`src/components/plan/PlanSchemes/` | ||
| 189 | + | ||
| 190 | +**功能**:计划方案组件(SchemeA, SchemeB) | ||
| 191 | + | ||
| 192 | +--- | ||
| 193 | + | ||
| 194 | +### PlanPopup/ - 计划弹窗 | ||
| 195 | + | ||
| 196 | +**位置**:`src/components/plan/PlanPopup/` | ||
| 197 | + | ||
| 198 | +**功能**:计划弹窗组件 | ||
| 199 | + | ||
| 200 | +**关键特性**: | ||
| 201 | +- 使用 provide/inject 模式实现父子弹窗通信 | ||
| 202 | +- 子弹窗打开时自动隐藏父级 footer | ||
| 203 | +- 支持 AgePicker、DatePicker、SelectPicker 等字段组件 | ||
| 204 | + | ||
| 205 | +--- | ||
| 206 | + | ||
| 207 | +### PlanFields/ - 计划书表单字段 | ||
| 208 | + | ||
| 209 | +**位置**:`src/components/plan/PlanFields/` | ||
| 210 | + | ||
| 211 | +**功能**:计划书表单字段组件集 | ||
| 212 | + | ||
| 213 | +**子组件**: | ||
| 214 | +- `AgePicker.vue` - 年龄选择器 | ||
| 215 | +- `AmountInput.vue` - 金额输入框 | ||
| 216 | +- `DatePicker.vue` - 日期选择器 | ||
| 217 | +- `SelectPicker.vue` - 下拉选择器 | ||
| 218 | +- `RadioGroup.vue` - 单选按钮组 | ||
| 219 | + | ||
| 220 | +--- | ||
| 221 | + | ||
| 222 | +### PlanTemplates/ - 计划模板 | ||
| 223 | + | ||
| 224 | +**位置**:`src/components/plan/PlanTemplates/` | ||
| 225 | + | ||
| 226 | +**功能**:计划模板组件 | ||
| 227 | + | ||
| 228 | +--- | ||
| 229 | + | ||
| 230 | +## 工具组件 | ||
| 231 | + | ||
| 232 | +### PosterBuilder/ - 海报生成器 | ||
| 233 | + | ||
| 234 | +**位置**:`src/components/PosterBuilder/` | ||
| 235 | + | ||
| 236 | +**功能**:海报生成 | ||
| 237 | + | ||
| 238 | +--- | ||
| 239 | + | ||
| 240 | +### time-picker-data/ - 时间选择器数据 | ||
| 241 | + | ||
| 242 | +**位置**:`src/components/time-picker-data/` | ||
| 243 | + | ||
| 244 | +**功能**:时间选择器数据配置 | ||
| 245 | + | ||
| 246 | +--- | ||
| 247 | + | ||
| 248 | +## 可选功能组件 | ||
| 249 | + | ||
| 250 | +以下组件如果不需要,可以移除: | ||
| 251 | + | ||
| 252 | +- **微信支付**:`src/utils/wechatPay.js`、`src/api/wx/pay.js` | ||
| 253 | +- **二维码**:`src/components/qrCode.vue`、`src/components/qrCodeSearch.vue` | ||
| 254 | +- **海报生成器**:`src/components/PosterBuilder/` | ||
| 255 | +- **时间选择器**:`src/components/time-picker-data/` | ||
| 256 | + | ||
| 257 | +--- | ||
| 258 | + | ||
| 259 | +## 组件使用原则 | ||
| 260 | + | ||
| 261 | +### 第 3 次出现原则 | ||
| 262 | + | ||
| 263 | +当相同代码模式出现 3 次时,**必须**抽取为组件。 | ||
| 264 | + | ||
| 265 | +### 组件自包含原则 | ||
| 266 | + | ||
| 267 | +对于重复的UI结构,抽取为可复用组件(如 MaterialCard、ProductCard): | ||
| 268 | +- 组件应该自包含业务逻辑(查看、收藏等) | ||
| 269 | +- 通过事件与父组件通信 | ||
| 270 | +- 避免在父组件中重复编写相同的逻辑代码 | ||
| 271 | + | ||
| 272 | +## 相关文档 | ||
| 273 | + | ||
| 274 | +- **[Composables 参考](composables.md)** - 可复用逻辑 |
docs/reference/composables.md
0 → 100644
| 1 | +# Composables 参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有 Composables 的详细说明。 | ||
| 4 | + | ||
| 5 | +## 项目中的 Composables | ||
| 6 | + | ||
| 7 | +| Composable | 用途 | 文档 | | ||
| 8 | +|-----------|------|------| | ||
| 9 | +| `useSectionList` | 分组列表管理 | [详情](#usesectionlist) | | ||
| 10 | +| `useFileOperation` | 文件下载、预览、打开 | [详情](#usefileoperation) | | ||
| 11 | +| `useListItemClick` | 统一的列表点击处理 | [详情](#uselistitemclick) | | ||
| 12 | +| `useCollectOperation` | 收藏操作 | [详情](#usecollectoperation) | | ||
| 13 | +| `useEventTracking` | 事件埋点 | [详情](#useeventtracking) | | ||
| 14 | +| `useGo` | 增强导航 | [详情](#usego) | | ||
| 15 | +| `usePlanPermission` | 计划书权限检查 | [详情](#useplanpermission) | | ||
| 16 | + | ||
| 17 | +--- | ||
| 18 | + | ||
| 19 | +## useSectionList | ||
| 20 | + | ||
| 21 | +**位置**:`src/composables/useSectionList.js` | ||
| 22 | + | ||
| 23 | +**功能**:分组列表管理 | ||
| 24 | + | ||
| 25 | +**用途**:处理分组数据的展开/收起、过滤等逻辑 | ||
| 26 | + | ||
| 27 | +**示例**: | ||
| 28 | +```javascript | ||
| 29 | +import { useSectionList } from '@/composables/useSectionList' | ||
| 30 | + | ||
| 31 | +const { sections, toggleSection, isExpanded } = useSectionList(data) | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +--- | ||
| 35 | + | ||
| 36 | +## useFileOperation | ||
| 37 | + | ||
| 38 | +**位置**:`src/composables/useFileOperation.js` | ||
| 39 | + | ||
| 40 | +**功能**:文件操作(下载、预览、打开) | ||
| 41 | + | ||
| 42 | +**用途**:统一的文件操作逻辑,支持多种文件类型 | ||
| 43 | + | ||
| 44 | +**示例**: | ||
| 45 | +```javascript | ||
| 46 | +import { useFileOperation } from '@/composables/useFileOperation' | ||
| 47 | + | ||
| 48 | +const { downloadFile, previewFile, openFile } = useFileOperation() | ||
| 49 | + | ||
| 50 | +// 下载文件 | ||
| 51 | +await downloadFile(file) | ||
| 52 | + | ||
| 53 | +// 预览文件 | ||
| 54 | +await previewFile(file) | ||
| 55 | +``` | ||
| 56 | + | ||
| 57 | +--- | ||
| 58 | + | ||
| 59 | +## useListItemClick | ||
| 60 | + | ||
| 61 | +**位置**:`src/composables/useListItemClick.js` | ||
| 62 | + | ||
| 63 | +**功能**:统一的列表点击处理 | ||
| 64 | + | ||
| 65 | +**用途**:处理列表项的点击事件,包含权限检查和埋点 | ||
| 66 | + | ||
| 67 | +**示例**: | ||
| 68 | +```javascript | ||
| 69 | +import { useListItemClick } from '@/composables/useListItemClick' | ||
| 70 | + | ||
| 71 | +const { handleItemClick } = useListItemClick() | ||
| 72 | + | ||
| 73 | +// 处理点击 | ||
| 74 | +await handleItemClick(item, () => { | ||
| 75 | + // 点击后的操作 | ||
| 76 | +}) | ||
| 77 | +``` | ||
| 78 | + | ||
| 79 | +--- | ||
| 80 | + | ||
| 81 | +## useCollectOperation | ||
| 82 | + | ||
| 83 | +**位置**:`src/composables/useCollectOperation.js` | ||
| 84 | + | ||
| 85 | +**功能**:收藏/取消收藏操作 | ||
| 86 | + | ||
| 87 | +**用途**:处理收藏状态切换和 API 调用 | ||
| 88 | + | ||
| 89 | +**示例**: | ||
| 90 | +```javascript | ||
| 91 | +import { useCollectOperation } from '@/composables/useCollectOperation' | ||
| 92 | + | ||
| 93 | +const { isCollected, toggleCollect } = useCollectOperation(metaId) | ||
| 94 | + | ||
| 95 | +// 切换收藏状态 | ||
| 96 | +await toggleCollect() | ||
| 97 | +``` | ||
| 98 | + | ||
| 99 | +--- | ||
| 100 | + | ||
| 101 | +## useEventTracking | ||
| 102 | + | ||
| 103 | +**位置**:`src/composables/useEventTracking.js` | ||
| 104 | + | ||
| 105 | +**功能**:事件埋点 | ||
| 106 | + | ||
| 107 | +**用途**:统一的事件埋点功能,支持多种埋点类型 | ||
| 108 | + | ||
| 109 | +**事件类型**: | ||
| 110 | +- `READ_FILE` - 阅读素材 | ||
| 111 | + | ||
| 112 | +**示例**: | ||
| 113 | +```javascript | ||
| 114 | +import { useEventTracking } from '@/composables/useEventTracking' | ||
| 115 | + | ||
| 116 | +const { trackEvent, trackFileRead } = useEventTracking() | ||
| 117 | + | ||
| 118 | +// 追踪阅读事件 | ||
| 119 | +await trackFileRead('file-id-123') | ||
| 120 | + | ||
| 121 | +// 追踪自定义事件 | ||
| 122 | +await trackEvent('CUSTOM_EVENT', 'object-id', { | ||
| 123 | + title: '文档标题', | ||
| 124 | + category: '分类' | ||
| 125 | +}) | ||
| 126 | +``` | ||
| 127 | + | ||
| 128 | +--- | ||
| 129 | + | ||
| 130 | +## useGo | ||
| 131 | + | ||
| 132 | +**位置**:`src/hooks/useGo.js` | ||
| 133 | + | ||
| 134 | +**功能**:增强导航 | ||
| 135 | + | ||
| 136 | +**用途**:自动路径补全和便捷导航方法 | ||
| 137 | + | ||
| 138 | +**示例**: | ||
| 139 | +```javascript | ||
| 140 | +import { useGo } from '@/hooks/useGo' | ||
| 141 | + | ||
| 142 | +const go = useGo() | ||
| 143 | + | ||
| 144 | +// 导航到页面 | ||
| 145 | +go('/pages/detail/index') | ||
| 146 | + | ||
| 147 | +// 带参数导航 | ||
| 148 | +go('/pages/product-detail/index', { id: 123 }) | ||
| 149 | + | ||
| 150 | +// 返回 | ||
| 151 | +go.back() | ||
| 152 | +``` | ||
| 153 | + | ||
| 154 | +--- | ||
| 155 | + | ||
| 156 | +## usePlanPermission | ||
| 157 | + | ||
| 158 | +**位置**:`src/composables/usePlanPermission.js` | ||
| 159 | + | ||
| 160 | +**功能**:计划书权限检查 | ||
| 161 | + | ||
| 162 | +**用途**:检查用户是否有权限操作计划书 | ||
| 163 | + | ||
| 164 | +**示例**: | ||
| 165 | +```javascript | ||
| 166 | +import { usePlanPermission } from '@/composables/usePlanPermission' | ||
| 167 | + | ||
| 168 | +const { checkPlanPermission } = usePlanPermission() | ||
| 169 | + | ||
| 170 | +// 检查权限后执行操作 | ||
| 171 | +await checkPlanPermission(() => { | ||
| 172 | + // 有权限后的操作 | ||
| 173 | +}) | ||
| 174 | +``` | ||
| 175 | + | ||
| 176 | +--- | ||
| 177 | + | ||
| 178 | +## 抽取原则 | ||
| 179 | + | ||
| 180 | +**第 3 次出现原则** | ||
| 181 | + | ||
| 182 | +当相同代码模式出现 3 次时,**必须**抽取为 Composable。 | ||
| 183 | + | ||
| 184 | +**示例**: | ||
| 185 | + | ||
| 186 | +```javascript | ||
| 187 | +// ❌ BAD - 在多个组件中重复 | ||
| 188 | +const handleClick = async () => { | ||
| 189 | + if (!isLoggedIn()) { | ||
| 190 | + Taro.showToast({ title: '请先登录', icon: 'none' }) | ||
| 191 | + return | ||
| 192 | + } | ||
| 193 | + // ... 业务逻辑 | ||
| 194 | +} | ||
| 195 | + | ||
| 196 | +// ✅ GOOD - 抽取为 Composable | ||
| 197 | +const { requireLogin } = usePermission() | ||
| 198 | +await requireLogin(() => { | ||
| 199 | + // ... 业务逻辑 | ||
| 200 | +}) | ||
| 201 | +``` | ||
| 202 | + | ||
| 203 | +--- | ||
| 204 | + | ||
| 205 | +## 创建新的 Composable | ||
| 206 | + | ||
| 207 | +### 命名规范 | ||
| 208 | + | ||
| 209 | +- 使用 `use` 前缀 | ||
| 210 | +- 使用驼峰命名 | ||
| 211 | +- 名称应描述功能 | ||
| 212 | + | ||
| 213 | +**示例**: | ||
| 214 | +- ✅ `useUserData` | ||
| 215 | +- ✅ `useFormValidation` | ||
| 216 | +- ❌ `userData` | ||
| 217 | +- ❌ `validation` | ||
| 218 | + | ||
| 219 | +### 基本结构 | ||
| 220 | + | ||
| 221 | +```javascript | ||
| 222 | +/** | ||
| 223 | + * 使用 XXX 功能 | ||
| 224 | + * | ||
| 225 | + * @description 功能描述 | ||
| 226 | + * @returns {Object} 返回值描述 | ||
| 227 | + */ | ||
| 228 | +export function useXxx() { | ||
| 229 | + // 响应式状态 | ||
| 230 | + const state = ref(null) | ||
| 231 | + | ||
| 232 | + // 方法 | ||
| 233 | + const method = () => { | ||
| 234 | + // ... | ||
| 235 | + } | ||
| 236 | + | ||
| 237 | + // 返回公共 API | ||
| 238 | + return { | ||
| 239 | + state, | ||
| 240 | + method | ||
| 241 | + } | ||
| 242 | +} | ||
| 243 | +``` | ||
| 244 | + | ||
| 245 | +### 使用示例 | ||
| 246 | + | ||
| 247 | +```javascript | ||
| 248 | +// 在组件中使用 | ||
| 249 | +import { useXxx } from '@/composables/useXxx' | ||
| 250 | + | ||
| 251 | +const { state, method } = useXxx() | ||
| 252 | +``` | ||
| 253 | + | ||
| 254 | +--- | ||
| 255 | + | ||
| 256 | +## 相关文档 | ||
| 257 | + | ||
| 258 | +- **[组件参考](components.md)** - 可复用组件 | ||
| 259 | +- **[最佳实践](../best-practices.md)** - 代码复用原则 |
docs/reference/pages.md
0 → 100644
| 1 | +# 页面参考文档 | ||
| 2 | + | ||
| 3 | +本文档列出项目中所有页面的详细说明。 | ||
| 4 | + | ||
| 5 | +## 核心页面 | ||
| 6 | + | ||
| 7 | +### 1. pages/index/index - 首页 | ||
| 8 | + | ||
| 9 | +**功能**:产品展示、搜索、网格导航 | ||
| 10 | + | ||
| 11 | +**关键特性**: | ||
| 12 | +- 热门产品的"产品资料"按钮跳转到 `product-detail` 页面,带产品 ID | ||
| 13 | +- 热门资料的"查看更多"跳转到 `material-list` 页面 | ||
| 14 | +- 网格导航图标跳转到各个业务页面 | ||
| 15 | + | ||
| 16 | +**使用的组件**: | ||
| 17 | +- MaterialCard - 资料卡片 | ||
| 18 | +- ProductCard - 产品卡片 | ||
| 19 | + | ||
| 20 | +--- | ||
| 21 | + | ||
| 22 | +### 2. pages/search/index - 搜索页 | ||
| 23 | + | ||
| 24 | +**功能**:产品和资料搜索 | ||
| 25 | + | ||
| 26 | +**关键特性**: | ||
| 27 | +- 支持实时搜索(输入关键字自动调用 searchAPI) | ||
| 28 | +- 双Tab切换(产品、资料) | ||
| 29 | +- 支持分页加载和触底加载更多 | ||
| 30 | + | ||
| 31 | +**使用的组件**: | ||
| 32 | +- MaterialCard | ||
| 33 | +- ProductCard | ||
| 34 | + | ||
| 35 | +--- | ||
| 36 | + | ||
| 37 | +### 3. pages/webview/index - WebView 包装器 | ||
| 38 | + | ||
| 39 | +**功能**:外部 URL 的 WebView 包装器 | ||
| 40 | + | ||
| 41 | +--- | ||
| 42 | + | ||
| 43 | +### 4. pages/document-preview/index - 文档预览页 | ||
| 44 | + | ||
| 45 | +**功能**:文档预览 | ||
| 46 | + | ||
| 47 | +--- | ||
| 48 | + | ||
| 49 | +### 5. pages/document-demo/index - 文档预览演示页 | ||
| 50 | + | ||
| 51 | +**功能**:文档预览演示 | ||
| 52 | + | ||
| 53 | +--- | ||
| 54 | + | ||
| 55 | +### 6. pages/onboarding/index - 新用户引导 | ||
| 56 | + | ||
| 57 | +**功能**:新用户引导流程 | ||
| 58 | + | ||
| 59 | +--- | ||
| 60 | + | ||
| 61 | +## 业务页面 | ||
| 62 | + | ||
| 63 | +### 7. pages/family-office/index - 家族办公室 | ||
| 64 | + | ||
| 65 | +**功能**:家族办公室服务 | ||
| 66 | + | ||
| 67 | +--- | ||
| 68 | + | ||
| 69 | +### 8. pages/product-center/index - 产品中心 | ||
| 70 | + | ||
| 71 | +**功能**:产品列表展示 | ||
| 72 | + | ||
| 73 | +**关键特性**: | ||
| 74 | +- 分类筛选 | ||
| 75 | +- 分页加载 | ||
| 76 | + | ||
| 77 | +--- | ||
| 78 | + | ||
| 79 | +### 9. pages/category-list/index - 分类列表 | ||
| 80 | + | ||
| 81 | +**功能**:分类列表展示 | ||
| 82 | + | ||
| 83 | +--- | ||
| 84 | + | ||
| 85 | +### 10. pages/product-detail/index - 产品详情 | ||
| 86 | + | ||
| 87 | +**功能**:产品详情展示 | ||
| 88 | + | ||
| 89 | +**关键特性**: | ||
| 90 | +- 通过 Taro 的 `useLoad` hook 接收 `id` 参数 | ||
| 91 | +- 导航示例:`go('/pages/product-detail/index', { id: 1 })` | ||
| 92 | +- 参数可用于从 API 获取产品详情 | ||
| 93 | + | ||
| 94 | +--- | ||
| 95 | + | ||
| 96 | +### 11. pages/material-list/index - 资料列表 | ||
| 97 | + | ||
| 98 | +**功能**:资料/文档列表展示 | ||
| 99 | + | ||
| 100 | +**关键特性**: | ||
| 101 | +- 分类筛选 | ||
| 102 | +- 分页加载 | ||
| 103 | +- 触底加载更多 | ||
| 104 | + | ||
| 105 | +--- | ||
| 106 | + | ||
| 107 | +### 12. pages/week-hot-material/index - 周热门资料 | ||
| 108 | + | ||
| 109 | +**功能**:热门资料展示 | ||
| 110 | + | ||
| 111 | +**关键特性**: | ||
| 112 | +- 使用 MaterialCard 组件展示热门资料 | ||
| 113 | +- 支持分页加载和触底加载更多 | ||
| 114 | + | ||
| 115 | +--- | ||
| 116 | + | ||
| 117 | +### 13. pages/signing/index - 签约 | ||
| 118 | + | ||
| 119 | +**功能**:签约流程 | ||
| 120 | + | ||
| 121 | +--- | ||
| 122 | + | ||
| 123 | +### 14. pages/mine/index - 我的 | ||
| 124 | + | ||
| 125 | +**功能**:用户资料页面 | ||
| 126 | + | ||
| 127 | +**关键特性**: | ||
| 128 | +- 用户信息展示 | ||
| 129 | +- 设置入口 | ||
| 130 | + | ||
| 131 | +--- | ||
| 132 | + | ||
| 133 | +### 15. pages/plan/index - 计划书管理 | ||
| 134 | + | ||
| 135 | +**功能**:业务计划管理 | ||
| 136 | + | ||
| 137 | +**关键特性**: | ||
| 138 | +- 使用 PlanSchemes 和 PlanPopup 组件 | ||
| 139 | +- 支持嵌套弹窗交互(provide/inject 模式) | ||
| 140 | +- 支持滚动加载和分页 | ||
| 141 | + | ||
| 142 | +--- | ||
| 143 | + | ||
| 144 | +### 16. pages/plan-submit-result/index - 计划提交结果 | ||
| 145 | + | ||
| 146 | +**功能**:计划提交结果展示 | ||
| 147 | + | ||
| 148 | +**关键特性**: | ||
| 149 | +- 导航按钮:返回上一页(非首页) | ||
| 150 | + | ||
| 151 | +--- | ||
| 152 | + | ||
| 153 | +## 用户相关页面 | ||
| 154 | + | ||
| 155 | +### 17. pages/favorites/index - 收藏 | ||
| 156 | + | ||
| 157 | +**功能**:用户收藏列表 | ||
| 158 | + | ||
| 159 | +--- | ||
| 160 | + | ||
| 161 | +### 18. pages/avatar/index - 头像设置 | ||
| 162 | + | ||
| 163 | +**功能**:用户头像上传和设置 | ||
| 164 | + | ||
| 165 | +--- | ||
| 166 | + | ||
| 167 | +### 19. pages/message/index - 消息列表 | ||
| 168 | + | ||
| 169 | +**功能**:消息列表展示 | ||
| 170 | + | ||
| 171 | +**关键特性**: | ||
| 172 | +- 未读消息红点 | ||
| 173 | +- 消息分类 | ||
| 174 | + | ||
| 175 | +--- | ||
| 176 | + | ||
| 177 | +### 20. pages/message-detail/index - 消息详情 | ||
| 178 | + | ||
| 179 | +**功能**:消息详情展示 | ||
| 180 | + | ||
| 181 | +--- | ||
| 182 | + | ||
| 183 | +### 21. pages/feedback-list/index - 反馈列表 | ||
| 184 | + | ||
| 185 | +**功能**:意见反馈列表 | ||
| 186 | + | ||
| 187 | +--- | ||
| 188 | + | ||
| 189 | +### 22. pages/feedback/index - 用户反馈 | ||
| 190 | + | ||
| 191 | +**功能**:提交用户反馈 | ||
| 192 | + | ||
| 193 | +--- | ||
| 194 | + | ||
| 195 | +### 23. pages/login/index - 登录 | ||
| 196 | + | ||
| 197 | +**功能**:用户登录 | ||
| 198 | + | ||
| 199 | +**关键特性**: | ||
| 200 | +- 微信授权登录 | ||
| 201 | +- 登录状态检查 | ||
| 202 | + | ||
| 203 | +--- | ||
| 204 | + | ||
| 205 | +### 24. pages/help-center/index - 帮助中心 | ||
| 206 | + | ||
| 207 | +**功能**:帮助中心和常见问题 | ||
| 208 | + | ||
| 209 | +--- | ||
| 210 | + | ||
| 211 | +## 开发测试页面 | ||
| 212 | + | ||
| 213 | +### 25. pages/test-tabs/index - 标签页测试 | ||
| 214 | + | ||
| 215 | +**功能**:仅开发环境,用于测试标签页组件 | ||
| 216 | + | ||
| 217 | +--- | ||
| 218 | + | ||
| 219 | +## 页面注册 | ||
| 220 | + | ||
| 221 | +所有页面在 `src/app.config.js` 中注册: | ||
| 222 | + | ||
| 223 | +```javascript | ||
| 224 | +export default { | ||
| 225 | + pages: [ | ||
| 226 | + 'pages/index/index', | ||
| 227 | + 'pages/search/index', | ||
| 228 | + // ... 其他页面 | ||
| 229 | + ] | ||
| 230 | +} | ||
| 231 | +``` | ||
| 232 | + | ||
| 233 | +## 相关文档 | ||
| 234 | + | ||
| 235 | +- **[页面开发指南](guides/page-development.md)** - 如何添加新页面 | ||
| 236 | +- **[导航系统指南](guides/navigation.md)** - 导航和参数传递 |
-
Please register or login to post a comment