# 项目规则 - 信达小程序 (Xinda Mini Program) WeChat Mini Program for thyroid eye disease patient management. Dual-mode app serving both patients (患者端) and doctors (医生端). ## Project Type - **Framework**: WeChat Mini Program v3.7.7 - **Language**: TypeScript (strict mode) - **Styling**: SCSS/Sass via `useCompilerPlugins` - **UI Library**: Vant Weapp (@vant/weapp) - **Renderer**: Skyline enabled - **App ID**: `wxf9ce8010f1ad24aa` (dev), `wx71ac9c27c3c3e3f4` (prod) ## Directory Structure ``` src/ ├── pages/ # Doctor-side main pages (医生端) ├── patient/pages/ # Patient-side pages (subpackage) ├── gift/pages/ # DTP pharmacy pages (subpackage) ├── doc/pages/ # Privacy/terms documentation (subpackage) ├── components/ # Shared components ├── utils/ # Request, page/component wrappers, utilities ├── app.ts # App entry with global data └── config.ts # Environment configs per appId ``` ## Key Conventions ### Path Aliases - `@/*` → `src/*` (configured in tsconfig.json and app.json `resolveAlias`) - Use `@/utils/request` not relative paths ### User Types & Routing Login types enforced in `app.ts`: - `0`: Not logged in - `1`: Patient (患者) → routes to `/patient/pages/*` - `2`: Doctor (医生) → routes to `/pages/*` Use `app.zdWaitLogin()` to guard pages that require login. ### Page/Component Wrappers `app.ts` overrides global `Page` and `Component` with wrappers from `utils/page.ts` and `utils/component.ts`: - Auto-sets `imageUrl` and `Timestamp` on page load - Auto-handles navbar background on scroll - Provides default share behavior ### Modal Colors (Required) All `wx.showModal` must use: ```ts wx.showModal({ confirmColor: '#8c75d0', cancelColor: '#141515', }) ``` ### App Instance (Required) `getApp()` should be called at the top of the file, not inside methods: ```ts // ✅ Correct const app = getApp() Page({ onLoad() { // Use app directly console.log(app.globalData) } }) // ❌ Incorrect Page({ onLoad() { const app = getApp() console.log(app.globalData) } }) ``` ## WXML 开发规范 ### 1. 不支持在 WXML 中直接调用方法 **错误示例:** ```xml {{getUploadedCount()}} 内容 ``` **正确做法:** ```xml {{uploadedCount}} 内容 ``` ```typescript // 在 TS 中计算好数据 this.setData({ uploadedCount: photos.length, hasPhotos: photos.length > 0 }) ``` ### 2. 列表渲染时使用 wx:key ```xml {{item.name}} ``` ### 3. 条件渲染 ```xml 显示 隐藏 ``` ### 4. WXML 表达式限制 WXML 中的双大括号 `{{}}` 只能使用简单的表达式,**不支持**以下语法: **❌ 不支持的语法:** ```xml {{arr.indexOf(item) > -1}} {{obj.method().property}} {{str.match(/regex/)}} {{new Date().getTime()}} ``` **✅ 支持的语法:** ```xml {{item.name}} 内容 有数据 VIP用户 ``` **解决方案:** ```typescript // 在 TS 中预处理数据,将复杂计算转换为简单布尔值 this.setData({ // ❌ 不要在 WXML 中使用 indexOf // isSelected: selectedDates.indexOf(item.recordId) > -1 // ✅ 在数据中直接提供 isSelected 字段 list: rawList.map(item => ({ ...item, isSelected: selectedDates.includes(item.recordId) })) }) ``` ## Available Commands ```bash # Lint and auto-fix (only command available) npm run lint:fix # Install dependencies (pnpm preferred based on lockfile) pnpm install # Build: Use WeChat Developer Tools # - Import project with src/ as root # - project.config.json at repo root ``` ## NPM Dependencies **Critical**: After `npm install`, run **Tools → Build npm** in WeChat Developer Tools to generate `miniprogram_npm/`. This is required for Vant and other packages. Key dependencies: - `@vant/weapp`: UI components - `miniprogram-licia`: Utility library (available as `licia`) - `dayjs`: Date handling - `mp-html`: Rich HTML rendering ## Images & Assets **Images are stored in SVN, not git.** - SVN URL: `svn://39.106.86.127:28386/projects/xd/proj_src/shop/frontend/web/xd/` - Local path: `src/images/` (gitignored) - Excluded from upload via `project.config.json` packOptions - Only `/images/tabbar/*` is included in uploads Image URL pattern: ``` {{imageUrl}}/path/to/image.png?t={{Timestamp}} ``` ## TypeScript Configuration - Strict mode enabled - `noImplicitAny: false` (allows implicit any) - Paths: `@/*` mapped to `src/*` - Types: `miniprogram-api-typings` for WX API Global types in `typings/index.d.ts`: - `IAppOption`: Global app instance interface - `pageType`: 0 | 1 | 2 for user types - `wx.ajax`: Extended request method ## ESLint & Formatting - Config: `@antfu/eslint-config` (flat config in `eslint.config.js`) - Prettier: 2-space tabs, no semis, single quotes, trailing commas - WXML files parsed as HTML, WXSS as CSS - Globals defined: `wx`, `App`, `Page`, `Component`, `getCurrentPages`, etc. ## Environment Configuration Selected by App ID in `src/config.ts`: - `wxf9ce8010f1ad24aa`: Dev/Staging (hbraas.com) - `wx71ac9c27c3c3e3f4`: Production (hbsaas.com) App reads `wx.getAccountInfoSync().miniProgram.appId` on launch to select config. ## Testing & Development - No unit test framework configured - Manual testing via WeChat Developer Tools - `project.private.config.json` has hot reload enabled (`compileHotReLoad: true`) - Predefined test pages in `project.private.config.json` condition list ## Common Gotchas 1. **NPM packages**: Must run "Build npm" in WeChat tools after install 2. **Images**: Will 404 if SVN images not checked out to `src/images/` 3. **Subpackages**: Patient pages are in `patient/` subpackage, not main package 4. **Skyline**: Enabled in rendererOptions, some components may behave differently 5. **Login flow**: App automatically calls `startLogin()` on launch; pages must wait via `app.zdWaitLogin()` ## 自定义导航栏规范 ### 统一使用 navbar 组件 所有需要自定义导航的页面统一使用 `/components/navbar/index`。`/components/zd-navBar/navBar` 是历史遗留组件,新页面**不要使用**。 **JSON 配置:** ```json { "navigationStyle": "custom", "usingComponents": { "navbar": "/components/navbar/index" } } ``` **WXML 用法:** ```xml ``` **TS 实现:** ```typescript handleBack() { wx.navigateBack() } ``` ### 关键参数 | 属性 | 说明 | 常用值 | | -------------- | ------------ | --------------------------- | | `fixed` | 固定导航栏 | `fixed` | | `title` | 导航栏标题 | 字符串 | | `custom-style` | 自定义样式 | `background:{{background}}` | | `leftArrow` | 显示返回箭头 | Boolean | | `slot="left"` | 左侧插槽 | van-icon 返回按钮 | | `bind:tap` | 返回按钮点击 | `handleBack` | ### pageTop 变量 `pageTop` 由 `utils/page.ts` wrapper 自动注入,表示导航栏高度。页面内容区使用 `padding-top:{{pageTop+20}}px;` 避开导航栏。**不要手动计算 navBarHeight**。 ## 返回拦截与弹窗规范 ### 使用自定义弹窗替代系统弹窗 **禁止使用** `wx.showModal` 进行操作确认,统一使用项目内的 popup 组件: ```json { "usingComponents": { "popup": "/components/popup/index" } } ``` ```xml ``` ### 常用 popup 类型 | 类型 | 场景 | 标题 | 确认按钮 | 取消按钮 | | --------- | -------------- | ------------------ | -------- | -------- | | `popup15` | 删除确认 | "确认删除记录?" | 确认删除 | 取消 | | `popup16` | 未保存数据退出 | "您的记录还未保存" | 保存记录 | 退出 | | `popup17` | 裁剪未保存退出 | "您有裁剪的照片" | 退出 | 取消 | ### popup 数据定义 ```typescript data: { popupShow: false, popupType: 'popup16', popupParams: { close: false, position: 'center', } as any, } ``` ### 返回拦截模式 当页面有未保存数据时,使用自定义导航 + `bind:back` 拦截返回: ```typescript handleBack() { if (this.hasUnsavedData()) { this.setData({ popupShow: true, popupType: 'popup16' }) } else { wx.navigateBack() } } handlePopupOk() { this.setData({ popupShow: false }) this.handleSave() } handlePopupCancel() { this.setData({ popupShow: false }) wx.navigateBack() } ``` **注意**:`navigationStyle: "custom"` 后系统返回手势无法拦截,只有 navBar 的返回按钮可被拦截。如需拦截系统返回手势,需要额外使用 `wx.enableAlertBeforeUnload`。 ## wx.cropImage 规范 `wx.cropImage` 的 `src` 参数**只接受本地文件路径**,不支持网络 URL。裁剪网络图片时必须先用 `wx.getImageInfo` 下载到本地: ```typescript handleCrop(e: any) { const index = e.currentTarget.dataset.index const photo = this.data.photos[index] wx.showLoading({ title: '加载中...' }) wx.getImageInfo({ src: photo.photoUrl, success: (imgRes) => { wx.hideLoading() wx.cropImage({ src: imgRes.path, cropScale: '9:16', success: (cropRes) => { photos[index].croppedUrl = cropRes.tempFilePath this.setData({ photos }) }, fail: (err) => { if (err.errMsg?.includes('cancel')) return wx.showToast({ title: '裁剪取消', icon: 'none' }) }, }) }, fail: () => { wx.hideLoading() wx.showToast({ title: '图片加载失败', icon: 'none' }) }, }) } ``` ## 子包组件引用规范 主包页面(`src/pages/`)**不能引用**子包组件(`src/patient/components/`)。如果主包和子包都需要使用某个组件,需要将组件复制到 `src/components/` 目录下。 示例:`image-merge` 组件在 `patient/components/` 和 `components/` 各有一份,分别供子包和主包页面使用。