From b5f8e6ba787c16a8c3d8c86c61f14130a2393388 Mon Sep 17 00:00:00 2001 From: kola-web Date: Wed, 29 Apr 2026 18:46:37 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AA=81=E7=9C=BC=E6=97=A5=E8=AE=B0=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 134 ++++++++ project.config.json | 2 +- project.private.config.json | 79 ++++- src/app.json | 11 +- src/app.ts | 12 +- src/components/noteImagePreview/index.json | 6 + src/components/noteImagePreview/index.scss | 75 +++++ src/components/noteImagePreview/index.ts | 56 ++++ src/components/noteImagePreview/index.wxml | 12 + src/components/patient-tab-bar/index.scss | 31 ++ src/components/patient-tab-bar/index.ts | 16 +- src/components/patient-tab-bar/index.wxml | 7 + src/components/popup/index.scss | 148 ++++++++- src/components/popup/index.wxml | 24 ++ src/pages/d_noteDetail/index.json | 7 + src/pages/d_noteDetail/index.scss | 168 ++++++++++ src/pages/d_noteDetail/index.ts | 88 ++++++ src/pages/d_noteDetail/index.wxml | 74 +++++ src/pages/d_noteDiffData/index.json | 5 + src/pages/d_noteDiffData/index.scss | 148 +++++++++ src/pages/d_noteDiffData/index.ts | 25 ++ src/pages/d_noteDiffData/index.wxml | 35 ++ src/pages/d_noteList/index.json | 7 + src/pages/d_noteList/index.scss | 106 +++++++ src/pages/d_noteList/index.ts | 93 ++++++ src/pages/d_noteList/index.wxml | 21 ++ src/pages/d_patientDetail/index.scss | 96 ++++++ src/pages/d_patientDetail/index.ts | 10 + src/pages/d_patientDetail/index.wxml | 26 +- src/patient/components/camera/index.json | 5 +- src/patient/components/camera/index.scss | 456 +++++++++++++++++++++------ src/patient/components/camera/index.ts | 223 ++++++++++++- src/patient/components/camera/index.wxml | 127 ++++++-- src/patient/components/image-merge/index.ts | 69 ++-- src/patient/pages/changePhone/index.ts | 156 ++++----- src/patient/pages/imageProcessing/index.wxml | 4 +- src/patient/pages/index/index.scss | 1 + src/patient/pages/note/index.json | 7 + src/patient/pages/note/index.scss | 269 ++++++++++++++++ src/patient/pages/note/index.ts | 35 ++ src/patient/pages/note/index.wxml | 70 ++++ src/patient/pages/noteAdd/index.json | 10 + src/patient/pages/noteAdd/index.scss | 309 ++++++++++++++++++ src/patient/pages/noteAdd/index.ts | 132 ++++++++ src/patient/pages/noteAdd/index.wxml | 146 +++++++++ src/patient/pages/noteDemo/index.json | 7 + src/patient/pages/noteDemo/index.scss | 55 ++++ src/patient/pages/noteDemo/index.ts | 11 + src/patient/pages/noteDemo/index.wxml | 86 +++++ src/patient/pages/noteDiff/index.json | 7 + src/patient/pages/noteDiff/index.scss | 284 +++++++++++++++++ src/patient/pages/noteDiff/index.ts | 13 + src/patient/pages/noteDiff/index.wxml | 85 +++++ src/patient/pages/noteDiffEdit/index.json | 8 + src/patient/pages/noteDiffEdit/index.scss | 157 +++++++++ src/patient/pages/noteDiffEdit/index.ts | 19 ++ src/patient/pages/noteDiffEdit/index.wxml | 40 +++ src/patient/pages/noteHistory/index.json | 8 + src/patient/pages/noteHistory/index.scss | 183 +++++++++++ src/patient/pages/noteHistory/index.ts | 94 ++++++ src/patient/pages/noteHistory/index.wxml | 98 ++++++ src/utils/captcha.ts | 220 +++++++++++++ 62 files changed, 4637 insertions(+), 279 deletions(-) create mode 100644 AGENTS.md create mode 100644 src/components/noteImagePreview/index.json create mode 100644 src/components/noteImagePreview/index.scss create mode 100644 src/components/noteImagePreview/index.ts create mode 100644 src/components/noteImagePreview/index.wxml create mode 100644 src/pages/d_noteDetail/index.json create mode 100644 src/pages/d_noteDetail/index.scss create mode 100644 src/pages/d_noteDetail/index.ts create mode 100644 src/pages/d_noteDetail/index.wxml create mode 100644 src/pages/d_noteDiffData/index.json create mode 100644 src/pages/d_noteDiffData/index.scss create mode 100644 src/pages/d_noteDiffData/index.ts create mode 100644 src/pages/d_noteDiffData/index.wxml create mode 100644 src/pages/d_noteList/index.json create mode 100644 src/pages/d_noteList/index.scss create mode 100644 src/pages/d_noteList/index.ts create mode 100644 src/pages/d_noteList/index.wxml create mode 100644 src/patient/pages/note/index.json create mode 100644 src/patient/pages/note/index.scss create mode 100644 src/patient/pages/note/index.ts create mode 100644 src/patient/pages/note/index.wxml create mode 100644 src/patient/pages/noteAdd/index.json create mode 100644 src/patient/pages/noteAdd/index.scss create mode 100644 src/patient/pages/noteAdd/index.ts create mode 100644 src/patient/pages/noteAdd/index.wxml create mode 100644 src/patient/pages/noteDemo/index.json create mode 100644 src/patient/pages/noteDemo/index.scss create mode 100644 src/patient/pages/noteDemo/index.ts create mode 100644 src/patient/pages/noteDemo/index.wxml create mode 100644 src/patient/pages/noteDiff/index.json create mode 100644 src/patient/pages/noteDiff/index.scss create mode 100644 src/patient/pages/noteDiff/index.ts create mode 100644 src/patient/pages/noteDiff/index.wxml create mode 100644 src/patient/pages/noteDiffEdit/index.json create mode 100644 src/patient/pages/noteDiffEdit/index.scss create mode 100644 src/patient/pages/noteDiffEdit/index.ts create mode 100644 src/patient/pages/noteDiffEdit/index.wxml create mode 100644 src/patient/pages/noteHistory/index.json create mode 100644 src/patient/pages/noteHistory/index.scss create mode 100644 src/patient/pages/noteHistory/index.ts create mode 100644 src/patient/pages/noteHistory/index.wxml create mode 100644 src/utils/captcha.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..859e23e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,134 @@ +# AGENTS.md - 信达小程序 (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', +}) +``` + +## 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()` diff --git a/project.config.json b/project.config.json index 1d8faf4..625d498 100644 --- a/project.config.json +++ b/project.config.json @@ -71,5 +71,5 @@ } ] }, - "appid": "wx71ac9c27c3c3e3f4" + "appid": "wxf9ce8010f1ad24aa" } \ No newline at end of file diff --git a/project.private.config.json b/project.private.config.json index 45f583a..a931232 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -23,29 +23,92 @@ "miniprogram": { "list": [ { - "name": "patient/pages/imageProcessing/index", - "pathName": "patient/pages/imageProcessing/index", + "name": "医生端-眼突度-对比", + "pathName": "pages/d_noteDiffData/index", "query": "", "scene": null, "launchMode": "default" }, { - "name": "患者-qol", - "pathName": "patient/pages/qol/index", - "query": "pushId=81", + "name": "医生端-日记列表", + "pathName": "pages/d_noteList/index", + "query": "", "launchMode": "default", "scene": null }, { - "name": "医生-患者量表", - "pathName": "pages/d_qolDetail/index", - "query": "id=178", + "name": "医生-突眼日记单条记录", + "pathName": "pages/d_noteDetail/index", + "query": "", "launchMode": "default", "scene": null }, { "name": "医生-患者详情", "pathName": "pages/d_patientDetail/index", + "query": "id=319", + "launchMode": "default", + "scene": null + }, + { + "name": "突眼日记照片对比编辑", + "pathName": "patient/pages/noteDiffEdit/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "眼突日记照片对比", + "pathName": "patient/pages/noteDiff/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "突眼日记-历史记录", + "pathName": "patient/pages/noteHistory/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "突眼日记拍照示例", + "pathName": "patient/pages/noteDemo/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "突眼日记添加", + "pathName": "patient/pages/noteAdd/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "突眼日记", + "pathName": "patient/pages/note/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "patient/pages/imageProcessing/index", + "pathName": "patient/pages/imageProcessing/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "患者-qol", + "pathName": "patient/pages/qol/index", + "query": "pushId=81", + "launchMode": "default", + "scene": null + }, + { + "name": "医生-患者量表", + "pathName": "pages/d_qolDetail/index", "query": "id=178", "launchMode": "default", "scene": null diff --git a/src/app.json b/src/app.json index 2fcae83..0027fa5 100644 --- a/src/app.json +++ b/src/app.json @@ -16,7 +16,10 @@ "pages/d_invite/index", "pages/d_patient/index", "pages/d_patientHormones/index", - "pages/d_qolDetail/index" + "pages/d_qolDetail/index", + "pages/d_noteDetail/index", + "pages/d_noteList/index", + "pages/d_noteDiffData/index" ], "subPackages": [ { @@ -72,6 +75,12 @@ "pages/hormonesResult/index", "pages/medical/index", "pages/medicalDetail/index", + "pages/note/index", + "pages/noteAdd/index", + "pages/noteDemo/index", + "pages/noteHistory/index", + "pages/noteDiff/index", + "pages/noteDiffEdit/index", "pages/imageProcessing/index" ] }, diff --git a/src/app.ts b/src/app.ts index d095bd7..cbd248d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -18,15 +18,15 @@ App({ // 测试号 wx2b0bb13edf717c1d // dev // appid:wxf9ce8010f1ad24aa - // url: 'https://m.xd.hbraas.com', - // upFileUrl: 'https://m.xd.hbraas.com/', - // imageUrl: 'https://m.xd.hbraas.com/xd/', + url: 'https://m.xd.hbraas.com', + upFileUrl: 'https://m.xd.hbraas.com/', + imageUrl: 'https://m.xd.hbraas.com/xd/', // pro // appid:wx71ac9c27c3c3e3f4 - url: 'https://m.xd.hbsaas.com', - upFileUrl: 'https://m.xd.hbsaas.com/', - imageUrl: 'https://m.xd.hbsaas.com/api/xd/', + // url: 'https://m.xd.hbsaas.com', + // upFileUrl: 'https://m.xd.hbsaas.com/', + // imageUrl: 'https://m.xd.hbsaas.com/api/xd/', loginState: '', isLogin: 0, diff --git a/src/components/noteImagePreview/index.json b/src/components/noteImagePreview/index.json new file mode 100644 index 0000000..6f50345 --- /dev/null +++ b/src/components/noteImagePreview/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + } +} \ No newline at end of file diff --git a/src/components/noteImagePreview/index.scss b/src/components/noteImagePreview/index.scss new file mode 100644 index 0000000..5736736 --- /dev/null +++ b/src/components/noteImagePreview/index.scss @@ -0,0 +1,75 @@ +.preview-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #000; + z-index: 1000; + display: flex; + flex-direction: column; + opacity: 0; + visibility: hidden; + transition: + opacity 0.3s, + visibility 0.3s; + + &.show { + opacity: 1; + visibility: visible; + } +} + +// 图片展示区域 +.image-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + + .preview-image { + display: block; + width: 100%; + height: 100%; + } +} + +// 底部操作栏 +.action-bar { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 40rpx; + padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); + background: #fff; + display: flex; + gap: 32rpx; + + .btn { + flex: 1; + height: 88rpx; + border-radius: 44rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 500; + + &-delete { + border: 1px solid #b982ff; + color: #b982ff; + background: #fff; + } + + &-retake { + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + color: #fff; + } + + &:active { + opacity: 0.8; + } + } +} diff --git a/src/components/noteImagePreview/index.ts b/src/components/noteImagePreview/index.ts new file mode 100644 index 0000000..a5cddab --- /dev/null +++ b/src/components/noteImagePreview/index.ts @@ -0,0 +1,56 @@ +Component({ + properties: {}, + + data: { + visible: false, + src: '', + }, + + methods: { + // 返回/关闭 + handleBack() { + this.triggerEvent('close') + }, + + // 更多操作 + handleMore() { + this.triggerEvent('more') + }, + + // 预览/查看 + handlePreview(src) { + if (src) { + this.setData({ + visible: true, + src, + }) + } + }, + handleHidePreview() { + this.setData({ + visible: false, + sec: '', + }) + }, + + // 删除 + handleDelete() { + wx.showModal({ + title: '提示', + content: '确定要删除这张照片吗?', + success: (res) => { + if (res.confirm) { + this.handleHidePreview() + this.triggerEvent('delete') + } + }, + }) + }, + + // 重拍 + handleRetake() { + this.handleHidePreview() + this.triggerEvent('retake') + }, + }, +}) diff --git a/src/components/noteImagePreview/index.wxml b/src/components/noteImagePreview/index.wxml new file mode 100644 index 0000000..9ceeed7 --- /dev/null +++ b/src/components/noteImagePreview/index.wxml @@ -0,0 +1,12 @@ + + + + + + + + + 删除 + 重拍 + + diff --git a/src/components/patient-tab-bar/index.scss b/src/components/patient-tab-bar/index.scss index 8dd17c9..68a50e2 100644 --- a/src/components/patient-tab-bar/index.scss +++ b/src/components/patient-tab-bar/index.scss @@ -1,6 +1,37 @@ /* custom-tab-bar/index.wxss */ +.tab-custom-item { + flex: 1; + z-index: 1; + .circle { + display: block; + margin: 0 auto; + margin-top: -53rpx; + width: 100rpx; + height: 100rpx; + border-radius: 50%; + border: 3px solid #fff; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + display: flex; + align-items: center; + justify-content: center; + .icon { + width: 60rpx; + height: 60rpx; + } + } + .name { + font-size: 26rpx; + color: rgba(105, 104, 110, 1); + text-align: center; + &.active { + color: rgba(33, 29, 46, 1); + } + } +} + .tab-item { + flex: 1; .icon { width: 48rpx; height: 48rpx; diff --git a/src/components/patient-tab-bar/index.ts b/src/components/patient-tab-bar/index.ts index e8f11ac..eafc814 100644 --- a/src/components/patient-tab-bar/index.ts +++ b/src/components/patient-tab-bar/index.ts @@ -1,4 +1,5 @@ import { getCurrentPageUrl } from '@/utils/util' + const app = getApp() Component({ @@ -21,6 +22,11 @@ Component({ iconActive: 'tab-active2', }, { + pagePath: '/patient/pages/note/index', + text: '突眼日记', + custom: true, + }, + { pagePath: '/patient/pages/live/index', text: '大咖说', icon: 'tab5', @@ -48,7 +54,7 @@ Component({ }) const pagePath = getCurrentPageUrl() - const active = this.data.list.findIndex((item) => item.pagePath === pagePath) + const active = this.data.list.findIndex(item => item.pagePath === pagePath) this.setData({ active, }) @@ -72,8 +78,14 @@ Component({ handleNav(e) { const { index } = e.currentTarget.dataset const { list } = this.data - const pagePath = list[index].pagePath + const { pagePath, custom } = list[index] app.globalData.BeginnerCardId = '' + if (custom) { + wx.navigateTo({ + url: pagePath, + }) + return + } wx.reLaunch({ url: pagePath, }) diff --git a/src/components/patient-tab-bar/index.wxml b/src/components/patient-tab-bar/index.wxml index ca5a1ce..a8c9b78 100644 --- a/src/components/patient-tab-bar/index.wxml +++ b/src/components/patient-tab-bar/index.wxml @@ -1,6 +1,13 @@ + + + + + 突眼日记 + 继续 + + + + 确认删除记录? + + 删除 + 2026-04-02 + 记录 + 此操作不可逆,相关照片将永久删除 + + 确认删除 + 取消 + + + + + + 您的记录还未保存 + + 退出 + 保存记录 + + + () + +Page({ + data: { + history: { + frontend: [ + { + title: '正面睁眼照', + content: '平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。', + }, + { + title: '正面闭眼照', + content: '正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。', + }, + { + title: '正面仰头照', + content: '拍摄时,正对镜头,面部居中,头部自然向上仰至约 45°,双眼同步平视,保持自然睁眼、不眯眼。', + }, + ], + backend: [ + { + title: '左侧-90°', + content: '身体与头部完全转向右侧,呈标准 90° 侧面,仅可见左侧眼睛。', + }, + { + title: '右侧-90°', + content: '身体与头部完全转向左侧,呈标准 90° 侧面,仅可见右侧眼睛。', + }, + { + title: '左侧-45°', + content: '身体与头部转向右前方 45°。', + }, + { + title: '右侧-45°', + content: '身体与头部转向左前方 45°', + }, + ], + other: [ + { + title: '正面眼睛上看', + content: '', + }, + { + title: '正面眼睛下看', + content: '', + }, + { + title: '正面眼睛左看', + content: '', + }, + { + title: '正面眼睛右看', + content: '', + }, + { + title: '正面眼睛左上看', + content: '', + }, + { + title: '正面眼睛右上看', + content: '', + }, + { + title: '正面眼睛左下看', + content: '', + }, + { + title: '正面眼睛右下看', + content: '', + }, + ], + }, + }, + onLoad() {}, + + handlePopupOk() { + this.setData({ + popupShow: false, + }) + }, + handlePopupCancel() { + this.setData({ + popupShow: false, + }) + }, +}) + +export {} diff --git a/src/pages/d_noteDetail/index.wxml b/src/pages/d_noteDetail/index.wxml new file mode 100644 index 0000000..39f68c4 --- /dev/null +++ b/src/pages/d_noteDetail/index.wxml @@ -0,0 +1,74 @@ + + + + + + + 基准照 + 2026-04-02 + + 当前记录对应的替妥尤单抗使用次数: + 1 + + + + 正面 + + + + {{item.title}} + + + + + 侧面 + + + + {{item.title}} + + + + + 眼球运动八个方向 + + + + {{item.title}} + + + + + + 眼突度 对比 + 照片 对比 + + diff --git a/src/pages/d_noteDiffData/index.json b/src/pages/d_noteDiffData/index.json new file mode 100644 index 0000000..e5328d3 --- /dev/null +++ b/src/pages/d_noteDiffData/index.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "xxx的眼突度对比", + "navigationStyle": "default", + "usingComponents": {} +} diff --git a/src/pages/d_noteDiffData/index.scss b/src/pages/d_noteDiffData/index.scss new file mode 100644 index 0000000..df0c378 --- /dev/null +++ b/src/pages/d_noteDiffData/index.scss @@ -0,0 +1,148 @@ +.container { + min-height: 100vh; + background-color: #f6f8f9; + padding: 16rpx 30rpx; + box-sizing: border-box; +} + +.table-wrapper { + background-color: #eee2ff; + border-radius: 13rpx; + border: 1rpx solid #fff; + overflow: hidden; +} + +/* 表头样式 */ +.table-header { + display: flex; + background-color: #f7f0ff; + + &.sticky { + position: sticky; + top: 0; + z-index: 100; + } +} + +.th { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #b982ff; + font-size: 28rpx; + font-weight: 500; + box-sizing: border-box; +} + +.th-date { + width: 200rpx; + height: 120rpx; + border-right: 1rpx solid #eee4ff; + justify-content: flex-end; + padding-bottom: 20rpx; +} + +.th-eye { + flex: 1; + padding: 0; + border-right: 1rpx solid #eee4ff; +} + +.th-eye-title { + text-align: center; + padding: 16rpx 0; + border-bottom: 1rpx solid #eee4ff; + width: 100%; + font-size: 28rpx; + height: 60rpx; + box-sizing: border-box; +} + +.th-eye-sub { + display: flex; + width: 100%; + height: 60rpx; +} + +.th-sub-item { + flex: 1; + text-align: center; + border-right: 1rpx solid #eee4ff; + font-size: 28rpx; + display: flex; + align-items: center; + justify-content: center; + + &:last-child { + border-right: none; + } +} + +.th-count { + width: 120rpx; + text-align: center; + line-height: 1.3; + font-size: 28rpx; + height: 120rpx; + justify-content: center; +} + +/* 表格内容样式 */ +.table-body { + background-color: #eee2ff; +} + +.tr { + display: flex; + height: 100rpx; + + &:last-child { + .td-date, + .td-eye, + .td-count { + border-bottom: none; + } + } +} + +.td { + display: flex; + align-items: center; + justify-content: center; + color: #211d2e; + font-size: 32rpx; + font-weight: 500; + box-sizing: border-box; + height: 100rpx; +} + +.td-date { + width: 200rpx; + background-color: #ffffff; + border-right: 1rpx solid #eee4ff; + border-bottom: 1rpx solid #eee4ff; +} + +.td-eye { + flex: 1; + display: flex; + background-color: #fff; + border-bottom: 1rpx solid #eee4ff; +} + +.td-eye-item { + flex: 1; + text-align: center; + border-right: 1rpx solid #eee4ff; + display: flex; + align-items: center; + justify-content: center; + height: 100rpx; +} + +.td-count { + width: 120rpx; + background-color: #ffffff; + border-bottom: 1rpx solid #eee4ff; +} diff --git a/src/pages/d_noteDiffData/index.ts b/src/pages/d_noteDiffData/index.ts new file mode 100644 index 0000000..288d544 --- /dev/null +++ b/src/pages/d_noteDiffData/index.ts @@ -0,0 +1,25 @@ +const _app = getApp(); + +Page({ + data: { + dataList: [ + { + date: '2026.1.5', + left: '40', + spacing: '40', + right: '40', + count: '30', + }, + { + date: '2025.12.30', + left: '30', + spacing: '30', + right: '30', + count: '20', + }, + ], + }, + onLoad() {}, +}); + +export {}; diff --git a/src/pages/d_noteDiffData/index.wxml b/src/pages/d_noteDiffData/index.wxml new file mode 100644 index 0000000..2cfff38 --- /dev/null +++ b/src/pages/d_noteDiffData/index.wxml @@ -0,0 +1,35 @@ + + + + + + 日期 + + 眼突度(mm) + + 左侧 + 眶间距 + 右侧 + + + + 替妥尤 + 输注 + 次数 + + + + + + + {{item.date}} + + {{item.left}} + {{item.spacing}} + {{item.right}} + + {{item.count}} + + + + diff --git a/src/pages/d_noteList/index.json b/src/pages/d_noteList/index.json new file mode 100644 index 0000000..fd686d5 --- /dev/null +++ b/src/pages/d_noteList/index.json @@ -0,0 +1,7 @@ +{ + "navigationStyle": "default", + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + }, + "navigationBarTitleText": "记录" +} diff --git a/src/pages/d_noteList/index.scss b/src/pages/d_noteList/index.scss new file mode 100644 index 0000000..e33bff5 --- /dev/null +++ b/src/pages/d_noteList/index.scss @@ -0,0 +1,106 @@ +page { + background-color: #f6f8f9; +} +.page { + .total { + padding: 32rpx 40rpx; + font-size: 32rpx; + color: #211d2e; + } + .history-list { + padding: 0 40rpx 240rpx; + .list-item { + margin-bottom: 24rpx; + position: relative; + border-radius: 32rpx; + background-color: #fff; + padding: 32rpx; + display: flex; + justify-content: space-between; + gap: 24rpx; + .benchmark { + position: absolute; + top: 0; + right: 0; + padding-top: 8rpx; + text-align: center; + width: 124rpx; + height: 64rpx; + font-size: 28rpx; + color: #ffa300; + line-height: 32rpx; + } + .photo { + width: 140rpx; + height: 140rpx; + border-radius: 16rpx; + } + .wrap { + flex: 1; + .date { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .tag { + margin-top: 16rpx; + padding: 4rpx 12rpx; + display: inline-block; + font-size: 24rpx; + color: #b073ff; + background: rgba(176, 115, 255, 0.16); + border-radius: 6rpx 6rpx 6rpx 6rpx; + } + .rotate { + margin-top: 28rpx; + font-size: 28rpx; + color: #adacb2; + line-height: 1; + } + } + .more { + align-self: center; + width: 44rpx; + height: 44rpx; + } + } + } + + .footer { + position: fixed; + bottom: 0; + left: 0; + padding: 20rpx 30rpx 60rpx; + width: 100%; + box-sizing: border-box; + margin-top: 46rpx; + display: flex; + align-items: center; + gap: 22rpx; + background-color: #fff; + box-shadow: 0 -10rpx 10rpx rgba(204, 204, 204, 0.1); + .btn1 { + flex: 1; + height: 88rpx; + font-size: 36rpx; + color: #b982ff; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #b982ff; + background-color: #fff; + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + .btn2 { + flex: 1; + height: 88rpx; + font-size: 36rpx; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + } +} diff --git a/src/pages/d_noteList/index.ts b/src/pages/d_noteList/index.ts new file mode 100644 index 0000000..1d1b3d7 --- /dev/null +++ b/src/pages/d_noteList/index.ts @@ -0,0 +1,93 @@ +const _app = getApp() + +Page({ + data: { + history: { + frontend: [ + { + title: '正面睁眼照', + content: '平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。', + }, + { + title: '正面闭眼照', + content: '正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。', + }, + { + title: '正面仰头照', + content: '拍摄时,正对镜头,面部居中,头部自然向上仰至约 45°,双眼同步平视,保持自然睁眼、不眯眼。', + }, + ], + backend: [ + { + title: '左侧-90°', + content: '身体与头部完全转向右侧,呈标准 90° 侧面,仅可见左侧眼睛。', + }, + { + title: '右侧-90°', + content: '身体与头部完全转向左侧,呈标准 90° 侧面,仅可见右侧眼睛。', + }, + { + title: '左侧-45°', + content: '身体与头部转向右前方 45°。', + }, + { + title: '右侧-45°', + content: '身体与头部转向左前方 45°', + }, + ], + other: [ + { + title: '正面眼睛上看', + content: '', + }, + { + title: '正面眼睛下看', + content: '', + }, + { + title: '正面眼睛左看', + content: '', + }, + { + title: '正面眼睛右看', + content: '', + }, + { + title: '正面眼睛左上看', + content: '', + }, + { + title: '正面眼睛右上看', + content: '', + }, + { + title: '正面眼睛左下看', + content: '', + }, + { + title: '正面眼睛右下看', + content: '', + }, + ], + }, + }, + onLoad() {}, + handleDiffData() { + wx.navigateTo({ + url: '/pages/d_noteDiffData/index', + }) + }, + + handlePopupOk() { + this.setData({ + popupShow: false, + }) + }, + handlePopupCancel() { + this.setData({ + popupShow: false, + }) + }, +}) + +export {} diff --git a/src/pages/d_noteList/index.wxml b/src/pages/d_noteList/index.wxml new file mode 100644 index 0000000..6828ad0 --- /dev/null +++ b/src/pages/d_noteList/index.wxml @@ -0,0 +1,21 @@ + + 共X条日记记录 + + + + 基准照 + + + + 2026-04-01 + 替妥尤单抗:2 + 已上传1个角度 + + + + + + 眼突度 对比 + 照片 对比 + + diff --git a/src/pages/d_patientDetail/index.scss b/src/pages/d_patientDetail/index.scss index 2bf91a9..ee425da 100644 --- a/src/pages/d_patientDetail/index.scss +++ b/src/pages/d_patientDetail/index.scss @@ -142,6 +142,102 @@ page { font-weight: bold; } } + .note { + margin: 48rpx 30rpx 0; + .n-title { + display: flex; + align-items: center; + gap: 12rpx; + font-size: 36rpx; + color: #211d2e; + font-weight: bold; + &::before { + content: ''; + width: 8rpx; + height: 36rpx; + background: #b982ff; + border-radius: 2rpx; + } + .sub { + align-self: flex-end; + font-size: 32rpx; + color: #adacb2; + font-weight: normal; + } + } + .n-container { + margin-top: 24rpx; + padding: 32rpx; + background: #ffffff; + border-radius: 32rpx 32rpx 32rpx 32rpx; + .n-card { + position: relative; + margin-top: 24rpx; + border-radius: 32rpx; + background-color: #f6f8f9; + padding: 24rpx; + display: flex; + justify-content: space-between; + gap: 24rpx; + .benchmark { + position: absolute; + top: 0; + right: 0; + padding-top: 8rpx; + text-align: center; + width: 124rpx; + height: 64rpx; + font-size: 28rpx; + color: #fff; + line-height: 28rpx; + } + .photo { + width: 140rpx; + height: 140rpx; + border-radius: 16rpx; + } + .wrap { + flex: 1; + .date { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .tag { + margin-top: 16rpx; + padding: 4rpx 12rpx; + display: inline-block; + font-size: 24rpx; + color: #b073ff; + background: rgba(176, 115, 255, 0.16); + border-radius: 6rpx 6rpx 6rpx 6rpx; + } + .rotate { + margin-top: 28rpx; + font-size: 28rpx; + color: #adacb2; + line-height: 1; + } + } + .more { + align-self: center; + width: 44rpx; + height: 44rpx; + } + } + .btn { + margin-top: 24rpx; + height: 76rpx; + font-size: 32rpx; + color: #FFFFFF; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(270deg, #e98ff8 0%, #b073ff 100%); + border-radius: 72rpx 72rpx 72rpx 72rpx; + } + } + } .kkd { margin: 48rpx 30rpx 0; .k-title { diff --git a/src/pages/d_patientDetail/index.ts b/src/pages/d_patientDetail/index.ts index b2df3ab..0b5b305 100644 --- a/src/pages/d_patientDetail/index.ts +++ b/src/pages/d_patientDetail/index.ts @@ -892,4 +892,14 @@ Page({ url: `/pages/d_qolDetail/index?id=${this.data.detail.PatientId}`, }) }, + handleNoteDetail() { + wx.navigateTo({ + url: '/pages/d_noteDetail/index', + }) + }, + handleNoteList() { + wx.navigateTo({ + url: '/pages/d_noteList/index', + }) + }, }) diff --git a/src/pages/d_patientDetail/index.wxml b/src/pages/d_patientDetail/index.wxml index 14f0ca4..441579f 100644 --- a/src/pages/d_patientDetail/index.wxml +++ b/src/pages/d_patientDetail/index.wxml @@ -72,9 +72,33 @@ 标识为EDC患者 + + + 突眼日记 + (可查看患者突眼、眼突度) + + + + + 基准照 + + + + 2026-04-01 + 替妥尤单抗:2 + 已上传1个角度 + + + + 共3条日记记录,点击查看全部 + + - 患者健康图表 + 健康量表 最近数值 diff --git a/src/patient/components/camera/index.json b/src/patient/components/camera/index.json index 33ea64d..84b14ac 100644 --- a/src/patient/components/camera/index.json +++ b/src/patient/components/camera/index.json @@ -2,6 +2,7 @@ "component": true, "usingComponents": { "van-icon": "@vant/weapp/icon/index", - "van-button": "@vant/weapp/button/index" + "van-button": "@vant/weapp/button/index", + "van-popup": "@vant/weapp/popup/index" } -} \ No newline at end of file +} diff --git a/src/patient/components/camera/index.scss b/src/patient/components/camera/index.scss index db97bcc..01dad23 100644 --- a/src/patient/components/camera/index.scss +++ b/src/patient/components/camera/index.scss @@ -9,130 +9,380 @@ flex-direction: column; background-color: #000; z-index: 1000; -} + .camera-container { + width: 100%; + height: 100%; + } -.camera { - flex: 1; - width: 100%; - position: relative; -} + .camera { + flex: 1; + width: 100%; + position: relative; + text-align: center; + .example { + position: relative; + margin: 120rpx 32rpx 0; + width: 202rpx; + height: 56rpx; + font-size: 32rpx; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.33); + border-radius: 124rpx 124rpx 124rpx 124rpx; + .icon { + width: 36rpx; + height: 36rpx; + } + &.active { + background: linear-gradient(180deg, #e98ff8 0%, #b073ff 100%); + } + .example-popup { + padding: 32rpx; + position: absolute; + left: 8rpx; + bottom: -32rpx; + width: 670rpx; + transform: translateY(100%); + background-color: #fff; + border-radius: 32rpx; + box-sizing: border-box; + .content { + text-align: left; + font-size: 36rpx; + color: #69686e; + line-height: 48rpx; + } + .photo-wrap { + position: relative; + .label { + position: absolute; + top: 20rpx; + right: -20rpx; + width: 110rpx; + height: 78rpx; + } + .photo { + margin-top: 32rpx; + width: 606rpx; + max-height: 304rpx; + } + } + &::before { + content: ''; + position: absolute; + top: -20rpx; + left: 62rpx; + width: 0; + height: 0; + border-style: solid; + border-width: 0 29rpx 50rpx 29rpx; + border-color: transparent transparent #fff transparent; + } + } + } + .camera-frame { + position: relative; + display: block; + margin: 38rpx auto 0; + height: 50vh; + aspect-ratio: 4/5; + pointer-events: none; -.camera-frame { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 80%; - aspect-ratio: 3 / 4; - pointer-events: none; - border: 2rpx solid rgba(255, 255, 255, 0.3); -} + // 公共 icon 基础样式 + %icon-base { + position: absolute; + width: 40rpx; + height: 105rpx; + } -.frame-corner { - position: absolute; - width: 40rpx; - height: 40rpx; - border: 4rpx solid #fff; -} + .icon8 { + @extend %icon-base; + top: 11.5vh; + left: 4vh; + transform: rotate(-45deg); + animation: 1s ease-in-out infinite slidein8; + } -.left-top { - top: 0; - left: 0; - border-right: none; - border-bottom: none; -} + .icon9-1 { + @extend %icon-base; + top: 9vh; + left: 10.7vh; + transform: rotate(0); + animation: 1s ease-in-out infinite slidein9; + } -.right-top { - top: 0; - right: 0; - border-left: none; - border-bottom: none; -} + .icon9-2 { + @extend %icon-base; + top: 9vh; + right: 10.7vh; + transform: rotate(0); + animation: 1s ease-in-out infinite slidein9; + } -.left-bottom { - bottom: 0; - left: 0; - border-right: none; - border-top: none; + .icon10 { + @extend %icon-base; + top: 22.5vh; + left: 4vh; + transform: rotate(-135deg); + animation: 1s ease-in-out infinite slidein10; + } + + .icon11 { + @extend %icon-base; + top: 17vh; + left: 4vh; + transform: rotate(-90deg); + animation: 1s ease-in-out infinite slidein11; + } + + .icon12 { + @extend %icon-base; + top: 17vh; + right: 4vh; + transform: rotate(90deg); + animation: 1s ease-in-out infinite slidein12; + } + + .icon13 { + @extend %icon-base; + top: 12vh; + right: 4vh; + transform: rotate(45deg); + animation: 1s ease-in-out infinite slidein13; + } + + .icon14-1 { + @extend %icon-base; + top: 25vh; + left: 10.7vh; + transform: rotate(180deg); + animation: 1s ease-in-out infinite slidein14; + } + + .icon14-2 { + @extend %icon-base; + top: 25vh; + right: 10.7vh; + transform: rotate(180deg); + animation: 1s ease-in-out infinite slidein14; + } + + .icon15 { + @extend %icon-base; + top: 22vh; + right: 4vh; + transform: rotate(135deg); + animation: 1s ease-in-out infinite slidein15; + } + } + .tip { + margin: 28rpx auto 0; + padding: 0 32rpx; + font-size: 32rpx; + color: #ffffff; + display: inline-block; + line-height: 56rpx; + background: rgba(0, 0, 0, 0.33); + border-radius: 124rpx 124rpx 124rpx 124rpx; + } + .order { + margin: 32rpx auto 0; + padding: 18rpx 48rpx; + font-size: 40rpx; + font-weight: bold; + text-align: center; + color: #211d2e; + border-radius: 94rpx; + display: inline-flex; + align-items: baseline; + background-color: #fff; + .num { + margin-left: 20rpx; + } + .m-num { + font-size: 28rpx; + } + } + } + + .controls { + position: fixed; + bottom: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 80rpx; + padding: 32rpx 114rpx calc(env(safe-area-inset-bottom) + 20rpx); + background: rgba(255, 255, 255, 0.88); + border-radius: 32rpx 32rpx 0 0; + .switch-btn { + width: 120rpx; + .icon { + width: 68rpx; + height: 68rpx; + } + .name { + font-size: 32rpx; + color: #adacb2; + } + } + .control-btn { + flex-shrink: 0; + width: 160rpx; + height: 160rpx; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + box-shadow: 0rpx 8rpx 47rpx 0rpx rgba(69, 0, 157, 0.15); + border-radius: 96rpx 96rpx 96rpx 96rpx; + .icon { + width: 48rpx; + height: 48rpx; + } + } + .block { + width: 120rpx; + } + } } -.right-bottom { - bottom: 0; - right: 0; - border-left: none; - border-top: none; +.popup-container { + width: 100vw; + box-sizing: border-box; + padding: 0 48rpx 132rpx; + border-radius: 32rpx; + background: #fff; + box-sizing: border-box; + background: linear-gradient(360deg, #f1e6ff 0%, #f6f8f9 49.07%, #f6f8f9 100%); + .title { + padding: 32rpx 0; + font-size: 36rpx; + color: #211d2e; + font-weight: bold; + display: flex; + justify-content: space-between; + align-items: center; + } + .select { + margin-top: 32rpx; + display: flex; + align-items: center; + gap: 34rpx; + .item { + flex: 1; + padding: 64rpx; + text-align: center; + background: #ffffff; + border-radius: 24rpx 24rpx 24rpx 24rpx; + .icon { + width: 116rpx; + height: 116rpx; + } + .name { + font-size: 36rpx; + color: #211d2e; + } + } + } } -.close-btn { - position: fixed; - top: 40rpx; - right: 40rpx; - width: 60rpx; - height: 60rpx; - display: flex; - align-items: center; - justify-content: center; - background-color: rgba(0, 0, 0, 0.5); - border-radius: 50%; - z-index: 10; +@keyframes slidein8 { + 0% { + transform: translate(0, 0) rotate(-45deg); + } + 50% { + transform: translate(-20rpx, -20rpx) rotate(-45deg); + } + 100% { + transform: translate(0, 0) rotate(-45deg); + } } -.controls { - position: fixed; - bottom: 80rpx; - left: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - gap: 80rpx; - padding: 30rpx 0; +@keyframes slidein9 { + 0% { + transform: translate(0, 0) rotate(0); + } + 50% { + transform: translate(0, -20rpx) rotate(0); + } + 100% { + transform: translate(0, 0) rotate(0); + } } -.control-btn { - display: flex; - align-items: center; - justify-content: center; - width: 80rpx; - height: 80rpx; - border-radius: 50%; - background-color: rgba(255, 255, 255, 0.2); +@keyframes slidein10 { + 0% { + transform: translate(0, 0) rotate(-135deg); + } + 50% { + transform: translate(20rpx, -20rpx) rotate(-135deg); + } + 100% { + transform: translate(0, 0) rotate(-135deg); + } } -.capture-btn { - width: 140rpx; - height: 140rpx; - background-color: rgba(255, 255, 255, 0.3); - border: 6rpx solid #fff; +@keyframes slidein11 { + 0% { + transform: translate(0, 0) rotate(-90deg); + } + 50% { + transform: translate(-20rpx, 0rpx) rotate(-90deg); + } + 100% { + transform: translate(0, 0) rotate(-90deg); + } +} - .capture-inner { - width: 110rpx; - height: 110rpx; - border-radius: 50%; - background-color: #fff; +@keyframes slidein12 { + 0% { + transform: translate(0, 0) rotate(90deg); + } + 50% { + transform: translate(20rpx, 0rpx) rotate(90deg); + } + 100% { + transform: translate(0, 0) rotate(90deg); } } -.preview-section { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #000; - display: flex; - flex-direction: column; - z-index: 10; +@keyframes slidein13 { + 0% { + transform: translate(0, 0) rotate(45deg); + } + 50% { + transform: translate(20rpx, -20rpx) rotate(45deg); + } + 100% { + transform: translate(0, 0) rotate(45deg); + } +} - .preview-image { - flex: 1; - width: 100%; +@keyframes slidein14 { + 0% { + transform: translate(0, 0) rotate(180deg); + } + 50% { + transform: translate(0, 20rpx) rotate(180deg); } + 100% { + transform: translate(0, 0) rotate(180deg); + } +} - .preview-actions { - display: flex; - justify-content: center; - padding: 40rpx; - background-color: #000; +@keyframes slidein15 { + 0% { + transform: translate(0, 0) rotate(135deg); + } + 50% { + transform: translate(20rpx, 20rpx) rotate(135deg); } -} \ No newline at end of file + 100% { + transform: translate(0, 0) rotate(135deg); + } +} diff --git a/src/patient/components/camera/index.ts b/src/patient/components/camera/index.ts index e4c0fb1..8b91d7b 100644 --- a/src/patient/components/camera/index.ts +++ b/src/patient/components/camera/index.ts @@ -11,6 +11,73 @@ Component({ devicePosition: 'back', flash: 'off' as 'off' | 'auto' | 'on', previewImage: '', + + selectShow: false, + exampleShow: false, + + type: 1, + frame: { + 1: { + src: 'note-camera1', + exampleSrc: 'note-demo1', + }, + 2: { + src: 'note-camera1', + exampleSrc: 'note-demo2', + }, + 3: { + src: 'note-camera3', + exampleSrc: 'note-demo3', + }, + 4: { + src: 'note-camera4', + exampleSrc: 'note-demo4', + }, + 5: { + src: 'note-camera5', + exampleSrc: 'note-demo5', + }, + 6: { + src: 'note-camera6', + exampleSrc: 'note-demo6', + }, + 7: { + src: 'note-camera7', + exampleSrc: 'note-demo7', + }, + 8: { + src: 'note-camera1', + exampleSrc: 'note-demo8', + }, + 9: { + src: 'note-camera1', + exampleSrc: 'note-demo9', + }, + 10: { + src: 'note-camera1', + exampleSrc: 'note-demo10', + }, + 11: { + src: 'note-camera1', + exampleSrc: 'note-demo11', + }, + 12: { + src: 'note-camera1', + exampleSrc: 'note-demo12', + }, + 13: { + src: 'note-camera1', + exampleSrc: 'note-demo13', + }, + 14: { + src: 'note-camera1', + exampleSrc: 'note-demo14', + }, + 15: { + src: 'note-camera1', + exampleSrc: 'note-demo15', + }, + }, }, lifetimes: { @@ -22,6 +89,54 @@ Component({ }, methods: { + handleSelect(type: number) { + this.setData({ + selectShow: true, + type, + }) + }, + handleCancel() { + this.setData({ + selectShow: false, + }) + }, + handleCamera() { + this.setData({ + selectShow: false, + visible: true, + }) + }, + handleHideCamera() { + this.setData({ + visible: false, + }) + }, + handlePicture() { + this.handleCancel() + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album'], + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFile = res.tempFiles[0] + const maxSize = 10 * 1024 * 1024 // 10M + if (tempFile.size > maxSize) { + wx.showToast({ + title: '图片大小不能超过10M', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'file_too_large', size: tempFile.size, maxSize }) + return + } + this.uploadImage(tempFile.tempFilePath) + } + }, + fail: (err) => { + console.error('选择图片失败:', err) + }, + }) + }, openCamera() { this.setData({ visible: true, @@ -29,11 +144,6 @@ Component({ }) }, - closeCamera() { - this.setData({ visible: false }) - this.triggerEvent('cancel') - }, - switchCamera() { const newPosition = this.data.devicePosition === 'back' ? 'front' : 'back' this.setData({ @@ -60,7 +170,19 @@ Component({ this.setData({ previewImage: res.tempImagePath, }) - this.triggerEvent('capture', { tempFilePath: res.tempImagePath }) + wx.cropImage({ + src: res.tempImagePath, + cropScale: '9:16', + success: (cropRes) => { + // 裁剪成功后上传图片 + this.uploadImage(cropRes.tempFilePath || res.tempImagePath) + }, + fail: () => { + // 裁剪失败直接上传原图 + this.uploadImage(res.tempImagePath) + }, + }) + // this.triggerEvent('capture', { tempFilePath: res.tempImagePath }) }, fail: (err) => { console.error('拍照失败:', err) @@ -81,17 +203,90 @@ Component({ }) this.triggerEvent('error', { reason: 'permission_denied' }) }, - - retake() { - this.setData({ - previewImage: '', + checkImageSize(tempFilePath: string): Promise { + return new Promise((resolve) => { + const fs = wx.getFileSystemManager() + fs.stat({ + path: tempFilePath, + success: (res) => { + const maxSize = 10 * 1024 * 1024 // 10M + const stats = Array.isArray(res.stats) ? res.stats[0] : res.stats + const fileSize = (stats as WechatMiniprogram.Stats).size + if (fileSize > maxSize) { + wx.showToast({ + title: '图片大小不能超过10M', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'file_too_large', size: fileSize, maxSize }) + resolve(false) + } + else { + resolve(true) + } + }, + fail: () => { + resolve(true) + }, + }) }) - this.triggerEvent('retake') }, - usePhoto() { - this.triggerEvent('use', { tempFilePath: this.data.previewImage }) - this.setData({ visible: false }) + uploadImage(tempFilePath: string) { + this.checkImageSize(tempFilePath).then((isValid) => { + if (!isValid) + return + + const app = getApp() + wx.showLoading({ + title: '正在上传', + }) + + wx.uploadFile({ + filePath: tempFilePath, + name: 'file', + url: `${app.globalData.upFileUrl}?r=file-service/upload-img`, + success: (res) => { + wx.hideLoading() + try { + const data = JSON.parse(res.data) + if (data.code === 0 && data.data && data.data.Url) { + this.triggerEvent('uploadsuccess', { + url: data.data.Url, + fileUrl: data.data.Url, + tempFilePath, + }) + } + else { + wx.showToast({ + title: '上传失败', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'upload_failed', data }) + } + } + catch (e) { + wx.showToast({ + title: '解析响应失败', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'parse_error', error: e }) + } + }, + fail: (err) => { + wx.hideLoading() + wx.showToast({ + title: '上传失败', + icon: 'none', + }) + this.triggerEvent('uploaderror', { reason: 'upload_failed', error: err }) + }, + }) + }) + }, + handleExample() { + this.setData({ + exampleShow: !this.data.exampleShow, + }) }, }, }) diff --git a/src/patient/components/camera/index.wxml b/src/patient/components/camera/index.wxml index 0f7c5fc..4bc6ee7 100644 --- a/src/patient/components/camera/index.wxml +++ b/src/patient/components/camera/index.wxml @@ -1,41 +1,102 @@ - - - - - - + + + + + 查看示范 + + + + + + + + + + + + + + + + + + + + 请将眼睛置于虚线框中心位置 + + + + 正面睁眼 + 1 + /3 + + + + + + 翻转 + + + + + + - - - - - - - - + + + + + + + + + + + + + + - - + + + + 选择上传方式 + - - - - - - - - - - 重拍 - 使用照片 + + + + 拍摄 + + + + 从相册选择 + - \ No newline at end of file + diff --git a/src/patient/components/image-merge/index.ts b/src/patient/components/image-merge/index.ts index ffa75eb..e6189b1 100644 --- a/src/patient/components/image-merge/index.ts +++ b/src/patient/components/image-merge/index.ts @@ -43,6 +43,7 @@ Component({ wx.previewImage({ urls: [mergedImage], current: mergedImage, + showmenu: true, }) }) .catch((error) => { @@ -56,15 +57,16 @@ Component({ validateImages(imageList: ImageItem[]): Promise { return Promise.all( - imageList.map((item, index) => - new Promise((resolve, reject) => { - wx.getImageInfo({ - src: item.src, - success: () => resolve(), - fail: () => reject(new Error(`第${index + 1}张图片加载失败`)), - }) - }) - ) + imageList.map( + (item, index) => + new Promise((resolve, reject) => { + wx.getImageInfo({ + src: item.src, + success: () => resolve(), + fail: () => reject(new Error(`第${index + 1}张图片加载失败`)), + }) + }), + ), ) }, @@ -93,15 +95,12 @@ Component({ const canvas = res[0].node const ctx = canvas.getContext('2d') - ctx.fillStyle = '#ffffff' Promise.all(imageList.map(item => this.getImageInfo(item.src))) .then((imageInfos) => { const targetWidth = 750 const pixelRatio = wx.getWindowInfo().pixelRatio const canvasWidth = Math.floor((targetWidth * pixelRatio) / 2) - const timeAreaHeight = 60 - const watermarkHeight = 80 let totalHeight = 0 const scaledHeights: number[] = [] @@ -110,16 +109,12 @@ Component({ const scale = canvasWidth / info.width const scaledHeight = Math.floor(info.height * scale) scaledHeights.push(scaledHeight) - totalHeight += scaledHeight + timeAreaHeight + totalHeight += scaledHeight }) - totalHeight += watermarkHeight - canvas.width = canvasWidth canvas.height = totalHeight - ctx.fillRect(0, 0, canvasWidth, totalHeight) - let currentY = 0 let loadedCount = 0 @@ -129,22 +124,40 @@ Component({ img.onload = () => { ctx.drawImage(img, 0, currentY, canvasWidth, scaledHeights[index]) - const timeY = currentY + scaledHeights[index] + 40 - ctx.fillStyle = '#666666' - ctx.font = '24px sans-serif' - ctx.textAlign = 'center' + // 在每张图片左上角绘制时间,白色字体 const timeText = imageList[index].time || this.formatTime(new Date()) - ctx.fillText(timeText, canvasWidth / 2, timeY) + const padding = 20 + const textY = currentY + 40 + + // 设置白色字体和阴影以增强可读性 + ctx.fillStyle = '#ffffff' + ctx.font = 'bold 28px sans-serif' + ctx.textAlign = 'left' + ctx.shadowColor = 'rgba(0, 0, 0, 0.5)' + ctx.shadowBlur = 4 + ctx.shadowOffsetX = 1 + ctx.shadowOffsetY = 1 + ctx.fillText(timeText, padding, textY) + + // 重置阴影 + ctx.shadowColor = 'transparent' + ctx.shadowBlur = 0 + ctx.shadowOffsetX = 0 + ctx.shadowOffsetY = 0 + + // 如果是最后一张图片,在其右下角绘制水印 + if (index === imageInfos.length - 1) { + const lastImageBottom = currentY + scaledHeights[index] + ctx.fillStyle = 'rgba(255, 255, 255, 0.8)' + ctx.font = '24px sans-serif' + ctx.textAlign = 'right' + ctx.fillText('由-TED关爱小助手-小程序生成', canvasWidth - 20, lastImageBottom - 20) + } - currentY += scaledHeights[index] + timeAreaHeight + currentY += scaledHeights[index] loadedCount++ if (loadedCount === imageInfos.length) { - ctx.fillStyle = '#999999' - ctx.font = '28px sans-serif' - ctx.textAlign = 'right' - ctx.fillText('TED关爱小助手', canvasWidth - 30, totalHeight - 30) - wx.canvasToTempFilePath({ canvas, success: (result) => { diff --git a/src/patient/pages/changePhone/index.ts b/src/patient/pages/changePhone/index.ts index 4a2a05d..9da5da1 100644 --- a/src/patient/pages/changePhone/index.ts +++ b/src/patient/pages/changePhone/index.ts @@ -1,78 +1,78 @@ -const app = getApp(); -let timer = null as null | number; - -Page({ - data: { - mobile: "", - code: "", - - codeText: "发送验证码", - }, - onLoad() {}, - getCode() { - if (timer) return; - const mobile = this.data.mobile; - if (!mobile) { - wx.showToast({ - title: "手机号不能为空", - icon: "none", - }); - return; - } - // 验证手机号 - if (!/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/.test(mobile)) { - wx.showToast({ - title: "手机号格式不正确", - icon: "none", - }); - return; - } - wx.ajax({ - method: "POST", - url: "?r=zd/login/send-verify-code", - data: { - mobile, - }, - }).then((res) => { - console.log(res); - wx.showToast({ - icon: "none", - title: "验证码已发送~", - }); - let time = 60; - timer = setInterval(() => { - time--; - this.setData({ - codeText: time + "s后重新发送", - }); - if (time <= 0) { - clearInterval(timer as number); - timer = null; - this.setData({ - codeText: "发送验证码", - }); - } - }, 1000); - }); - }, - handleSubmit() { - const { mobile, code } = this.data; - const { registrationSource, registChannel, regBusinessId } = app.globalData; - wx.ajax({ - method: "POST", - url: "?r=zd/account/update-telephone", - data: { - mobile, - code, - registrationSource, - registChannel, - regBusinessId, - }, - }).then((_res) => { - wx.navigateBack(); - }); - }, - handleBack() { - wx.navigateBack(); - }, -}); +const app = getApp(); +let timer = null as null | number; + +Page({ + data: { + mobile: "", + code: "", + + codeText: "发送验证码", + }, + onLoad() {}, + getCode() { + if (timer) return; + const mobile = this.data.mobile; + if (!mobile) { + wx.showToast({ + title: "手机号不能为空", + icon: "none", + }); + return; + } + // 验证手机号 + if (!/^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/.test(mobile)) { + wx.showToast({ + title: "手机号格式不正确", + icon: "none", + }); + return; + } + wx.ajax({ + method: "POST", + url: "?r=zd/login/send-verify-code", + data: { + mobile, + }, + }).then((res) => { + console.log(res); + wx.showToast({ + icon: "none", + title: "验证码已发送~", + }); + let time = 60; + timer = setInterval(() => { + time--; + this.setData({ + codeText: time + "s后重新发送", + }); + if (time <= 0) { + clearInterval(timer as number); + timer = null; + this.setData({ + codeText: "发送验证码", + }); + } + }, 1000); + }); + }, + handleSubmit() { + const { mobile, code } = this.data; + const { registrationSource, registChannel, regBusinessId } = app.globalData; + wx.ajax({ + method: "POST", + url: "?r=zd/account/update-telephone", + data: { + mobile, + code, + registrationSource, + registChannel, + regBusinessId, + }, + }).then((_res) => { + wx.navigateBack(); + }); + }, + handleBack() { + wx.navigateBack(); + }, +}); diff --git a/src/patient/pages/imageProcessing/index.wxml b/src/patient/pages/imageProcessing/index.wxml index e13c14c..f40881f 100644 --- a/src/patient/pages/imageProcessing/index.wxml +++ b/src/patient/pages/imageProcessing/index.wxml @@ -43,7 +43,9 @@ 点击下方按钮使用示例图片演示拼接功能 - 演示图片拼接 + + 演示图片拼接 + diff --git a/src/patient/pages/index/index.scss b/src/patient/pages/index/index.scss index c618919..d956f4b 100644 --- a/src/patient/pages/index/index.scss +++ b/src/patient/pages/index/index.scss @@ -725,6 +725,7 @@ page { } .reg { position: fixed; + z-index: 10; left: 64rpx; bottom: calc(env(safe-area-inset-bottom) + 120rpx); width: 622rpx; diff --git a/src/patient/pages/note/index.json b/src/patient/pages/note/index.json new file mode 100644 index 0000000..856f659 --- /dev/null +++ b/src/patient/pages/note/index.json @@ -0,0 +1,7 @@ +{ + "usingComponents": { + "navbar": "/components/navbar/index", + "van-icon": "@vant/weapp/icon/index" + }, + "navigationBarTitleText": "突眼日记" +} diff --git a/src/patient/pages/note/index.scss b/src/patient/pages/note/index.scss new file mode 100644 index 0000000..b3309de --- /dev/null +++ b/src/patient/pages/note/index.scss @@ -0,0 +1,269 @@ +page { + background-color: #f6f8f9; +} +.container { + padding: 20rpx 30rpx; + min-height: 100vh; +} + +// 基准照片设置卡片 +.setting-card-empty { + background: #fff; + border-radius: 32rpx; + padding: 32rpx; + margin-bottom: 32rpx; + box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.04); + .setting-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20rpx; + } + + .setting-title { + display: flex; + align-items: center; + gap: 12rpx; + .icon { + width: 36rpx; + height: 36rpx; + } + + text { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + } + + .setting-status { + background: linear-gradient(90deg, #ffd650 0%, #f8a61a 100%); + color: #fff; + font-size: 24rpx; + padding: 6rpx 16rpx; + border-radius: 6rpx; + } + + .setting-desc { + font-size: 32rpx; + color: #69686e; + line-height: 48rpx; + background: #f6f8f9; + padding: 32rpx; + border-radius: 24rpx; + } +} +.setting-card { + padding: 32rpx; + background-color: #fff; + border-radius: 32rpx; + padding: 32rpx; + margin-bottom: 32rpx; + box-shadow: 0rpx 4rpx 32rpx 0rpx rgba(0, 0, 0, 0.04); + .setting-header { + .setting-title { + display: flex; + align-items: center; + gap: 12rpx; + .icon { + width: 36rpx; + height: 36rpx; + } + + text { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + } + } + .setting-body { + margin-top: 32rpx; + display: flex; + justify-content: space-between; + gap: 24rpx; + .photo { + width: 120rpx; + height: 120rpx; + border-radius: 16rpx; + } + .wrap { + flex: 1; + .name { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .date { + margin-top: 24rpx; + font-size: 28rpx; + color: #69686e; + } + } + .more { + font-size: 32rpx; + color: #b073ff; + } + } +} + +// 功能按钮区 +.action-buttons { + display: flex; + gap: 30rpx; + margin-bottom: 56rpx; +} + +.action-btn { + flex: 1; + height: 320rpx; + border-radius: 20rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20rpx; + position: relative; + overflow: hidden; + + &.primary { + background: linear-gradient(180deg, #e98ff8 0%, #b073ff 100%); + + .btn-text { + color: #fff; + } + + .corner-fold { + position: absolute; + top: 0; + right: 0; + width: 60rpx; + height: 60rpx; + background: linear-gradient(45deg, #e5d2ff 0%, #f6f8f9 50%, #f6f8f9 100%); + border-radius: 0 0 0 18rpx; + } + } + + &.secondary { + background: #fff; + box-shadow: 0 2rpx 12rpx rgba(14, 0, 32, 0.19); + + .btn-text { + color: #a855f7; + } + } +} + +.btn-icon { + display: flex; + align-items: center; + justify-content: center; + width: 108rpx; + height: 108rpx; +} + +.btn-text { + font-size: 32rpx; + font-weight: 500; +} + +.history-section { + .section-title { + display: flex; + align-items: center; + gap: 16rpx; + margin-bottom: 30rpx; + + .title-bar { + width: 8rpx; + height: 32rpx; + background: #a855f7; + border-radius: 4rpx; + } + + text { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + } + + .history-empty { + background: #fff; + border-radius: 32rpx; + padding: 30rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 80rpx 0; + gap: 24rpx; + .icon { + width: 64rpx; + height: 64rpx; + } + + .empty-text { + font-size: 32rpx; + color: #adacb2; + } + } + + .history-list { + .list-item { + position: relative; + margin-top: 24rpx; + border-radius: 32rpx; + background-color: #fff; + padding: 32rpx; + display: flex; + justify-content: space-between; + gap: 24rpx; + .benchmark { + position: absolute; + top: 0; + right: 0; + padding-top: 8rpx; + text-align: center; + width: 124rpx; + height: 64rpx; + font-size: 28rpx; + color: #ffa300; + line-height: 32rpx; + } + .photo { + width: 140rpx; + height: 140rpx; + border-radius: 16rpx; + } + .wrap { + flex: 1; + .date { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .tag { + margin-top: 16rpx; + padding: 4rpx 12rpx; + display: inline-block; + font-size: 24rpx; + color: #b073ff; + background: rgba(176, 115, 255, 0.16); + border-radius: 6rpx 6rpx 6rpx 6rpx; + } + .rotate { + margin-top: 28rpx; + font-size: 28rpx; + color: #adacb2; + line-height: 1; + } + } + .more { + align-self: center; + width: 44rpx; + height: 44rpx; + } + } + } +} diff --git a/src/patient/pages/note/index.ts b/src/patient/pages/note/index.ts new file mode 100644 index 0000000..2380d43 --- /dev/null +++ b/src/patient/pages/note/index.ts @@ -0,0 +1,35 @@ +const _app = getApp() + +Page({ + data: { + background: '#fff', + }, + + onLoad() { + // 页面加载时的初始化 + }, + + // 返回上一页 + handleBack() { + wx.navigateBack() + }, + + // 新增记录 + addRecord() { + wx.navigateTo({ + url: '/patient/pages/noteAdd/index', + }) + }, + handleHistory() { + wx.navigateTo({ + url: '/patient/pages/noteHistory/index', + }) + }, + comparePhotos() { + wx.navigateTo({ + url: '/patient/pages/noteDiff/index', + }) + }, +}) + +export {} diff --git a/src/patient/pages/note/index.wxml b/src/patient/pages/note/index.wxml new file mode 100644 index 0000000..3f87a40 --- /dev/null +++ b/src/patient/pages/note/index.wxml @@ -0,0 +1,70 @@ + + + + + + + + + + + 基准照片设置 + + 待设置 + + 请先上传一组完整的照片作为基准,后续记录将以此为参照进行对比。 + + + + + + 基准照片设置 + + + + + + 已设置基准照 + 记录日期:2026-04-01 + + 查看 + + + + + + + + + 新增记录 + + + + 照片对比 + + + + + + + + 历史记录 + + + + 暂无记录,点击上方按钮开始 + + + + 基准照 + + + 2026-04-01 + 替妥尤单抗:2 + 已上传1个角度 + + + + + + diff --git a/src/patient/pages/noteAdd/index.json b/src/patient/pages/noteAdd/index.json new file mode 100644 index 0000000..feef64a --- /dev/null +++ b/src/patient/pages/noteAdd/index.json @@ -0,0 +1,10 @@ +{ + "usingComponents": { + "navbar": "/components/navbar/index", + "van-icon": "@vant/weapp/icon/index", + "popup": "/components/popup/index", + "noteImagePreview": "/components/noteImagePreview/index", + "camera": "/patient/components/camera/index" + }, + "navigationBarTitleText": "记录新照片" +} diff --git a/src/patient/pages/noteAdd/index.scss b/src/patient/pages/noteAdd/index.scss new file mode 100644 index 0000000..09f6325 --- /dev/null +++ b/src/patient/pages/noteAdd/index.scss @@ -0,0 +1,309 @@ +page { + background-color: #f6f8f9; +} + +.page { + .benchmark { + margin-top: 32rpx; + padding: 0 40rpx 28rpx; + .checkbox { + font-size: 28rpx; + color: #211d2e; + .wx-checkbox-input { + background: rgba(173, 172, 178, 0.36); + border-color: transparent; + width: 32rpx; + height: 32rpx; + } + } + } + .form { + margin: 0 40rpx 24rpx; + padding: 32rpx; + background: #ffffff; + border-radius: 32rpx 32rpx 32rpx 32rpx; + .form-title { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + display: flex; + align-items: center; + gap: 16rpx; + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .form-title-gap { + margin-top: 48rpx; + } + .select { + margin-top: 24rpx; + display: flex; + justify-content: space-between; + padding: 26rpx 32rpx; + background: #f6f8f9; + border-radius: 24rpx 24rpx 24rpx 24rpx; + .content { + font-size: 32rpx; + color: #211d2e; + &:empty::after { + content: attr(data-place); + color: rgba(173, 172, 178, 0.6); + } + } + } + .dobule { + margin-top: 44rpx; + border-radius: 24rpx 24rpx 24rpx 24rpx; + display: flex; + gap: 26rpx; + .item { + .name { + font-size: 32rpx; + color: #211d2e; + } + .i-content { + margin-top: 24rpx; + padding: 26rpx 32rpx; + display: flex; + align-items: center; + background: #f6f8f9; + border-radius: 24rpx 24rpx 24rpx 24rpx; + font-size: 32rpx; + color: #211d2e; + .num { + font-size: 32rpx; + color: #211d2e; + } + } + } + } + } + .container { + margin-top: 72rpx; + padding: 32rpx 40rpx 0; + background-color: #fff; + border-radius: 32rpx 32rpx 0 0; + .c-title { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + display: flex; + align-items: center; + gap: 16rpx; + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .options { + margin-top: 30rpx; + display: flex; + align-items: center; + gap: 22rpx; + .btn { + flex: 1; + display: flex; + align-items: center; + gap: 16rpx; + padding: 26rpx; + border-radius: 18rpx; + .icon { + width: 36rpx; + height: 36rpx; + } + font-size: 32rpx; + &.btn1 { + color: #b982ff; + background: rgba(185, 130, 255, 0.12); + } + &.btn2 { + color: #ffffff; + background: linear-gradient(180deg, #e98ff8 0%, #b073ff 100%); + } + } + } + .card { + margin-top: 32rpx; + padding: 32rpx; + background: #f6f8f9; + border-radius: 24rpx 24rpx 24rpx 24rpx; + .card-title { + display: flex; + align-items: center; + gap: 16rpx; + .order { + width: 40rpx; + height: 40rpx; + font-size: 28rpx; + color: #b982ff; + display: flex; + align-items: center; + justify-content: center; + background: rgba(185, 130, 255, 0.1); + border-radius: 8rpx 8rpx 8rpx 8rpx; + } + } + .upload-container { + margin-top: 32rpx; + display: grid; + grid-template-columns: repeat(3, 1fr); + justify-content: space-between; + gap: 24rpx; + .upload-item { + $degs: ( + 1: -45deg, + 2: 0deg, + 3: 45deg, + 4: -90deg, + 5: 90deg, + 6: -135deg, + 7: -180deg, + 8: 135deg, + ); + @each $index, $deg in $degs { + &:nth-of-type(#{$index}) { + .arrow { + transform: rotate(#{$deg}); + } + } + } + &:nth-of-type(3), + &:nth-of-type(5), + &:nth-of-type(8) { + .name { + flex-direction: row-reverse; + } + } + .upload-place { + border: 1px dashed #b982ff; + border-radius: 16rpx; + width: 186rpx; + height: 186rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 24rpx; + box-sizing: border-box; + .icon { + width: 52rpx; + height: 52rpx; + } + .name { + font-size: 24rpx; + display: flex; + align-items: flex-end; + gap: 8rpx; + color: #211d2e; + line-height: 1; + .arrow { + width: 24rpx; + height: 24rpx; + } + } + } + .upload-preview { + position: relative; + width: 186rpx; + height: 186rpx; + .photo { + width: 100%; + height: 100%; + border-radius: 16rpx; + } + .status { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 16rpx; + background: rgba(9, 9, 9, 0.45); + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + .icon { + margin-top: 32rpx; + width: 40rpx; + height: 40rpx; + } + .content { + margin-top: 8rpx; + font-size: 24rpx; + color: #ffffff; + } + .guide { + margin-top: 26rpx; + width: 100%; + height: 40rpx; + text-align: center; + line-height: 40rpx; + font-size: 28rpx; + color: #ffffff; + border-radius: 0 0 16rpx 16rpx; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + } + } + } + } + } + &.card3 .upload-container { + .upload-item:nth-child(1) { + grid-area: 1 / 1; + } /* 左上 */ + .upload-item:nth-child(2) { + grid-area: 1 / 2; + } /* 上 */ + .upload-item:nth-child(3) { + grid-area: 1 / 3; + } /* 右上 */ + .upload-item:nth-child(4) { + grid-area: 2 / 1; + } /* 左 */ + .upload-item:nth-child(5) { + grid-area: 2 / 3; + } /* 右 */ + .upload-item:nth-child(6) { + grid-area: 3 / 1; + } /* 左下 */ + .upload-item:nth-child(7) { + grid-area: 3 / 2; + } /* 下 */ + .upload-item:nth-child(8) { + grid-area: 3 / 3; + } /* 右下 */ + } + } + .c-footer { + position: sticky; + bottom: 0; + left: 0; + background-color: #fff; + padding: 32rpx 0 60rpx; + .btn { + height: 88rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + color: #ffffff; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + box-shadow: 0rpx 16rpx 32rpx 0rpx rgba(185, 130, 255, 0.25); + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + } + } +} + +.place { + color: rgba(173, 172, 178, 0.6); +} diff --git a/src/patient/pages/noteAdd/index.ts b/src/patient/pages/noteAdd/index.ts new file mode 100644 index 0000000..667d26e --- /dev/null +++ b/src/patient/pages/noteAdd/index.ts @@ -0,0 +1,132 @@ +const _app = getApp() + +Page({ + data: { + popupShow: false, + popupType: 'popup16', // 提示保存弹窗 + popupParams: { + close: false, + position: 'bottom', + } as any, + + imagePreview: false, + + imageSrc: '', + + cameraList: { + frontend: [ + { + name: '睁眼', + type: 1, + }, + { + name: '闭眼', + type: 2, + }, + { + name: '仰头', + type: 3, + }, + ], + backend: [ + { + name: '左侧-90°', + type: 4, + }, + { + name: '右侧-90°', + type: 5, + }, + { + name: '左侧-45°', + type: 6, + }, + { + name: '右侧-45°', + type: 7, + }, + ], + other: [ + { + name: '左上', + type: 8, + }, + { + name: '向上', + type: 9, + }, + { + name: '左下', + type: 10, + }, + { + name: '向左', + type: 11, + }, + { + name: '向右', + type: 12, + }, + { + name: '右上', + type: 13, + }, + { + name: '向下', + type: 14, + }, + { + name: '右下', + type: 15, + }, + ], + }, + }, + onLoad() {}, + handleDemo() { + wx.navigateTo({ + url: '/patient/pages/noteDemo/index', + }) + }, + handleImageRetake() { + this.setData({ + imagePreview: false, + }) + }, + handleImageDel() { + this.setData({ + imagePreview: false, + }) + }, + handleCamera(e) { + const { type } = e.currentTarget.dataset + const cameraComponent = this.selectComponent('#camera-component') + cameraComponent.handleSelect(type) + }, + onUploadSuccess(e) { + this.setData({ + imageSrc: e.detail.url, + }) + }, + onUploadError(e) { + console.log('DEBUGPRINT[221]: index.ts:34: e=', e) + }, + handlePreview() { + this.selectComponent('#note-image-preview').handlePreview(this.data.imageSrc) + }, + handlePopupOk() { + this.setData({ + popupShow: false, + }) + }, + handlePopupCancel() { + this.setData({ + popupShow: false, + }) + }, + handleBack() { + wx.navigateBack() + }, +}) + +export {} diff --git a/src/patient/pages/noteAdd/index.wxml b/src/patient/pages/noteAdd/index.wxml new file mode 100644 index 0000000..c5d1c12 --- /dev/null +++ b/src/patient/pages/noteAdd/index.wxml @@ -0,0 +1,146 @@ + + + + + + + + + 设置为基准记录,用于对比 + + + 记录日期 + + + + + + + 当前记录对应的替妥尤单抗使用次数 + + + + + + + + + 记录本次突眼度 + + + 右眼 + + + MM + + + + 眶间距 + + + MM + + + + 左眼 + + + MM + + + + + + 持续追踪、固定位置拍摄,以便对比评估变化 + + + + 标准拍摄示范 + + + + 一键顺序拍摄 + + + + + 1 + 正面 + + + + + + + + 不符合规范 + 重新上传 + + + + + {{item.name}} + + + + + + + 2 + 侧面 + + + + + + + + + {{item.name}} + + + + + + + 3 + 眼球运动八个方向 + + + + + + + + + + + {{item.name}} + + + + + + + 诺和益托管视频 + + + + + + + + + diff --git a/src/patient/pages/noteDemo/index.json b/src/patient/pages/noteDemo/index.json new file mode 100644 index 0000000..8db3ce2 --- /dev/null +++ b/src/patient/pages/noteDemo/index.json @@ -0,0 +1,7 @@ +{ + "usingComponents": { + "navbar": "/components/navbar/index", + "van-icon": "@vant/weapp/icon/index" + }, + "navigationBarTitleText": "标准拍照示范" +} diff --git a/src/patient/pages/noteDemo/index.scss b/src/patient/pages/noteDemo/index.scss new file mode 100644 index 0000000..1c8fe4b --- /dev/null +++ b/src/patient/pages/noteDemo/index.scss @@ -0,0 +1,55 @@ +page { + background-color: #f6f8f9; +} +.page { + padding: 0 40rpx 40rpx; + .card { + margin-bottom: 32rpx; + padding: 116rpx 32rpx 32rpx; + border-radius: 32rpx; + background-color: #fff; + .tip { + background: #f7f1ff; + padding: 32rpx; + border-radius: 24rpx 24rpx 24rpx 24rpx; + border: 2rpx solid #b982ff; + } + .item { + margin-top: 36rpx; + .title { + display: flex; + align-items: center; + gap: 12rpx; + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + &::before { + content: ''; + width: 6rpx; + height: 32rpx; + background: linear-gradient(344deg, #ffbcf9 0%, #b982ff 100%); + border-radius: 6rpx 6rpx 6rpx 6rpx; + } + } + .content { + margin-top: 22rpx; + font-size: 32rpx; + color: #69686e; + line-height: 48rpx; + } + .photo { + margin-top: 32rpx; + width: 100%; + display: block; + } + } + &.card-dobule { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 48rpx 30rpx; + .item { + margin: 0; + } + } + } +} diff --git a/src/patient/pages/noteDemo/index.ts b/src/patient/pages/noteDemo/index.ts new file mode 100644 index 0000000..c563ebc --- /dev/null +++ b/src/patient/pages/noteDemo/index.ts @@ -0,0 +1,11 @@ +const _app = getApp() + +Page({ + data: {}, + onLoad() {}, + handleBack() { + wx.navigateBack() + }, +}) + +export {} diff --git a/src/patient/pages/noteDemo/index.wxml b/src/patient/pages/noteDemo/index.wxml new file mode 100644 index 0000000..842bb67 --- /dev/null +++ b/src/patient/pages/noteDemo/index.wxml @@ -0,0 +1,86 @@ + + + + + + + 拍摄时,正对镜头,面部居中,露出完整双眼及眼眶;光线均匀无阴影。 + + 正面睁眼照 + 平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。 + + + + 正面闭眼照 + 正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。 + + + + 正面仰头照 + + 拍摄时,正对镜头,面部居中,头部自然向上仰至约 45°,双眼同步平视,保持自然睁眼、不眯眼。 + + + + + + 拍摄时,头部保持水平,不仰头、不低头,双眼平视前方;光线均匀无阴影。 + + 左侧-90° + 身体与头部完全转向右侧,呈标准 90° 侧面,仅可见左侧眼睛。 + + + + 右侧-90° + 身体与头部完全转向左侧,呈标准 90° 侧面,仅可见右侧眼睛。 + + + + 左侧-45° + 身体与头部转向右前方 45°。 + + + + 右侧-45° + 身体与头部转向左前方 45° + + + + + + 正面眼睛上看 + + + + 正面眼睛下看 + + + + 正面眼睛左看 + + + + 正面眼睛右看 + + + + 正面眼睛左上看 + + + + 正面眼睛右上看 + + + + 正面眼睛左下看 + + + + 正面眼睛右下看 + + + + diff --git a/src/patient/pages/noteDiff/index.json b/src/patient/pages/noteDiff/index.json new file mode 100644 index 0000000..3f2ebce --- /dev/null +++ b/src/patient/pages/noteDiff/index.json @@ -0,0 +1,7 @@ +{ + "navigationStyle": "default", + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + }, + "navigationBarTitleText": "照片对比分析" +} diff --git a/src/patient/pages/noteDiff/index.scss b/src/patient/pages/noteDiff/index.scss new file mode 100644 index 0000000..4258647 --- /dev/null +++ b/src/patient/pages/noteDiff/index.scss @@ -0,0 +1,284 @@ +page { + background-color: #f6f8f9; +} +.page-none { + padding: 290rpx 76rpx 0; + .icon { + display: block; + margin: 0 auto; + width: 458rpx; + height: 218rpx; + } + .title { + margin-top: 26rpx; + font-size: 38rpx; + color: #211d2e; + font-weight: bold; + text-align: center; + } + .content { + margin-top: 24rpx; + font-size: 32rpx; + color: #69686e; + line-height: 48rpx; + text-align: center; + } + .btn { + margin: 88rpx 0 0; + font-size: 32rpx; + color: #ffffff; + height: 88rpx; + text-align: center; + line-height: 88rpx; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + box-shadow: 0rpx 16rpx 32rpx 0rpx rgba(185, 130, 255, 0.25); + border-radius: 100rpx 100rpx 100rpx 100rpx; + } +} +.page { + padding-bottom: 80rpx; + .page-tip { + padding: 24rpx 28rpx; + background-color: #fff7e9; + display: flex; + .icon { + margin-top: 6rpx; + flex-shrink: 0; + width: 32rpx; + height: 32rpx; + } + .content { + margin-left: 16rpx; + font-size: 28rpx; + color: #ffa300; + line-height: 44rpx; + } + } + .form { + margin: 32rpx 40rpx 0; + padding: 38rpx 32rpx 32rpx; + background: #ffffff; + border-radius: 32rpx 32rpx 32rpx 32rpx; + .form-item { + margin-bottom: 48rpx; + &:last-of-type { + margin-bottom: 0; + } + .title { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + display: flex; + align-items: center; + gap: 16rpx; + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .select { + margin-top: 24rpx; + display: flex; + justify-content: space-between; + padding: 26rpx 32rpx; + background: #f6f8f9; + border-radius: 24rpx 24rpx 24rpx 24rpx; + .content { + font-size: 32rpx; + color: #211d2e; + &:empty::after { + content: attr(data-place); + color: rgba(173, 172, 178, 0.6); + } + } + } + .multiple { + margin-top: 24rpx; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24rpx 22rpx; + .item { + padding: 16rpx; + font-size: 32rpx; + color: #211d2e; + display: flex; + align-items: center; + justify-content: center; + background: #f6f8f9; + border-radius: 24rpx 24rpx 24rpx 24rpx; + &.active { + color: #fff; + background: linear-gradient(180deg, #e98ff8 0%, #b073ff 100%); + } + } + } + } + } + .container { + margin: 76rpx 40rpx 0; + .title { + padding-bottom: 40rpx; + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + display: flex; + align-items: center; + gap: 16rpx; + .date { + font-size: 28rpx; + color: #adacb2; + font-weight: normal; + align-self: flex-end; + } + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .card { + display: flex; + .aside { + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + .line-bottom { + border: 1px dashed #b982ff; + } + .line-bottom { + flex: 1; + } + .circle { + position: relative; + width: 32rpx; + height: 32rpx; + border-radius: 50%; + background: rgba(185, 130, 255, 0.29); + &::after { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + content: ''; + width: 18rpx; + height: 18rpx; + border-radius: 50%; + background-color: #b982ff; + } + } + } + .c-container { + margin-top: -6rpx; + margin-left: 14rpx; + flex: 1; + padding-bottom: 48rpx; + .c-header { + display: flex; + align-items: center; + justify-content: space-between; + .date { + font-size: 32rpx; + color: #211d2e; + } + .btn { + width: 164rpx; + height: 56rpx; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + border-radius: 124rpx 124rpx 124rpx 124rpx; + display: flex; + align-items: center; + justify-content: center; + gap: 12rpx; + font-size: 32rpx; + color: #ffffff; + .icon { + width: 40rpx; + height: 40rpx; + } + } + } + .tags { + margin-top: 12rpx; + .tag { + display: inline-block; + margin-right: 16rpx; + padding: 2rpx 12rpx; + font-size: 24rpx; + color: #ffa300; + line-height: 32rpx; + border-radius: 6rpx; + &.tag1 { + background: rgba(255, 163, 0, 0.17); + color: #ffa300; + } + &.tag2 { + background: rgba(176, 115, 255, 0.16); + color: #b073ff; + } + } + } + .photo-card { + margin-top: 24rpx; + .photo { + border-radius: 32rpx 32rpx 0 0; + height: 352rpx; + display: block; + } + .row { + display: flex; + gap: 24rpx; + text-align: center; + border-radius: 0 0 32rpx 32rpx; + background-color: #fff; + .col { + padding: 24rpx; + flex: 1; + .name { + font-size: 28rpx; + color: #211d2e; + line-height: 32rpx; + } + .content { + margin-top: 16rpx; + display: flex; + justify-content: center; + align-items: baseline; + gap: 8rpx; + .num { + font-size: 56rpx; + color: #b073ff; + font-weight: bold; + } + .sub { + font-size: 28rpx; + color: #211d2e; + } + } + } + } + } + } + } + + .footer { + margin-top: 46rpx; + .btn1 { + height: 88rpx; + font-size: 36rpx; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + } + } +} diff --git a/src/patient/pages/noteDiff/index.ts b/src/patient/pages/noteDiff/index.ts new file mode 100644 index 0000000..8ea56c2 --- /dev/null +++ b/src/patient/pages/noteDiff/index.ts @@ -0,0 +1,13 @@ +const _app = getApp() + +Page({ + data: {}, + onLoad() {}, + handleEdit() { + wx.navigateTo({ + url: '/patient/pages/noteDiffEdit/index', + }) + }, +}) + +export {} diff --git a/src/patient/pages/noteDiff/index.wxml b/src/patient/pages/noteDiff/index.wxml new file mode 100644 index 0000000..110530e --- /dev/null +++ b/src/patient/pages/noteDiff/index.wxml @@ -0,0 +1,85 @@ + + + 未设置基准照片 + + 请先上传一组照片并将其设为基准 + + 以便进行对比分析 + + 去设置 + + + + + + 您可以同时选择多个日期,系统将按时间顺序排列照片。点击右上角按钮可预览并保存生成的对比长图。 + + + + + 选择对比角度 + + + + + + + + + 选择对比日期(可多选) + + 2026-04-02 + + + + + + 正面睁眼时间线对比 + 生成日期:2026-04-02 + + + + + + + + + 基准照片 2026-04-02 + + + 基准照片 + 替妥尤单抗:2 + + + + + + 右眼 + + 12 + MM + + + + 眶间距 + + 12 + MM + + + + 左眼 + + 12 + MM + + + + + + + + 生成对比图 + + + diff --git a/src/patient/pages/noteDiffEdit/index.json b/src/patient/pages/noteDiffEdit/index.json new file mode 100644 index 0000000..819d8a0 --- /dev/null +++ b/src/patient/pages/noteDiffEdit/index.json @@ -0,0 +1,8 @@ +{ + "navigationStyle": "default", + "usingComponents": { + "van-icon": "@vant/weapp/icon/index", + "imageMerge": "/patient/components/image-merge/index" + }, + "navigationBarTitleText": "照片对比分析" +} diff --git a/src/patient/pages/noteDiffEdit/index.scss b/src/patient/pages/noteDiffEdit/index.scss new file mode 100644 index 0000000..41137a0 --- /dev/null +++ b/src/patient/pages/noteDiffEdit/index.scss @@ -0,0 +1,157 @@ +page { + background-color: #f6f8f9; +} +.page { + padding-bottom: 80rpx; + .container { + margin: 40rpx 40rpx 0; + .title { + padding-bottom: 40rpx; + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + display: flex; + align-items: center; + gap: 16rpx; + .date { + font-size: 28rpx; + color: #adacb2; + font-weight: normal; + align-self: flex-end; + } + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .card { + display: flex; + .aside { + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + .line-bottom { + border: 1px dashed #b982ff; + } + .line-bottom { + flex: 1; + } + .circle { + position: relative; + width: 32rpx; + height: 32rpx; + border-radius: 50%; + background: rgba(185, 130, 255, 0.29); + &::after { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: block; + content: ''; + width: 18rpx; + height: 18rpx; + border-radius: 50%; + background-color: #b982ff; + } + } + } + .c-container { + margin-top: -6rpx; + margin-left: 14rpx; + flex: 1; + padding-bottom: 48rpx; + .c-header { + display: flex; + align-items: center; + justify-content: space-between; + .date { + font-size: 32rpx; + color: #211d2e; + } + } + .tags { + margin-top: 12rpx; + .tag { + display: inline-block; + margin-right: 16rpx; + padding: 2rpx 12rpx; + font-size: 24rpx; + color: #ffa300; + line-height: 32rpx; + border-radius: 6rpx; + &.tag1 { + background: rgba(255, 163, 0, 0.17); + color: #ffa300; + } + &.tag2 { + background: rgba(176, 115, 255, 0.16); + color: #b073ff; + } + } + } + .photo-card { + margin-top: 24rpx; + position: relative; + .photo { + border-radius: 32rpx 32rpx 0 0; + height: 352rpx; + display: block; + } + .mask { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 85rpx; + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; + background: rgba(0, 0, 0, 0.3); + font-size: 32rpx; + color: #ffffff; + .icon { + width: 40rpx; + height: 40rpx; + } + } + } + } + } + + .footer { + margin-top: 46rpx; + display: flex; + align-items: center; + gap: 22rpx; + .btn1 { + flex: 1; + height: 88rpx; + font-size: 36rpx; + color: #b982ff; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #b982ff; + background-color: #fff; + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + .btn2 { + flex: 1; + height: 88rpx; + font-size: 36rpx; + color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(0deg, #e98ff8 0%, #b073ff 100%); + border-radius: 100rpx 100rpx 100rpx 100rpx; + } + } + } +} diff --git a/src/patient/pages/noteDiffEdit/index.ts b/src/patient/pages/noteDiffEdit/index.ts new file mode 100644 index 0000000..fa382d9 --- /dev/null +++ b/src/patient/pages/noteDiffEdit/index.ts @@ -0,0 +1,19 @@ +const _app = getApp() + +Page({ + data: {}, + onLoad() {}, + handleMergePreview() { + const mergeComponent = this.selectComponent('#merge') + console.log("DEBUGPRINT[224]: index.ts:7: mergeComponent=", mergeComponent) + if (mergeComponent) { + mergeComponent.mergeImages([ + { src: 'https://picsum.photos/400/300', time: '2024-01-01 10:00' }, + { src: 'https://picsum.photos/400/301', time: '2024-01-01 12:00' }, + { src: 'https://picsum.photos/400/302' }, + ]) + } + }, +}) + +export {} diff --git a/src/patient/pages/noteDiffEdit/index.wxml b/src/patient/pages/noteDiffEdit/index.wxml new file mode 100644 index 0000000..d2cd546 --- /dev/null +++ b/src/patient/pages/noteDiffEdit/index.wxml @@ -0,0 +1,40 @@ + + + + 正面睁眼时间线对比 + 生成日期:2026-04-02 + + + + + + + + + 基准照片 2026-04-02 + + + 基准照片 + 替妥尤单抗:2 + + + + + + 点击裁剪 + + + + 点击还原 + + + + + + 对比图预览 + 保存到相册 + + + + + diff --git a/src/patient/pages/noteHistory/index.json b/src/patient/pages/noteHistory/index.json new file mode 100644 index 0000000..0a0db2b --- /dev/null +++ b/src/patient/pages/noteHistory/index.json @@ -0,0 +1,8 @@ +{ + "usingComponents": { + "navbar": "/components/navbar/index", + "van-icon": "@vant/weapp/icon/index", + "popup": "/components/popup/index" + }, + "navigationBarTitleText": "记录" +} diff --git a/src/patient/pages/noteHistory/index.scss b/src/patient/pages/noteHistory/index.scss new file mode 100644 index 0000000..d793433 --- /dev/null +++ b/src/patient/pages/noteHistory/index.scss @@ -0,0 +1,183 @@ +.page { + .header { + padding: 52rpx 32rpx 40rpx; + display: flex; + align-items: center; + justify-content: space-between; + .wrap { + .date { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .tags { + margin-top: 16rpx; + .tag { + display: inline-block; + margin-right: 16rpx; + padding: 2rpx 12rpx; + font-size: 24rpx; + color: #ffa300; + line-height: 32rpx; + border-radius: 6rpx; + &.tag1 { + background: rgba(255, 163, 0, 0.17); + color: #ffa300; + } + &.tag2 { + background: rgba(176, 115, 255, 0.16); + color: #b073ff; + } + } + } + } + .opt { + display: flex; + align-items: center; + gap: 24rpx; + .item { + width: 64rpx; + height: 64rpx; + display: flex; + align-items: center; + justify-content: center; + background: #ffffff; + border-radius: 50%; + .icon { + width: 40rpx; + height: 40rpx; + } + } + } + } + .container { + padding: 40rpx; + border-radius: 32rpx 32rpx 0 0; + background-color: #fff; + .tip-card { + padding: 24rpx 32rpx; + border-radius: 32rpx; + display: flex; + align-items: center; + justify-content: space-between; + background: #fff7e9; + gap: 16rpx; + .tip-icon { + margin-top: 6rpx; + align-self: flex-start; + flex-shrink: 0; + width: 32rpx; + height: 32rpx; + } + .wrap { + flex: 1; + line-height: 44rpx; + .title { + font-size: 28rpx; + color: #ffa300; + font-weight: bold; + } + .content { + font-size: 24rpx; + color: #ffa300; + } + } + .btn { + flex-shrink: 0; + font-size: 28rpx; + color: #ffffff; + width: 148rpx; + height: 56rpx; + display: flex; + align-items: center; + justify-content: center; + background: #ffa300; + border-radius: 92rpx 92rpx 92rpx 92rpx; + } + } + .banner { + margin-top: 24rpx; + padding: 32rpx; + background: #f6f8f9; + border-radius: 32rpx 32rpx 32rpx 32rpx; + border: 2rpx solid #ffffff; + .title { + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + } + .row { + margin-top: 24rpx; + display: flex; + gap: 24rpx; + text-align: center; + .col { + padding: 24rpx; + flex: 1; + background-color: #fff; + border-radius: 16rpx; + .name { + font-size: 28rpx; + color: #211d2e; + line-height: 32rpx; + } + .content { + margin-top: 16rpx; + display: flex; + justify-content: center; + align-items: baseline; + gap: 8rpx; + .num { + font-size: 56rpx; + color: #b073ff; + font-weight: bold; + } + .sub { + font-size: 28rpx; + color: #211d2e; + } + } + } + } + } + .card { + margin-top: 48rpx; + .card-title { + display: flex; + align-items: center; + gap: 16rpx; + font-size: 32rpx; + color: #211d2e; + font-weight: bold; + &::before { + content: ''; + width: 8rpx; + height: 32rpx; + background: #b982ff; + border-radius: 44rpx 44rpx 44rpx 44rpx; + } + } + .card-container { + margin-top: 24rpx; + display: grid; + grid-template-columns: repeat(2, 320rpx); + justify-content: space-between; + gap: 30rpx; + .card-item { + .photo { + display: block; + width: 100%; + height: 320rpx; + border-radius: 24rpx; + } + .name{ + margin-top: 16rpx; + font-size: 28rpx; + color: #ADACB2; + text-align: center; + } + } + } + } + } +} diff --git a/src/patient/pages/noteHistory/index.ts b/src/patient/pages/noteHistory/index.ts new file mode 100644 index 0000000..94b258a --- /dev/null +++ b/src/patient/pages/noteHistory/index.ts @@ -0,0 +1,94 @@ +const _app = getApp() + +Page({ + data: { + popupShow: true, + popupType: 'popup15', // 确认删除弹窗 + popupParams: { + close: false, + } as any, + + history: { + frontend: [ + { + title: '正面睁眼照', + content: '平视,目光看向镜头方向,自然睁眼,不眯眼、不瞪眼。', + }, + { + title: '正面闭眼照', + content: '正对镜头,面部居中,自然放松,双眼轻轻闭合,不皱眉、不挤眼。', + }, + { + title: '正面仰头照', + content: '拍摄时,正对镜头,面部居中,头部自然向上仰至约 45°,双眼同步平视,保持自然睁眼、不眯眼。', + }, + ], + backend: [ + { + title: '左侧-90°', + content: '身体与头部完全转向右侧,呈标准 90° 侧面,仅可见左侧眼睛。', + }, + { + title: '右侧-90°', + content: '身体与头部完全转向左侧,呈标准 90° 侧面,仅可见右侧眼睛。', + }, + { + title: '左侧-45°', + content: '身体与头部转向右前方 45°。', + }, + { + title: '右侧-45°', + content: '身体与头部转向左前方 45°', + }, + ], + other: [ + { + title: '正面眼睛上看', + content: '', + }, + { + title: '正面眼睛下看', + content: '', + }, + { + title: '正面眼睛左看', + content: '', + }, + { + title: '正面眼睛右看', + content: '', + }, + { + title: '正面眼睛左上看', + content: '', + }, + { + title: '正面眼睛右上看', + content: '', + }, + { + title: '正面眼睛左下看', + content: '', + }, + { + title: '正面眼睛右下看', + content: '', + }, + ], + }, + }, + onLoad() {}, + + handlePopupOk() { + this.setData({ + popupShow: false, + }) + }, + handlePopupCancel() { + this.setData({ + popupShow: false, + }) + }, +}) + +export {} diff --git a/src/patient/pages/noteHistory/index.wxml b/src/patient/pages/noteHistory/index.wxml new file mode 100644 index 0000000..966e52c --- /dev/null +++ b/src/patient/pages/noteHistory/index.wxml @@ -0,0 +1,98 @@ + + + + + + + + 2026-04-02 + + 基准照片 + 替妥尤单抗:2 + + + + + + + + + + + + + + + + + 记录未录入完全 + 当前已录入1/15个角度。您可以点击右上角的编辑按钮继续补充照片。 + + 去补充 + + + + 正面 + + + + {{item.title}} + + + + + 侧面 + + + + {{item.title}} + + + + + 眼球运动八个方向 + + + + {{item.title}} + + + + + + + diff --git a/src/utils/captcha.ts b/src/utils/captcha.ts new file mode 100644 index 0000000..bf5883a --- /dev/null +++ b/src/utils/captcha.ts @@ -0,0 +1,220 @@ +/** + * 阿里云验证码2.0工具模块 + * 用于小程序发送验证码前的行为验证 + */ + +// 验证码插件实例 +let AliyunCaptchaPluginInterface: any = null + +// 计时器 +let timer: number | null = null + +// 页面实例引用 +let pageInstance: any = null + +// 发送验证码的接口配置 +interface SendCodeConfig { + url: string + mobileField?: string + extraData?: Record +} + +// 验证码配置选项 +interface CaptchaOptions { + sceneId: string + sendCodeConfig: SendCodeConfig + onSendSuccess?: () => void + onSendFail?: (err: any) => void + countdown?: number +} + +// 当前配置 +let currentOptions: CaptchaOptions | null = null + +/** + * 初始化验证码插件 + */ +function initPlugin() { + if (!AliyunCaptchaPluginInterface) { + AliyunCaptchaPluginInterface = requirePlugin('AliyunCaptcha') + } +} + +/** + * 验证通过回调函数 + * @param captchaVerifyParam 验证码验证参数 + */ +async function successCallback(captchaVerifyParam: string) { + if (!pageInstance || !currentOptions) return + + const { sendCodeConfig, onSendSuccess, onSendFail, countdown = 60 } = currentOptions + const mobileField = sendCodeConfig.mobileField || 'mobile' + const mobile = pageInstance.data[mobileField] + + try { + const res = await wx.ajax({ + method: 'POST', + url: sendCodeConfig.url, + data: { + [mobileField]: mobile, + captchaVerifyParam, + ...sendCodeConfig.extraData, + }, + }) + + wx.showToast({ + icon: 'none', + title: '验证码已发送~', + }) + + // 开始倒计时 + startCountdown(countdown) + + // 执行成功回调 + onSendSuccess?.() + + return res + } + catch (err: any) { + wx.showToast({ + title: err.data?.msg || '发送失败,请重试', + icon: 'none', + }) + onSendFail?.(err) + throw err + } +} + +/** + * 验证失败回调函数 + * @param error 错误信息 + */ +function failCallback(error: any) { + console.error('阿里云验证码验证失败:', error) + wx.showToast({ + title: '验证失败,请重试', + icon: 'none', + }) +} + +/** + * 开始倒计时 + * @param seconds 倒计时秒数 + */ +function startCountdown(seconds: number) { + if (timer) { + clearInterval(timer) + timer = null + } + + let time = seconds + updateCountdownText(`${time}s后重新发送`) + + timer = setInterval(() => { + time-- + if (time <= 0) { + clearInterval(timer as number) + timer = null + updateCountdownText('发送验证码') + } + else { + updateCountdownText(`${time}s后重新发送`) + } + }, 1000) as unknown as number +} + +/** + * 更新倒计时文本 + * @param text 显示的文本 + */ +function updateCountdownText(text: string) { + if (pageInstance) { + pageInstance.setData({ codeText: text }) + } +} + +/** + * 初始化验证码功能 + * @param page 页面实例 + * @param options 配置选项 + * @returns 插件配置对象 + */ +export function initCaptcha(page: any, options: CaptchaOptions) { + initPlugin() + pageInstance = page + currentOptions = options + + const pluginProps = { + SceneId: options.sceneId, + mode: 'popup', + success: successCallback.bind(page), + fail: failCallback.bind(page), + slideStyle: { + width: 540, + height: 60, + }, + language: 'cn', + region: 'cn', + } + + return { + loadCaptcha: true, + pluginProps, + } +} + +/** + * 显示验证码弹窗 + * @returns 是否成功触发 + */ +export function showCaptcha(): boolean { + if (!AliyunCaptchaPluginInterface) { + initPlugin() + } + + if (isCountingDown()) { + return false + } + + AliyunCaptchaPluginInterface.show() + return true +} + +/** + * 检查是否正在倒计时 + * @returns 是否正在倒计时 + */ +export function isCountingDown(): boolean { + return timer !== null +} + +/** + * 清除倒计时 + */ +export function clearCountdown() { + if (timer) { + clearInterval(timer) + timer = null + } + updateCountdownText('发送验证码') +} + +/** + * 获取验证码插件接口 + * @returns 插件接口 + */ +export function getCaptchaPlugin() { + if (!AliyunCaptchaPluginInterface) { + initPlugin() + } + return AliyunCaptchaPluginInterface +} + +/** + * 页面卸载时清理资源 + */ +export function destroyCaptcha() { + clearCountdown() + pageInstance = null + currentOptions = null +}