From f542b1dcede56aafbb7bfb5a3e99dd0e40902b91 Mon Sep 17 00:00:00 2001 From: lik Date: Sun, 4 Oct 2026 21:21:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86AI=E9=A1=B5=E5=92=8C?= =?UTF-8?q?=E6=88=91=E7=9A=84=E9=A1=B5=EF=BC=8C=E4=B8=80=E7=BA=A7=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E9=83=BD=E9=87=8D=E6=9E=84=E5=AE=8C=E6=88=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 8 + app.json | 2 + doc/DESIGN.md | 3 +- doc/RERACT_PLAN.md | 16 +- pages/ai/aichat.js | 19 ++ pages/ai/aichat.wxml | 50 +++- pages/ai/aichat.wxss | 63 ++-- pages/escort/itemlist.wxss | 2 +- pages/mine/addressedit.js | 214 ++++++++++++++ .../comp_profile.json => addressedit.json} | 4 +- pages/mine/addressedit.wxml | 93 ++++++ pages/mine/addressedit.wxss | 268 ++++++++++++++++++ pages/mine/comp_address/comp_address.js | 195 ------------- pages/mine/comp_address/comp_address.wxml | 85 ------ pages/mine/comp_address/comp_address.wxss | 264 ----------------- pages/mine/comp_profile/comp_profile.js | 135 --------- pages/mine/comp_profile/comp_profile.wxml | 65 ----- pages/mine/comp_profile/comp_profile.wxss | 178 ------------ pages/mine/mine.js | 230 ++++++++------- pages/mine/mine.json | 6 +- pages/mine/mine.wxml | 107 +++++-- pages/mine/mine.wxss | 201 ++++++++++--- pages/mine/profileedit.js | 150 ++++++++++ .../comp_address.json => profileedit.json} | 4 +- pages/mine/profileedit.wxml | 77 +++++ pages/mine/profileedit.wxss | 184 ++++++++++++ 26 files changed, 1482 insertions(+), 1141 deletions(-) create mode 100644 pages/mine/addressedit.js rename pages/mine/{comp_profile/comp_profile.json => addressedit.json} (62%) create mode 100644 pages/mine/addressedit.wxml create mode 100644 pages/mine/addressedit.wxss delete mode 100644 pages/mine/comp_address/comp_address.js delete mode 100644 pages/mine/comp_address/comp_address.wxml delete mode 100644 pages/mine/comp_address/comp_address.wxss delete mode 100644 pages/mine/comp_profile/comp_profile.js delete mode 100644 pages/mine/comp_profile/comp_profile.wxml delete mode 100644 pages/mine/comp_profile/comp_profile.wxss create mode 100644 pages/mine/profileedit.js rename pages/mine/{comp_address/comp_address.json => profileedit.json} (62%) create mode 100644 pages/mine/profileedit.wxml create mode 100644 pages/mine/profileedit.wxss diff --git a/app.js b/app.js index 13fc9b3..39b6efb 100644 --- a/app.js +++ b/app.js @@ -65,6 +65,14 @@ App({ onShow(options) { const request = require('./utils/request.js') + // 用户主动退出过就不再静默重登:wxsignin 只凭 code 换到 openid 就能重新 + // 签发 token(api/user/handler/users.js 的 wxSignin),不拦这一下, + // 「我的」页的退出登录会被切后台/重进抵消。 + // 用户在「我的」页重新点登录并授权成功时会清掉这个标记 + if (wx.getStorageSync('has_logout')) { + this.initChatSocket() + return + } wx.login({ success: res => { if (res.code) { diff --git a/app.json b/app.json index 379840a..292e54e 100644 --- a/app.json +++ b/app.json @@ -12,6 +12,8 @@ "pages/health/profilelist", "pages/health/profileedit", "pages/mine/mine", + "pages/mine/profileedit", + "pages/mine/addressedit", "pages/mine/about", "pages/mine/contact", "pages/hospital/contact", diff --git a/doc/DESIGN.md b/doc/DESIGN.md index 2af1d73..29ba4eb 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -69,6 +69,7 @@ - **白卡一律走中性阴影** `--shadow-card: 0 2rpx 10rpx rgba(63, 43, 25, 0.07)`,圆角 28rpx。彩色(暖橙)阴影不得用在 `--white` 面上;它只允许用于实心 CTA、品牌印记等小体量元素(`--shadow-cta: 0 2rpx 8rpx rgba(184, 82, 10, 0.16)`)。 - 例外(已登记):`escort/itemlist` 三个服务分组做成**通栏白底块**——`--white` + 上下各 2rpx `--line` + `--shadow-card`,**圆角 0**;做法 `margin: 36rpx -32rpx 0` + `padding: 28rpx 32rpx`,行宽仍为 686rpx(若内缩,最长名称「VIP 5次 半天陪诊」会折行)。圆角必须为 0,否则满宽块的圆角会切在屏幕边上。页首 `.notice` 仍是内缩 28rpx 圆角白卡。 - 例外(已登记):`escort/itemlist` 页脚做**浅橙收口卡**(`--brand-soft` + 2rpx `--brand-line` + 28rpx 圆角 + 去阴影),与首页 `.quick-item-full` 同源,补回该页"暖色只剩线、没有面"的那一档。 +- 例外(已登记):`mine/mine` 的菜单与「退出登录」做成**通栏白底块**(`--white` + 上下各 2rpx `--line` + `--shadow-card`,圆角 0)。其中「退出登录」是**动作而非导航**,与菜单行不同族:单独一块、一行居中、**无图标无 chevron**、字色 `--muted`。状态色不得用于按钮(§三),退出因此不走红/橙,区分只靠结构与位置。 --- @@ -116,7 +117,7 @@ | 范围 | 现状 | 替换为 | |---|---|---| | `mine/about.wxss` | `#D4A853`、`#FF8500`、`#22A45D` | `--brand-strong` / `--brand-ink` | -| `mine/comp_address.wxml`(`switch` 属性) | 旧金色 | `#B8520A`(属性值需硬编码) | +| `mine/addressedit.wxml`(`switch` 属性) | 旧金色 | `#B8520A`(属性值需硬编码) | | `health/profilelist.js`(`confirmColor`) | 旧金色 | `#B8520A` | | `health/profilelist.wxss`、`profileedit.wxss` | `#D4A853` 系 | 暖橙令牌 | | `escort/recordlist.wxss` | `#D4A853` 系 | 暖橙令牌 + 状态四档 | diff --git a/doc/RERACT_PLAN.md b/doc/RERACT_PLAN.md index 7f51995..3fb92c2 100644 --- a/doc/RERACT_PLAN.md +++ b/doc/RERACT_PLAN.md @@ -5,20 +5,22 @@ --- -## 一、已完成(4 页) +## 一、已完成(6 页) | 页面 | 完成 | 主要动作 | |---|---|---| | `home/index`(首页) | 更早 | 暖白纸面 + 白卡语言重建:hero(大标题 / 场景行 / 通栏主 CTA)→ 陪诊服务(白卡宫格 + 浅橙介绍卡)→ 常用功能(青绿待服务条 + 双列 / 通栏卡)→ 页脚文字链。页内旧令牌副本已删 | | `escort/itemlist`(陪诊服务列表) | 更早 | 页首短横色标 + 引导,说明白卡(开通城市 / 费用披露 + 两个入口),三组「通栏白底 + 章线」价目表,空心环「预约」按钮,浅橙收口页脚卡。页内旧令牌副本已删 | | `mine/mine`(我的) | 2026-10-04 | 并入暖橙体系 + 首页白卡语言:纸面底、头像与昵称直接落纸面、菜单白卡(图标走 `--brand-ink`)、页脚细竖线分隔。默认头像改用暖墨品牌标记 `images/home-ink.png`。删掉一批死样式(`.user-phone`、`.avatar-btn`、昵称输入等) | -| `ai/aichat`(AI 陪诊助手) | 2026-10-04 | 整页重设计:纸面底 + 消息列表(头像行在上、气泡在下,用户消息整条靠右并把时间镜像到头像左侧);AI 与用户头像统一底座(浅橙底 + 暖橙描边),身份只由图形区分(品牌标记 / 人像);AI 声明由页底常驻行改为挂在每条 AI 气泡下方;清空入口移到输入框下方做成空心描边小按钮;全页圆角统一 20rpx。附带修复滚动布局(`.message-list` 补 `min-height: 0`,消息超一屏后输入区不再被顶出视口) | +| `ai/aichat`(AI 陪诊助手) | 2026-10-04 | 整页重设计:纸面底 + 消息列表(头像行在上、气泡在下,用户消息整条靠右并把时间镜像到头像左侧);AI 与用户头像统一底座(浅橙底 + 暖橙描边),身份只由图形区分(品牌标记 / 人像);AI 声明由页底常驻行改为挂在每条 AI 气泡下方;空态图标改用 `images/ai-ink.png`;清空入口移到输入框下方做成空心描边小按钮,并在其旁增加「常见问题」入口——点击把常见问题清单整块铺进会话流(行间以横线分隔,可重复点选),空会话欢迎区复用同一清单块样式;全页圆角统一 20rpx。附带修复滚动布局(`.message-list` 补 `min-height: 0`,消息超一屏后输入区不再被顶出视口) | +| `mine/profileedit`(个人信息) | 2026-10-04 | 由 `mine` 页上的 `comp_profile` 浮层改为真页面(不再用 fixed 浮层,返回键交给页面栈)。表单沿用 `mine` 的通栏白块语言:标签左走 `--muted`、值右走 `--ink`,选中态用软橙底 + 实心橙描边;保存是本页唯一实心橙块 | +| `mine/addressedit`(邮寄地址) | 2026-10-04 | 由 `comp_address` 浮层改为真页面,列表 / 表单两态同页切换并同步导航栏标题。地址卡走白卡 + chevron 点击暗示,默认地址用图形 + 文字双通道传达状态;表单态取消走空心、保存走实心橙 | -> DESIGN.md §5.1 已同步登记 `mine/mine.wxss`、`ai/aichat.wxss` 两页迁移完成。 +> DESIGN.md §5.1 已登记 `mine/mine.wxss`、`ai/aichat.wxss` 两页迁移完成;`mine/profileedit.wxss`、`mine/addressedit.wxss` 待补登。 --- -## 二、待重构(13 页 / 22 个文件) +## 二、待重构(11 页 / 19 个文件) 按"离规范的距离"分三档,**建议从上往下推进**——同档内先易后难。 @@ -33,13 +35,11 @@ | `escort/guide` | `.wxss` `.wxml` | 同上 | | `mine/contact` | `.wxss` `.wxml` | 同上 | -### B. 旧金色体系(`#D4A853` 系,改动最大)|9 页 + 3 组件/JS +### B. 旧金色体系(`#D4A853` 系,改动最大)|7 页 + 1 组件/JS | 页面 | 文件 | 动作 | |---|---|---| | `mine/about` | `.wxss` | 按 §5.3 映射表整体换色 | -| `mine/comp_address` | `.wxss` `.wxml`(`switch` 属性值需硬编码) | 同上 | -| `mine/comp_profile` | `.wxss` | 同上 | | `health/profilelist` | `.wxss` `.wxml` `.js`(`confirmColor`) | 同上 | | `health/profileedit` | `.wxss` | 同上 | | `escort/recordlist` | `.wxss` | 换色 + 状态四档 | @@ -61,7 +61,7 @@ ### 组件与 JS 里的硬编码(易漏) -`comp_address.wxml` 的 `switch` 属性、`profilelist.js` 的 `confirmColor` 走的是**属性值**,读不到 CSS 变量,必须硬编码暖橙。 +`addressedit.wxml` 的 `switch` 属性、`profilelist.js` 的 `confirmColor` 走的是**属性值**,读不到 CSS 变量,必须硬编码暖橙(`mine/addressedit` 迁移时已按此处理,`health/profilelist` 待处理)。 --- diff --git a/pages/ai/aichat.js b/pages/ai/aichat.js index 7e186c6..ccc7bd0 100644 --- a/pages/ai/aichat.js +++ b/pages/ai/aichat.js @@ -146,6 +146,25 @@ Page({ this.sendMessage() }, + // 把常见问题清单作为一条 AI 消息铺进会话流(可随时唤起、可重复点选) + showQuickQuestions() { + const questions = this.data.quickQuestions || [] + if (!questions.length) return + + const questionMessage = { + id: `msg_${++this.messageIdCounter}`, + type: 'ai', + contentType: 'questions', + content: questions, + time: this.formatTime(new Date()) + } + + const messages = [...this.data.messages, questionMessage] + this.setData({ messages: messages }) + this.saveChatHistory(messages) + this.scrollToBottom() + }, + sendMessage() { const content = this.data.inputValue.trim() if (!content) return diff --git a/pages/ai/aichat.wxml b/pages/ai/aichat.wxml index 3f1a213..b2789c0 100644 --- a/pages/ai/aichat.wxml +++ b/pages/ai/aichat.wxml @@ -10,11 +10,11 @@ - + - AI陪诊助手 + 陪诊助手 我是您的智能陪诊助手,可以为您解答陪诊相关问题 - + - + + + + + {{question}} + + + - 内容由AI生成,仅供参考 + (它太占底部空间)。放在 .message-content 内,与气泡同宽同左边缘。 + 问题清单由本地生成、非 AI 内容,故不挂声明 --> + 内容由AI生成,仅供参考 @@ -110,11 +129,18 @@ - - - - 清空聊天 + + + + + 常见问题 + + + + 清空聊天 + \ No newline at end of file diff --git a/pages/ai/aichat.wxss b/pages/ai/aichat.wxss index 90df033..e2307d5 100644 --- a/pages/ai/aichat.wxss +++ b/pages/ai/aichat.wxss @@ -18,31 +18,37 @@ background-color: var(--paper); } -/* ===== 清空入口(在输入区下方)===== */ +/* ===== 辅助入口(在输入区下方:常见问题 + 清空聊天)===== */ /* 空心描边小按钮:2rpx 暖橙描边、无填充。 - 清空是低频破坏性动作,用"只描边"与输入区右侧那个浅橙底发送键区分开; + 两者都是低频辅助动作,用"只描边"与输入区右侧那个浅橙底发送键区分开; 描边在纸面上 4.66:1、文字 6.10:1,均过线。 - 圆角 20rpx 与输入框、发送键、气泡同档——全页统一为圆角矩形,不再有胶囊 - 容器是 column,靠 align-self: flex-end 右对齐,不必再套一层行容器 */ -.clear-btn { + 圆角 20rpx 与输入框、发送键、气泡同档——全页统一为圆角矩形,不再有胶囊。 + 容器是 column,靠 align-self: flex-end 把整行右对齐 */ +.tool-bar { align-self: flex-end; display: flex; align-items: center; - gap: 8rpx; + gap: 16rpx; flex-shrink: 0; /* 上 12 贴住输入区,右 32 对齐页面页边距;下不留边距,底部空气统一交给容器 */ margin: 12rpx 32rpx 0; +} + +.tool-btn { + display: flex; + align-items: center; + gap: 8rpx; padding: 10rpx 26rpx; border-radius: 20rpx; border: 2rpx solid var(--brand-strong); transition: transform 0.15s ease; } -.clear-btn:active { +.tool-btn:active { transform: scale(0.96); } -.clear-btn-text { +.tool-btn-text { font-size: 24rpx; font-weight: 500; color: var(--brand-ink); @@ -64,7 +70,8 @@ display: flex; flex-direction: column; align-items: center; - padding: 80rpx 40rpx; + /* 上方留白收小:消息区本身已有 20rpx 内距,这里 80rpx 会显得图标吊得太低 */ + padding: 40rpx 40rpx 80rpx; text-align: center; } @@ -81,6 +88,14 @@ border: 2rpx solid var(--brand-line); } +/* 空态图标改为 ai-ink.png(暖墨线稿的 AI 气泡):与头像区的 home-ink.png 同源, + 落在浅橙衬底上仍清晰(暖陶土线稿约 6.8:1)。 + 88rpx 略大于原 t-icon 的 72rpx,因为该图是满画布的气泡轮廓,缩小会显弱 */ +.welcome-icon-img { + width: 88rpx; + height: 88rpx; +} + .welcome-title { margin-bottom: 12rpx; font-size: 40rpx; @@ -95,32 +110,42 @@ color: var(--muted); } -.quick-questions { +/* 常见问题清单:整块收成一张白卡,行与行之间只用横线分隔、不留间隙。 + 空会话的欢迎区与会话流里由「常见问题」按钮唤起的清单共用同一套语言—— + 都是一份"可点的清单",不该一处是散卡、一处才收成块 */ +.question-list { width: 100%; display: flex; flex-direction: column; - gap: 20rpx; + background: var(--white); + border: 2rpx solid var(--line); + border-radius: 20rpx; + box-shadow: var(--shadow-card); + /* 裁掉行的直角与点按底色,保住卡片自身的圆角 */ + overflow: hidden; } -/* 常见问题:首页白卡语言 + 右侧 chevron 做点击暗示 */ +/* 清单行:文字 + 右侧 chevron 做点击暗示 */ .quick-item { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; - padding: 28rpx 28rpx 28rpx 32rpx; - background: var(--white); - border: 2rpx solid var(--line); - border-radius: 20rpx; - box-shadow: var(--shadow-card); + padding: 26rpx 28rpx; font-size: 28rpx; color: var(--ink-2); text-align: left; - transition: transform 0.15s ease; + transition: background 0.15s ease; } +/* 行分隔线用 --sep(比 --line 深一档,白面上才看得见) */ +.quick-item + .quick-item { + border-top: 2rpx solid var(--sep); +} + +/* 行嵌在整块里,不能像独立卡那样整体缩放(会撕裂分隔线), + 点按反馈改由底色承担 */ .quick-item:active { - transform: scale(0.98); background: var(--brand-soft); } diff --git a/pages/escort/itemlist.wxss b/pages/escort/itemlist.wxss index d753b72..1e3e6ac 100644 --- a/pages/escort/itemlist.wxss +++ b/pages/escort/itemlist.wxss @@ -386,7 +386,7 @@ page { 加粗到 3rpx 让它稳稳承担"行边界",不靠加深线色来解决 */ .row-desc { display: block; - margin-top: 12rpx; + margin-top: 2rpx; font-size: 28rpx; color: var(--muted); line-height: 1.7; diff --git a/pages/mine/addressedit.js b/pages/mine/addressedit.js new file mode 100644 index 0000000..0aa294a --- /dev/null +++ b/pages/mine/addressedit.js @@ -0,0 +1,214 @@ +// pages/mine/addressedit.js +const API = require('../../utils/api.js') + +Page({ + data: { + addresses: [], + editData: { + _index: -1, + label: '', + province: '', + city: '', + district: '', + address: '', + postcode: '', + isDefault: false + }, + isEditing: false + }, + + onLoad() { + this.initAddressData() + }, + + // 列表初值取自全局用户对象;「我的」页不再持有地址,本页是唯一入口 + initAddressData() { + const app = getApp() + const user = app.globalData.user || {} + const addresses = user.addresses || [] + this.setData({ + // 深拷一份,编辑过程中不污染全局对象,落库成功后才回写 + addresses: addresses.map(item => ({ ...item })), + isEditing: false, + editData: this.emptyEditData() + }) + }, + + emptyEditData() { + return { + _index: -1, + label: '', + province: '', + city: '', + district: '', + address: '', + postcode: '', + isDefault: false + } + }, + + onAddAddress() { + this.setData({ + isEditing: true, + // 第一条地址自动设为默认,免得多一步勾选 + editData: { ...this.emptyEditData(), isDefault: this.data.addresses.length === 0 } + }) + wx.setNavigationBarTitle({ title: '新增地址' }) + }, + + onEditAddress(e) { + const index = e.currentTarget.dataset.index + const item = this.data.addresses[index] + this.setData({ + isEditing: true, + editData: { + _index: index, + label: item.label || '', + province: item.province || '', + city: item.city || '', + district: item.district || '', + address: item.address || '', + // 本页没有邮编输入项,但要原样带过去,避免编辑一次就把它清掉 + postcode: item.postcode || '', + isDefault: item.isDefault || false + } + }) + wx.setNavigationBarTitle({ title: '编辑地址' }) + }, + + onBackToList() { + this.setData({ + isEditing: false, + editData: this.emptyEditData() + }) + wx.setNavigationBarTitle({ title: '邮寄地址' }) + }, + + onDeleteAddress(e) { + const index = e.currentTarget.dataset.index + wx.showModal({ + title: '提示', + content: '确定删除该地址吗?', + success: (res) => { + if (!res.confirm) return + const addresses = this.data.addresses.slice() + addresses.splice(index, 1) + // 删掉的是默认地址时顺位补一个,保证列表里始终有默认 + if (addresses.length > 0 && !addresses.some(a => a.isDefault)) { + addresses[0].isDefault = true + } + this.setData({ addresses }) + this.persist(addresses) + } + }) + }, + + onSetDefault(e) { + const index = e.currentTarget.dataset.index + const addresses = this.data.addresses.map((item, i) => ({ + ...item, + isDefault: i === index + })) + this.setData({ addresses }) + this.persist(addresses) + }, + + onRegionChange(e) { + const region = e.detail.value + this.setData({ + 'editData.province': region[0] || '', + 'editData.city': region[1] || '', + 'editData.district': region[2] || '' + }) + }, + + onFieldInput(e) { + const field = e.currentTarget.dataset.field + this.setData({ + [`editData.${field}`]: e.detail.value + }) + }, + + onDefaultChange(e) { + this.setData({ + 'editData.isDefault': e.detail.value + }) + }, + + onSaveEdit() { + const editData = this.data.editData + if (!editData.label.trim()) { + wx.showToast({ title: '请输入地址标签', icon: 'none' }) + return + } + if (!editData.province || !editData.city || !editData.district) { + wx.showToast({ title: '请选择所在地区', icon: 'none' }) + return + } + if (!editData.address.trim()) { + wx.showToast({ title: '请输入详细地址', icon: 'none' }) + return + } + + const addresses = this.data.addresses.slice() + const newItem = { + label: editData.label.trim(), + province: editData.province, + city: editData.city, + district: editData.district, + address: editData.address.trim(), + postcode: editData.postcode.trim(), + isDefault: editData.isDefault + } + + if (editData.isDefault) { + addresses.forEach(item => { item.isDefault = false }) + } + + if (editData._index >= 0) { + addresses[editData._index] = newItem + } else { + if (addresses.length === 0) { + newItem.isDefault = true + } + addresses.push(newItem) + } + + this.setData({ addresses }) + this.onBackToList() + this.persist(addresses) + }, + + // 地址是即时生效的(删除 / 设默认 / 表单保存都直接落库), + // 成功后回写全局对象:「我的」页与下单页读的都是它 + persist(addresses) { + const app = getApp() + const user = app.globalData.user + + if (!user || !user._id) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + + wx.showLoading({ title: '保存中...' }) + + API.user.update({ + _id: user._id, + addresses + }) + .then((data) => { + if (data.code === 0) { + app.globalData.user = data.data.user + wx.showToast({ title: '保存成功', icon: 'success' }) + } else { + wx.showToast({ title: data.msg || '保存失败', icon: 'none' }) + } + }) + .catch(() => { + wx.showToast({ title: '网络请求失败', icon: 'none' }) + }) + .finally(() => { + wx.hideLoading() + }) + } +}) \ No newline at end of file diff --git a/pages/mine/comp_profile/comp_profile.json b/pages/mine/addressedit.json similarity index 62% rename from pages/mine/comp_profile/comp_profile.json rename to pages/mine/addressedit.json index 3fb9848..62aab2d 100644 --- a/pages/mine/comp_profile/comp_profile.json +++ b/pages/mine/addressedit.json @@ -1,6 +1,6 @@ { - "component": true, + "navigationBarTitleText": "邮寄地址", "usingComponents": { "t-icon": "tdesign-miniprogram/icon/icon" } -} +} \ No newline at end of file diff --git a/pages/mine/addressedit.wxml b/pages/mine/addressedit.wxml new file mode 100644 index 0000000..c8fd6f5 --- /dev/null +++ b/pages/mine/addressedit.wxml @@ -0,0 +1,93 @@ + + + + + + + + + + + + {{item.label}} + 默认 + + {{item.province}} {{item.city}} {{item.district}} + {{item.address}} + 邮编:{{item.postcode}} + + + + + + + + + + {{item.isDefault ? '默认地址' : '设为默认'}} + + + + 删除 + + + + + + + + 还没有邮寄地址,添加后下单即可选用 + + + + + + + + + + + + 地址标签 + + + + + 所在地区 + + + {{editData.province || '请选择地区'}}{{editData.city ? ' ' + editData.city : ''}}{{editData.district ? ' ' + editData.district : ''}} + + + + + + 详细地址 + + + + + 设为默认地址 + + + + + + + + + + + + \ No newline at end of file diff --git a/pages/mine/addressedit.wxss b/pages/mine/addressedit.wxss new file mode 100644 index 0000000..f1d6f9b --- /dev/null +++ b/pages/mine/addressedit.wxss @@ -0,0 +1,268 @@ +page { + /* 配色令牌来自 app.wxss(全站唯一来源),本页不留副本 */ + background-color: var(--paper); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.container { + min-height: 100vh; + box-sizing: border-box; + /* 底部留出固定操作栏的高度 + 安全区,避免最后一个地址被盖住 */ + padding: 40rpx 32rpx calc(180rpx + env(safe-area-inset-bottom)); +} + +/* ===== 通栏白块(列表态 / 表单态共用同一语言)===== + 白面 + 上下各 2rpx 发丝线 + 中性阴影,圆角 0; + 负向 margin 抵消容器 32rpx 内距后左右顶到屏幕两侧, + 行内再用 32rpx 内距回到同一左基线,内部发丝线因此通到底 */ +.addr-list, +.form-card { + margin: 0 -32rpx; + background: var(--white); + border-top: 2rpx solid var(--line); + border-bottom: 2rpx solid var(--line); + box-shadow: var(--shadow-card); +} + +/* ===== 地址卡 ===== */ +.addr-card { + padding: 28rpx 32rpx; + border-bottom: 2rpx solid var(--line); +} + +.addr-card-last { + border-bottom: none; +} + +.addr-main { + display: flex; + align-items: center; + justify-content: space-between; +} + +.addr-main-hover { + opacity: 0.7; +} + +.addr-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.addr-top { + display: flex; + align-items: center; + margin-bottom: 10rpx; +} + +.addr-label { + font-size: 30rpx; + font-weight: 700; + color: var(--ink); +} + +/* 默认标签:浅橙衬底档(--brand-soft + 2rpx --brand-line + --brand-ink), + 字号 24rpx 是中文最小字号下限,不得再小 */ +.addr-default-tag { + margin-left: 16rpx; + padding: 4rpx 12rpx; + border-radius: 6rpx; + background: var(--brand-soft); + border: 2rpx solid var(--brand-line); + font-size: 24rpx; + color: var(--brand-ink); +} + +.addr-region { + margin-bottom: 6rpx; + font-size: 26rpx; + color: var(--muted); +} + +.addr-detail { + font-size: 28rpx; + color: var(--ink); + line-height: 1.5; +} + +.addr-postcode { + margin-top: 6rpx; + font-size: 24rpx; + color: var(--muted); +} + +.addr-arrow { + flex-shrink: 0; + margin-left: 16rpx; + color: var(--muted); +} + +/* 卡片操作行:与主体隔一条发丝线,两项分列两端 */ +.addr-actions { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 24rpx; + padding-top: 20rpx; + border-top: 2rpx solid var(--line); +} + +/* 文字只有 26rpx 高,用 padding 撑热区、等量负外边距抵消,不改变行高 */ +.addr-action { + display: flex; + align-items: center; + padding: 16rpx 0; + margin: -16rpx 0; +} + +.addr-action-icon { + margin-right: 10rpx; + color: var(--muted); +} + +.addr-action-icon-active { + color: var(--brand-strong); +} + +.addr-action-text { + font-size: 26rpx; + color: var(--muted); +} + +.addr-action-text-active { + color: var(--brand-ink); + font-weight: 700; +} + +/* ===== 空态 ===== */ +.addr-empty { + display: flex; + flex-direction: column; + align-items: center; + padding: 140rpx 32rpx; +} + +/* --faint 仅用于非文字装饰,不承载信息 */ +.addr-empty-icon { + color: var(--faint); +} + +.addr-empty-text { + margin-top: 24rpx; + font-size: 28rpx; + color: var(--muted); +} + +/* ===== 表单 ===== */ +/* 行高约 87rpx(28rpx×1.4 + 上下 24rpx),贴近 iOS 44pt 触控目标 */ +.form-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx 32rpx; + border-bottom: 2rpx solid var(--line); +} + +.form-row-last { + border-bottom: none; +} + +/* 标签走 --muted、值走 --ink 700:层级靠明度与字重拉开,不给可点性上色 */ +.form-label { + flex-shrink: 0; + margin-right: 24rpx; + font-size: 28rpx; + color: var(--muted); + letter-spacing: 1rpx; +} + +.form-input { + flex: 1; + min-width: 0; + text-align: right; + font-size: 28rpx; + font-weight: 700; + color: var(--ink); +} + +.form-placeholder { + color: var(--muted); + font-weight: 400; +} + +/* picker 需占满剩余宽度,值才能靠右对齐 */ +.form-picker { + flex: 1; + min-width: 0; +} + +.picker-value { + display: block; + text-align: right; + font-size: 28rpx; + font-weight: 700; + color: var(--ink); +} + +.picker-placeholder { + color: var(--muted); + font-weight: 400; +} + +/* ===== 底部固定操作栏 ===== */ +/* 纸面与白面填充差只有 1.05:1,靠一道小偏移小模糊的上向暖墨阴影托住边界 */ +.footer-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); + background: var(--white); + box-shadow: 0 -2rpx 8rpx rgba(63, 43, 25, 0.07); +} + +.footer-bar-duo { + display: flex; + gap: 24rpx; +} + +/* 保存是本页唯一的实心暖橙块,独占「全页同类实心块不超过一处」的额度 */ +.primary-btn { + flex: 1; + margin: 0; + padding: 24rpx 0; + border: none; + border-radius: 999rpx; + background: var(--brand-strong); + box-shadow: var(--shadow-cta); + color: var(--white); + font-size: 30rpx; + font-weight: 700; + letter-spacing: 2rpx; + line-height: 1.4; +} + +.primary-btn::after { + border: none; +} + +/* 取消:空心胶囊,靠--sep 环 + 透明底表达"次要动作",不占用实心额度 */ +.ghost-btn { + flex: 1; + margin: 0; + padding: 22rpx 0; + border: 2rpx solid var(--sep); + border-radius: 999rpx; + background: transparent; + color: var(--ink-2); + font-size: 30rpx; + font-weight: 500; + letter-spacing: 2rpx; + line-height: 1.4; +} + +.ghost-btn::after { + border: none; +} \ No newline at end of file diff --git a/pages/mine/comp_address/comp_address.js b/pages/mine/comp_address/comp_address.js deleted file mode 100644 index 16682b6..0000000 --- a/pages/mine/comp_address/comp_address.js +++ /dev/null @@ -1,195 +0,0 @@ -// pages/mine/comp_address/comp_address.js -Component({ - properties: { - visible: { - type: Boolean, - value: false - } - }, - - data: { - addresses: [], - editData: { - _index: -1, - label: '', - province: '', - city: '', - district: '', - address: '', - postcode: '', - isDefault: false - }, - isEditing: false - }, - - observers: { - visible(newVal) { - if (newVal) { - this.initAddressData() - } - } - }, - - methods: { - initAddressData() { - const app = getApp() - const user = app.globalData.user || {} - const addresses = user.addresses || [] - this.setData({ - addresses: addresses.map(item => ({ ...item })), - isEditing: false, - editData: { - _index: -1, - label: '', - province: '', - city: '', - district: '', - address: '', - postcode: '', - isDefault: false - } - }) - }, - - onClose() { - this.triggerEvent('close') - }, - - onSheetTap() {}, - - onAddAddress() { - this.setData({ - isEditing: true, - editData: { - _index: -1, - label: '', - province: '', - city: '', - district: '', - address: '', - postcode: '', - isDefault: this.data.addresses.length === 0 - } - }) - }, - - onEditAddress(e) { - const index = e.currentTarget.dataset.index - const item = this.data.addresses[index] - this.setData({ - isEditing: true, - editData: { - _index: index, - label: item.label || '', - province: item.province || '', - city: item.city || '', - district: item.district || '', - address: item.address || '', - postcode: item.postcode || '', - isDefault: item.isDefault || false - } - }) - }, - - onDeleteAddress(e) { - const index = e.currentTarget.dataset.index - wx.showModal({ - title: '提示', - content: '确定删除该地址吗?', - success: (res) => { - if (res.confirm) { - const addresses = this.data.addresses.slice() - addresses.splice(index, 1) - if (addresses.length > 0 && !addresses.some(a => a.isDefault)) { - addresses[0].isDefault = true - } - this.setData({ addresses }) - this.triggerEvent('save', { addresses }) - } - } - }) - }, - - onSetDefault(e) { - const index = e.currentTarget.dataset.index - const addresses = this.data.addresses.map((item, i) => ({ - ...item, - isDefault: i === index - })) - this.setData({ addresses }) - this.triggerEvent('save', { addresses }) - }, - - onBackToList() { - this.setData({ isEditing: false }) - }, - - onRegionChange(e) { - const region = e.detail.value - this.setData({ - 'editData.province': region[0] || '', - 'editData.city': region[1] || '', - 'editData.district': region[2] || '' - }) - }, - - onFieldInput(e) { - const field = e.currentTarget.dataset.field - this.setData({ - [`editData.${field}`]: e.detail.value - }) - }, - - onDefaultChange(e) { - this.setData({ - 'editData.isDefault': e.detail.value - }) - }, - - onSaveEdit() { - const editData = this.data.editData - if (!editData.label.trim()) { - wx.showToast({ title: '请输入地址标签', icon: 'none' }) - return - } - if (!editData.province || !editData.city || !editData.district) { - wx.showToast({ title: '请选择所在地区', icon: 'none' }) - return - } - if (!editData.address.trim()) { - wx.showToast({ title: '请输入详细地址', icon: 'none' }) - return - } - - const addresses = this.data.addresses.slice() - const newItem = { - label: editData.label.trim(), - province: editData.province, - city: editData.city, - district: editData.district, - address: editData.address.trim(), - postcode: editData.postcode.trim(), - isDefault: editData.isDefault - } - - if (editData.isDefault) { - addresses.forEach(item => { item.isDefault = false }) - } - - if (editData._index >= 0) { - addresses[editData._index] = newItem - } else { - if (addresses.length === 0) { - newItem.isDefault = true - } - addresses.push(newItem) - } - - this.setData({ - addresses, - isEditing: false - }) - this.triggerEvent('save', { addresses }) - } - } -}) diff --git a/pages/mine/comp_address/comp_address.wxml b/pages/mine/comp_address/comp_address.wxml deleted file mode 100644 index 71d151e..0000000 --- a/pages/mine/comp_address/comp_address.wxml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - {{isEditing ? (editData._index >= 0 ? '编辑地址' : '新增地址') : '邮寄地址'}} - - - - - - - - - 暂无地址,点击添加 - - - - - - - {{item.label}} - 默认 - - - {{item.province}} {{item.city}} {{item.district}} - {{item.address}} - 邮编:{{item.postcode}} - - - - - - {{item.isDefault ? '默认地址' : '设为默认'}} - - - - 删除 - - - - - - - - - - 地址标签 - - - - - 所在地区 - - - {{editData.province || '请选择地区'}}{{editData.city ? ' ' + editData.city : ''}}{{editData.district ? ' ' + editData.district : ''}} - - - - - - 详细地址 - - - - - 设为默认地址 - - - - - - - - - - - - - - - - - diff --git a/pages/mine/comp_address/comp_address.wxss b/pages/mine/comp_address/comp_address.wxss deleted file mode 100644 index b7cf4f1..0000000 --- a/pages/mine/comp_address/comp_address.wxss +++ /dev/null @@ -1,264 +0,0 @@ -.address-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: 1000; - display: flex; - flex-direction: column; - justify-content: flex-end; -} - -.address-sheet { - background: #FFFFFF; - border-radius: 32rpx 32rpx 0 0; - padding-bottom: env(safe-area-inset-bottom); - animation: slideUp 0.3s ease; - max-height: 85vh; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -@keyframes slideUp { - from { transform: translateY(100%); } - to { transform: translateY(0); } -} - -.address-header { - display: flex; - align-items: center; - justify-content: center; - padding: 32rpx; - position: relative; - border-bottom: 1rpx solid #F5F5F5; - flex-shrink: 0; -} - -.address-title { - font-size: 32rpx; - font-weight: 600; - color: #1A1A1A; -} - -.address-back { - position: absolute; - left: 32rpx; - top: 50%; - transform: translateY(-50%); - padding: 8rpx; -} - -.address-close { - position: absolute; - right: 32rpx; - top: 50%; - transform: translateY(-50%); - padding: 8rpx; -} - -.address-body { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -.address-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 120rpx 32rpx; -} - -.address-empty-text { - margin-top: 24rpx; - font-size: 28rpx; - color: #999999; -} - -.address-list { - padding: 24rpx 32rpx; -} - -.address-card { - background: #FAFAFA; - border-radius: 16rpx; - padding: 24rpx; - margin-bottom: 24rpx; -} - -.address-card-main { - margin-bottom: 16rpx; -} - -.address-card-top { - display: flex; - align-items: center; - gap: 16rpx; - margin-bottom: 12rpx; -} - -.address-label { - font-size: 30rpx; - font-weight: 600; - color: #1A1A1A; -} - -.address-default-tag { - font-size: 22rpx; - color: #D4A853; - background: rgba(255, 133, 0, 0.1); - padding: 4rpx 12rpx; - border-radius: 8rpx; -} - -.address-card-content { - display: flex; - flex-direction: column; - gap: 8rpx; -} - -.address-region { - font-size: 28rpx; - color: #666666; -} - -.address-detail { - font-size: 28rpx; - color: #1A1A1A; - line-height: 1.5; -} - -.address-postcode { - font-size: 24rpx; - color: #999999; -} - -.address-card-actions { - display: flex; - align-items: center; - justify-content: space-between; - border-top: 1rpx solid #EEEEEE; - padding-top: 16rpx; -} - -.address-action-default, -.address-action-delete { - display: flex; - align-items: center; - gap: 8rpx; - padding: 8rpx; -} - -.action-text { - font-size: 26rpx; - color: #666666; -} - -.action-text.active { - color: #D4A853; -} - -.address-footer { - padding: 4rpx 32rpx 48rpx; - flex-shrink: 0; -} - -.address-list-footer, -.address-edit-footer { - display: flex; - gap: 24rpx; -} - -.address-add-btn { - flex: 1; - background: linear-gradient(135deg, #6B8E7B 0%, #6B8E7B 100%); - color: #FFFFFF; - font-size: 30rpx; - font-weight: 600; - padding: 28rpx; - border-radius: 20rpx; - border: none; - line-height: 1.5; - margin-top: 20rpx; -} - -.address-add-btn::after { - border: none; -} - -.address-cancel-btn { - flex: 1; - background: #F5F5F5; - color: #666666; - font-size: 30rpx; - font-weight: 600; - padding: 28rpx; - border-radius: 20rpx; - border: none; - line-height: 1.5; - margin-top: 20rpx; -} - -.address-cancel-btn::after { - border: none; -} - -.address-form { - padding: 0 32rpx; -} - -.form-item { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 100rpx; - border-bottom: 1rpx solid #F5F5F5; -} - -.form-item-switch { - justify-content: space-between; -} - -.form-label { - font-size: 30rpx; - color: #1A1A1A; - font-weight: 500; - flex-shrink: 0; - margin-right: 24rpx; -} - -.form-input { - flex: 1; - text-align: right; - font-size: 30rpx; - color: #1A1A1A; -} - -.form-picker { - flex: 1; - text-align: right; - font-size: 30rpx; - color: #666666; -} - -.address-save-btn { - flex: 1; - background: linear-gradient(135deg, #6B8E7B 0%, #6B8E7B 100%); - color: #FFFFFF; - font-size: 30rpx; - font-weight: 600; - padding: 28rpx; - border-radius: 20rpx; - border: none; - line-height: 1.5; - margin-top: 20rpx; -} - -.address-save-btn::after { - border: none; -} diff --git a/pages/mine/comp_profile/comp_profile.js b/pages/mine/comp_profile/comp_profile.js deleted file mode 100644 index c83c883..0000000 --- a/pages/mine/comp_profile/comp_profile.js +++ /dev/null @@ -1,135 +0,0 @@ -// pages/mine/comp_profile/comp_profile.js -Component({ - properties: { - visible: { - type: Boolean, - value: false - } - }, - - data: { - editData: { - name: '', - sex: 'male', - birth: '1970-1-1', - province: '', - city: '', - district: '', - phone: '' - }, - today: '' - }, - - lifetimes: { - attached() { - const today = new Date() - this.setData({ - today: `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` - }) - }, - ready() { - this.initEditData() - } - }, - - observers: { - visible(newVal) { - if (newVal) { - this.initEditData() - } - } - }, - - methods: { - initEditData() { - const app = getApp() - const user = app.globalData.user || {} - const profile = user.profile || {} - const location = user.location || {} - - let birth = '' - if (profile.birth) { - const date = new Date(profile.birth) - birth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` - } - - this.setData({ - editData: { - avatar: profile.avatar || '/images/home-yellow.png', - name: profile.name || '', - phone: profile.mobile || '', - sex: profile.sex || '', - birth: birth, - province: location.province || '', - city: location.city || '', - district: location.district || '' - } - }) - }, - - onClose() { - this.triggerEvent('close') - }, - - onSheetTap() {}, - - onChooseAvatar() { - wx.chooseMedia({ - count: 1, - mediaType: ['image'], - sourceType: ['album', 'camera'], - success: (res) => { - const tempFilePath = res.tempFiles[0].tempFilePath - this.setData({ - 'editData.avatar': tempFilePath - }) - } - }) - }, - - onSelectSex(e) { - const sex = e.currentTarget.dataset.sex - this.setData({ - 'editData.sex': sex - }) - }, - - onBirthChange(e) { - this.setData({ - 'editData.birth': e.detail.value - }) - }, - - onRegionChange(e) { - const region = e.detail.value - this.setData({ - 'editData.province': region[0] || '', - 'editData.city': region[1] || '', - 'editData.district': region[2] || '' - }) - }, - - onFieldInput(e) { - const field = e.currentTarget.dataset.field - this.setData({ - [`editData.${field}`]: e.detail.value - }) - }, - - onSave() { - const editData = this.data.editData - - if (editData.idnumber && editData.idnumber.length !== 18) { - wx.showToast({ - title: '身份证号应为18位', - icon: 'none' - }) - return - } - - this.triggerEvent('save', { - editData: editData - }) - } - } -}) \ No newline at end of file diff --git a/pages/mine/comp_profile/comp_profile.wxml b/pages/mine/comp_profile/comp_profile.wxml deleted file mode 100644 index b2d2605..0000000 --- a/pages/mine/comp_profile/comp_profile.wxml +++ /dev/null @@ -1,65 +0,0 @@ - - - - 个人信息 - - - - - - - - 头像 - - - - - - - - 姓名 - - - - - 性别 - - - 男 - - - 女 - - - - - - 出生日期 - - - {{editData.birth || '请选择出生日期'}} - - - - - - 所在地区 - - - {{editData.province || '请选择地区'}}{{editData.city ? ',' + editData.city : ''}}{{editData.district ? ',' + editData.district : ''}} - - - - - - 手机号 - {{editData.phone || '未绑定'}} - - - - - - - - - \ No newline at end of file diff --git a/pages/mine/comp_profile/comp_profile.wxss b/pages/mine/comp_profile/comp_profile.wxss deleted file mode 100644 index 0250918..0000000 --- a/pages/mine/comp_profile/comp_profile.wxss +++ /dev/null @@ -1,178 +0,0 @@ -.profile-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - z-index: 1000; - display: flex; - flex-direction: column; - justify-content: flex-end; -} - -.profile-sheet { - background: #FFFFFF; - border-radius: 32rpx 32rpx 0 0; - animation: slideUp 0.3s ease; - max-height: 85vh; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -@keyframes slideUp { - from { transform: translateY(100%); } - to { transform: translateY(0); } -} - -.profile-header { - display: flex; - align-items: center; - justify-content: center; - padding: 32rpx; - position: relative; - border-bottom: 1rpx solid #F5F5F5; - flex-shrink: 0; -} - -.profile-title { - font-size: 32rpx; - font-weight: 600; - color: #1A1A1A; -} - -.profile-close { - position: absolute; - right: 32rpx; - top: 50%; - transform: translateY(-50%); - padding: 8rpx; -} - -.profile-body { - padding: 0 32rpx; - flex: 1; - overflow-y: auto; -} - -.profile-item { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 100rpx; - border-bottom: 1rpx solid #F5F5F5; -} - -.profile-item-avatar { - justify-content: space-between; -} - -.profile-item-last { - border-bottom: none; -} - -.profile-label { - font-size: 30rpx; - color: #1A1A1A; - font-weight: 500; - flex-shrink: 0; - margin-right: 24rpx; -} - -.profile-input { - flex: 1; - text-align: right; - font-size: 30rpx; - color: #1A1A1A; -} - -.picker { - flex: 1; - text-align: right; - font-size: 30rpx; - color: #666666; -} - -.profile-value { - font-size: 30rpx; - color: #666666; -} - -.profile-avatar-wrap { - display: flex; - align-items: center; - gap: 16rpx; -} - -.profile-avatar { - width: 96rpx; - height: 96rpx; - border-radius: 50%; - background: #F5F5F5; -} - -.profile-sex-wrap { - display: flex; - gap: 20rpx; -} - -.profile-sex-tag { - padding: 10rpx 48rpx; - border-radius: 8rpx; - background: #F5F5F5; -} - -.profile-sex-tag-active { - background: linear-gradient(135deg, #D4A853 0%, #D4A853 100%); -} - -.profile-sex-text { - font-size: 28rpx; - color: #666666; -} - -.profile-sex-tag-active .profile-sex-text { - color: #FFFFFF; -} - -.profile-footer { - padding: 4rpx 32rpx 48rpx; - flex-shrink: 0; - display: flex; - gap: 24rpx; -} - -.profile-cancel-btn { - flex: 1; - background: #F5F5F5; - color: #666666; - font-size: 30rpx; - font-weight: 600; - padding: 28rpx; - border-radius: 20rpx; - border: none; - line-height: 1.5; - margin-top: 20rpx; -} - -.profile-cancel-btn::after { - border: none; -} - -.profile-save-btn { - flex: 1; - background: linear-gradient(135deg, #6B8E7B 0%, #6B8E7B 100%); - color: #FFFFFF; - font-size: 30rpx; - font-weight: 600; - padding: 28rpx; - border-radius: 20rpx; - border: none; - line-height: 1.5; - margin-top: 20rpx; -} - -.profile-save-btn::after { - border: none; -} \ No newline at end of file diff --git a/pages/mine/mine.js b/pages/mine/mine.js index 453f3f8..3d65e9b 100644 --- a/pages/mine/mine.js +++ b/pages/mine/mine.js @@ -3,11 +3,13 @@ const API = require('../../utils/api.js') Page({ data: { userInfo: { - // 默认头像:暖墨色品牌标记(home-ink #9B4508)。 - // home.png 本体是 tabBar 未选中态的灰色 #8A8A8A,故另存一份重上色位图; - // 与 ai-ink.png 同一做法(位图着色走不了 CSS 变量,改色须手改,见 DESIGN.md §7.3) - avatar: '/images/home-ink.png', + // 留空表示"没有真头像",模板据此渲染暖橙实心底 + 白色 user 线标的默认头像 + // (与首页 .brand-mark 同源)。默认头像不走位图:位图着色读不到 CSS 变量, + // 改主色时须整图重上色(见 DESIGN.md §7.3),用 t-icon + 令牌则自动跟随 + avatar: '', name: '用户', + // 身份行副行,loadUserInfo 时由手机号脱敏填入 + account: '', phone: '', sex: '', birth: '', @@ -16,16 +18,21 @@ Page({ ssn: '', isLoggedIn: false }, - profileVisible: false, + // 菜单分组:按"用途"而非"是不是陪诊"分,三组才立得住。 + // 图标与首页同目的地同图形(同一含义只对应一个表达,从色值延伸到图标): + // 预约记录 calendar-1、健康档案 user-checked、联系我们 call 均与首页一致。 + // requiresLogin 的组在未登录时整组隐藏——这些入口都依赖用户身份 menuGroups: [{ - groupName: '个人服务', + groupName: '我的服务', + requiresLogin: true, items: [{ - name: '个人信息', - icon: 'verify', - bindtap: 'onTapProfile' + // 陪诊产品的「我的」页核心是订单,故排全页第一项 + name: '预约记录', + icon: 'calendar-1', + bindtap: 'onTapEscortRecord' }, { name: '健康档案', - icon: 'attach', + icon: 'user-checked', bindtap: 'onTapHealth' }, { name: '邮寄地址', @@ -33,14 +40,26 @@ Page({ bindtap: 'onTapAddress' }] }, { - groupName: '陪诊', + groupName: '帮助与支持', + requiresLogin: false, items: [{ - name: '预约记录', - icon: 'assignment', - bindtap: 'onTapEscortRecord' + name: '联系我们', + icon: 'call', + bindtap: 'onTapContact' + }, { + // 口径统一为「服务指南」(首页区块标题行、列表页说明卡都用此名), + // 不另起「陪诊指南」这一第三叫法;与列表页「服务指南 │ 用户协议」同一配对 + name: '服务指南', + icon: 'book-open', + bindtap: 'onTapGuide' + }, { + name: '用户协议', + icon: 'secured', + bindtap: 'onTapAgreement' }] }, { groupName: '其他', + requiresLogin: false, items: [{ name: '关于我们', icon: 'info-circle', @@ -70,16 +89,41 @@ Page({ this.setData({ 'userInfo.isLoggedIn': true, 'userInfo.name': profile.name || '用户', + // 头像补读后端返回值;取不到留空,由模板渲染暖橙默认头像。 + // 注:profileedit 页里新选的图目前不会落库——utils/api.js 没有文件上传接口, + // 微信返回的是本地临时路径,存进库里也会失效,所以这里只修"显示"这一半 + 'userInfo.avatar': profile.avatar || '', 'userInfo.phone': profile.mobile || '', + // 身份行副行(微信那行是微信号):本应用没有账号体系, + // 等价标识是手机号,脱敏成 138****1234 后再上屏 + 'userInfo.account': this.maskMobile(profile.mobile || ''), 'userInfo.sex': profile.sex || '', 'userInfo.birth': profile.birth ? this.formatDate(profile.birth) : '', 'userInfo.email': profile.email || '', 'userInfo.idnumber': profile.idnumber || '', 'userInfo.ssn': profile.ssn || '' }) + } else { + // 未登录(含刚退出登录):身份行收敛回默认态。 + // 少了这个分支,globalData.user 被清空后页面仍停在"已登录"的样子—— + // 「我的服务」组和退出块都不会消失 + this.setData({ + 'userInfo.isLoggedIn': false, + 'userInfo.avatar': '', + 'userInfo.name': '用户', + 'userInfo.account': '', + 'userInfo.phone': '' + }) } }, + // 手机号脱敏:11 位取前三后四;位数不符时原样返回,不制造错位星号 + maskMobile(mobile) { + const digits = String(mobile).replace(/\D/g, '') + if (digits.length !== 11) return mobile || '' + return `${digits.slice(0, 3)}****${digits.slice(7)}` + }, + formatDate(date) { if (!date) return '' const d = new Date(date) @@ -89,49 +133,20 @@ Page({ return `${year}-${month}-${day}` }, + // 顶部身份行整行可点,但未登录时不该跳进"填完才被拦"的资料页 + // (个人信息与邮寄地址改真页面后系统返回键归页面栈管, + // 不再像原来的浮层那样被系统返回键直接退出小程序) onTapProfile() { - this.setData({ profileVisible: true }) + if (!this.data.userInfo.isLoggedIn) return + wx.navigateTo({ + url: '/pages/mine/profileedit' + }) }, onTapAddress() { - this.setData({ addressVisible: true }) - }, - - onAddressClose() { - this.setData({ addressVisible: false }) - }, - - onAddressSave(e) { - const app = getApp() - const user = app.globalData.user - - if (!user || !user._id) { - wx.showToast({ title: '请先登录', icon: 'none' }) - return - } - - wx.showLoading({ title: '保存中...' }) - - const updateData = { - _id: user._id, - addresses: e.detail.addresses - } - - API.user.update(updateData) - .then((data) => { - if (data.code === 0) { - app.globalData.user = data.data.user - wx.showToast({ title: '保存成功', icon: 'success' }) - } else { - wx.showToast({ title: data.msg || '保存失败', icon: 'none' }) - } - }) - .catch(() => { - wx.showToast({ title: '网络请求失败', icon: 'none' }) - }) - .finally(() => { - wx.hideLoading() - }) + wx.navigateTo({ + url: '/pages/mine/addressedit' + }) }, onTapHealth() { @@ -140,26 +155,39 @@ Page({ }) }, - onHealthClose() { - this.setData({ healthVisible: false }) - }, - onTapEscortRecord() { wx.navigateTo({ url: '/pages/escort/recordlist' }) }, + // 帮助与支持:联系我们(页面已存在,与首页「常用功能」同目的地) + onTapContact() { + wx.navigateTo({ + url: '/pages/mine/contact' + }) + }, + + // 帮助与支持:服务指南(与首页区块标题行、列表页说明卡同一页面) + onTapGuide() { + wx.navigateTo({ + url: '/pages/escort/guide' + }) + }, + + // 帮助与支持:用户协议(与陪诊服务列表页的「用户协议」同一页面) + onTapAgreement() { + wx.navigateTo({ + url: '/pages/escort/agreement' + }) + }, + onTapAboutUs() { wx.navigateTo({ url: '/pages/mine/about' }) }, - onTapSetting() { - this.setData({ settingVisible: true }) - }, - // 页脚 B 端合作链接:暂无独立页面,统一转接人工客服 onFooterTap() { wx.makePhoneCall({ @@ -167,58 +195,38 @@ Page({ }) }, - onSettingClose() { - this.setData({ settingVisible: false }) + // 退出登录:二次确认 → 调后端登出 → 清全局与本地缓存 + onLogout() { + wx.showModal({ + title: '退出登录', + content: '退出后需要重新授权手机号,才能查看预约记录与健康档案', + confirmText: '退出', + // confirmColor 是属性值,读不到 CSS 变量,取值须与令牌同步(DESIGN.md §5.1) + confirmColor: '#B8520A', + success: (res) => { + if (!res.confirm) return + wx.showLoading({ title: '退出中...' }) + // 必须先调接口:token 由 request.js 从 globalData.user 里取, + // 先清全局对象就带不上 token 了 + API.user.signout({}) + .then(() => this.doLogout()) + // 接口失败也要把本地退干净,不把用户卡在"已登录"的假象里 + .catch(() => this.doLogout()) + .finally(() => wx.hideLoading()) + } + }) }, - onProfileClose() { - this.setData({ profileVisible: false }) - }, - - onProfileSave(e) { - const newUserInfo = e.detail.editData + doLogout() { const app = getApp() - const user = app.globalData.user - - if (!user || !user._id) { - wx.showToast({ title: '请先登录', icon: 'none' }) - return - } - - wx.showLoading({ title: '保存中...' }) - - const updateData = { - _id: user._id, - profile: { - name: newUserInfo.name, - sex: newUserInfo.sex, - birth: newUserInfo.birth ? new Date(newUserInfo.birth).toISOString() : null, - mobile: newUserInfo.phone - }, - location: { - province: newUserInfo.province, - city: newUserInfo.city, - district: newUserInfo.district, - }, - } - - API.user.update(updateData) - .then((data) => { - if (data.code === 0) { - app.globalData.user = data.data.user - this.loadUserInfo() - wx.showToast({ title: '保存成功', icon: 'success' }) - this.setData({ profileVisible: false }) - } else { - wx.showToast({ title: data.msg || '保存失败', icon: 'none' }) - } - }) - .catch(() => { - wx.showToast({ title: '网络请求失败', icon: 'none' }) - }) - .finally(() => { - wx.hideLoading() - }) + app.globalData.user = null + wx.removeStorageSync('user_phonenumber') + // 落一个"已主动退出"标记:app.js 的 onShow 每次都会静默重登 + // (wxsignin 只凭 code 换 openid 即可重新签发 token),不拦这一下, + // 退出就形同虚设——切后台再回来又变成登录态 + wx.setStorageSync('has_logout', true) + this.loadUserInfo() + wx.showToast({ title: '已退出登录', icon: 'success' }) }, handleLogin(e) { @@ -235,6 +243,8 @@ Page({ if (data.code == 0) { const app = getApp() app.globalData.user = data.data.user + // 用户重新授权登录,解除对静默重登的拦截 + wx.removeStorageSync('has_logout') this.loadUserInfo() } }) diff --git a/pages/mine/mine.json b/pages/mine/mine.json index c1c4d1a..4e70649 100644 --- a/pages/mine/mine.json +++ b/pages/mine/mine.json @@ -1,11 +1,9 @@ { "navigationStyle": "custom", - "navigationBarTitleText": "Home", + "navigationBarTitleText": "我的", "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "usingComponents": { - "t-icon": "tdesign-miniprogram/icon/icon", - "comp-profile": "./comp_profile/comp_profile", - "comp-address": "./comp_address/comp_address" + "t-icon": "tdesign-miniprogram/icon/icon" } } \ No newline at end of file diff --git a/pages/mine/mine.wxml b/pages/mine/mine.wxml index 1e8a0ba..f6a85f5 100644 --- a/pages/mine/mine.wxml +++ b/pages/mine/mine.wxml @@ -1,33 +1,100 @@ - - - {{userInfo.name}} + + + + + + + + + + {{userInfo.isLoggedIn ? userInfo.name : '未登录'}} + + + + - + + + - - + + {{menuItem.name}} - - - - - \ No newline at end of file diff --git a/pages/mine/mine.wxss b/pages/mine/mine.wxss index 74801cd..eaa06d3 100644 --- a/pages/mine/mine.wxss +++ b/pages/mine/mine.wxss @@ -14,49 +14,179 @@ page { padding: 0 32rpx calc(140rpx + env(safe-area-inset-bottom)); } -/* 头部:头像 + 昵称,直接落在纸面上(与首页 hero 同一语言) */ +/* 顶部身份行:头像、昵称、出口三项左对齐浮在纸面上,与下方三个区块 + 共用同一条 32rpx 左基线,并复现首页 hero 的「纸面上 + 主 CTA」语言。 + 刻意不做成白卡——身份行与功能卡不是一类东西, + 按硬约束 3「需区分时先改结构,不要加颜色」,这里用"去卡"而非换色区分; + 若改用暖橙框,会与全站浅橙卡"区块收口"的语义打架, + 且未登录时浅橙底上再叠实心橙胶囊属暖色叠暖色,故不加色。 + 微信胶囊底沿约落在 152rpx,上距现取 176rpx(余量 24rpx)。 + 这是安全距不是设计留白:下限约 160rpx,再小就会与胶囊挤在一起。 + 纵向 32rpx 内距给身份行本身留出上下呼吸, + 同时把整行热区从 128rpx 撑到 192rpx(含内距),未登录点整行也更好点中 */ .header-card { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - padding: 120rpx 0 40rpx; + margin: 176rpx 0 24rpx; + padding: 32rpx 0; +} + +/* 无框行的按下反馈:与首页 .section-link:active 同一手法(整体淡出), + 不引入新色值;仅已登录时可点,未登录时 hover-class 传 'none' */ +.header-card-hover { + opacity: 0.7; } .avatar { width: 128rpx; height: 128rpx; - border-radius: 32rpx; + margin-right: 28rpx; + /* 圆角方形而非正圆,与微信「我」页头像同形 */ + border-radius: 24rpx; + flex-shrink: 0; + /* 默认头像底座要把 t-icon 居中,位图头像不受影响 */ + display: flex; + align-items: center; + justify-content: center; + /* 默认头像底座带 2rpx 描边,border-box 保证尺寸仍是 128rpx 不被撑到 132rpx */ + box-sizing: border-box; } +/* 默认头像底座:浅橙衬底 + 暖陶土线标,走项目既有的「图标衬底」档 + (同 .quick-icon 一类),而不是首页 .brand-mark 的实心橙档。 + 理由是面积:128rpx 实心橙是 .brand-mark(56rpx) 的 5.2 倍, + 已从"小体量印记"跨到"色块",而它承载的只是"头像待设置"这一低价值信息; + 且 --brand-strong 在本项目的语义是主操作/选中态,让占位头像占用它会误导。 + 浅橙块按既有规矩靠 2rpx --brand-line 描边托形、不加阴影; + --brand-ink 落 --brand-soft 上 5.96:1,比白字压 #B8520A 的 4.94:1 更高 */ +.avatar-default { + background: var(--brand-soft); + border: 2rpx solid var(--brand-line); +} + +/* 文字块:主行昵称与副行账号分别贴近头像的上、下沿(各内收 12rpx), + 中间的空隙交给 space-between 撑开——它随头像高度自适应, + 比写死 margin 稳。align-self: stretch 让文字块撑满 128rpx 头像高度。 + min-width 归零,长昵称/长账号才能在右侧出口前收成省略号 */ +.header-info { + flex: 1; + min-width: 0; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: space-between; + /* 两行各向内收 12rpx:与头像边缘严格齐平时读起来"被撑满了", + 略微内收后间隙从 57rpx 收到约 33rpx,视觉上更聚拢 */ + padding: 12rpx 0; +} + +/* 未登录时只有昵称一行,space-between 会把它顶到最上,故改回居中 */ +.header-info-single { + justify-content: center; +} + +/* 行高收到 1.15(约半行距 3rpx):1.3 的半行距会把字形从块边缘推进 5.5rpx, + "顶部对齐"就会变成"看着差一点"。1.15 把视觉边缘压到与头像边缘相差 3rpx 内, + 同时仍留一点余量,不至于配合 overflow: hidden 把字形切掉 */ .user-name { - margin-top: 24rpx; font-size: 36rpx; font-weight: 700; color: var(--ink); + line-height: 1.15; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } -/* 菜单组:首页「白卡」语言——白面 + 2rpx 发丝边 + 紧阴影 */ -.menu-section + .menu-section { +/* 副行账号:muted,比主行小两档,与微信副行同层级。 + 与主行的间距不再用 margin——已由 .header-info 的 space-between 撑开 */ +.user-account { + font-size: 26rpx; + color: var(--muted); + line-height: 1.15; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/* 身份行右侧 chevron:与菜单行的 .menu-arrow 同形同色, + 整行因此读成"菜单里的一行",点击暗示不必再添文字 */ +.header-arrow { + flex-shrink: 0; + color: var(--muted); +} + +/* 未登录时的登录胶囊:右对齐的一枚小胶囊。 + 它是本页唯一的实心暖橙块,独占「全页同类实心块不超过一处」的额度; + 登录成功后整枚换成 chevron,「我的服务」区块随之出现 */ +.login-btn { + width: 176rpx; + margin: 0; + padding: 18rpx 0; + border: none; + border-radius: 999rpx; + background: var(--brand-strong); + box-shadow: var(--shadow-cta); + color: var(--white); + font-size: 28rpx; + font-weight: 700; + letter-spacing: 2rpx; + line-height: 1.4; + flex-shrink: 0; +} + +.login-btn::after { + border: none; +} + +/* 区块间距:卡片改通栏、去掉区块抬头后,组与组之间只靠三样东西分界—— + 上一块的 2rpx 下边线、这 24rpx 纸面留白、下一块的 2rpx 上边线。 + 留白与身份行的下距同值,全页只有这一种竖向节奏。 + 这是全页唯一收窄过的一处:纸面与白面的填充差只有 1.05:1, + 分界其实全靠这一条纸面缝在看,所以不能收到贴边,24rpx(12px)是下限。 + 原「单竖条 + 标题」的抬头样式(.section-head/.section-bar/.section-title) + 已随标题一并删除,不留死代码 */ +.section + .section { margin-top: 24rpx; } .menu-list { + /* 通栏白卡:左右顶到屏幕两侧,做法与 itemlist 的服务分组同源 + (margin 负向抵消容器 32rpx 内距)。圆角必须为 0, + 否则满宽块的圆角会切在屏幕边上;上、下各一条 2rpx 发丝线托形。 + 行内容靠 .menu-item 自己的 32rpx 内距回到同一左基线, + 组内分隔线(.menu-item::after)则左缩进到文字左缘,不压到图标 */ + margin: 0 -32rpx; background: var(--white); - border: 2rpx solid var(--line); - border-radius: 28rpx; + border-top: 2rpx solid var(--line); + border-bottom: 2rpx solid var(--line); box-shadow: var(--shadow-card); - overflow: hidden; } .menu-item { + /* 给内嵌分隔线(::after)做定位参照 */ + position: relative; display: flex; align-items: center; padding: 32rpx; - border-bottom: 2rpx solid var(--line); } -.menu-item:last-child { - border-bottom: none; +/* 组内分隔线不横跨整行:左端从文字左缘起,避开图标和它的右侧间距; + 右端仍通到卡片边缘。 + 左缩进 = 行内距 32 + 图标 40 + 图标右距 24 = 96rpx */ +.menu-item::after { + content: ''; + position: absolute; + left: 96rpx; + right: 0; + bottom: 0; + height: 2rpx; + background: var(--line); +} + +.menu-item:last-child::after { + display: none; } .menu-item-hover { @@ -72,8 +202,9 @@ page { .menu-text { flex: 1; font-size: 30rpx; - font-weight: 500; - color: var(--ink); + font-weight: 400; + /* 比标题层的 --ink 轻一档的暖墨,弱化"导航项"的视觉重量 */ + color: var(--ink-2); } .menu-arrow { @@ -81,27 +212,31 @@ page { color: var(--muted); } -/* 登录按钮:与首页主 CTA 同语言(实心暖橙胶囊 + 白字 + CTA 阴影) */ -.login-section { - padding: 32rpx 0; +/* 退出登录:单独一个通栏白块(与 .menu-list 同法、同语言), + 但只有一行、居中、无图标无 chevron——动作与导航的区分交给结构, + 不交给颜色(状态色不得用于按钮,见 DESIGN.md §三)。 + 上距 24rpx 沿用全页唯一的那一种竖向节奏 */ +.logout-block { + display: flex; + align-items: center; + justify-content: center; + margin: 24rpx -32rpx 0; + padding: 32rpx; + background: var(--white); + border-top: 2rpx solid var(--line); + border-bottom: 2rpx solid var(--line); + box-shadow: var(--shadow-card); } -.login-btn { - width: 100%; - padding: 28rpx; - border: none; - border-radius: 999rpx; - background: var(--brand-strong); - box-shadow: var(--shadow-cta); - color: var(--white); +/* 按下反馈与 .menu-item-hover 同源,不引入新色值 */ +.logout-block-hover { + background: var(--brand-soft); +} + +/* 字色走 --muted:退出是次要动作,不给它主色,也不用状态色 */ +.logout-text { font-size: 30rpx; - font-weight: 700; - letter-spacing: 2rpx; - line-height: 1.4; -} - -.login-btn::after { - border: none; + color: var(--muted); } /* 页脚:B 端合作文字链 + 版本信息,低调可读 */ diff --git a/pages/mine/profileedit.js b/pages/mine/profileedit.js new file mode 100644 index 0000000..bdb8b83 --- /dev/null +++ b/pages/mine/profileedit.js @@ -0,0 +1,150 @@ +// pages/mine/profileedit.js +const API = require('../../utils/api.js') + +Page({ + data: { + editData: { + avatar: '', + name: '', + sex: '', + birth: '', + province: '', + city: '', + district: '', + phone: '' + }, + today: '', + saving: false + }, + + onLoad() { + const today = new Date() + this.setData({ + today: `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` + }) + this.initEditData() + }, + + // 表单初值取自全局用户对象。页面栈上「我的」页的 onShow 读的也是它, + // 所以保存成功后必须回写 app.globalData.user,否则返回时看到的还是旧值 + initEditData() { + const app = getApp() + const user = app.globalData.user || {} + const profile = user.profile || {} + const location = user.location || {} + + let birth = '' + if (profile.birth) { + const date = new Date(profile.birth) + birth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` + } + + this.setData({ + editData: { + // 空字符串表示"无真头像",模板据此渲染浅橙底 + 暖陶土 user 线标的默认头像 + avatar: profile.avatar || '', + name: profile.name || '', + phone: profile.mobile || '', + sex: profile.sex || '', + birth, + province: location.province || '', + city: location.city || '', + district: location.district || '' + } + }) + }, + + onChooseAvatar() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album', 'camera'], + success: (res) => { + // 微信返回的是本地临时路径,没有文件上传接口可落库, + // 故只在本次编辑中生效 + this.setData({ + 'editData.avatar': res.tempFiles[0].tempFilePath + }) + } + }) + }, + + onSelectSex(e) { + this.setData({ + 'editData.sex': e.currentTarget.dataset.sex + }) + }, + + onBirthChange(e) { + this.setData({ + 'editData.birth': e.detail.value + }) + }, + + onRegionChange(e) { + const region = e.detail.value + this.setData({ + 'editData.province': region[0] || '', + 'editData.city': region[1] || '', + 'editData.district': region[2] || '' + }) + }, + + onFieldInput(e) { + const field = e.currentTarget.dataset.field + this.setData({ + [`editData.${field}`]: e.detail.value + }) + }, + + onSave() { + if (this.data.saving) return + + const editData = this.data.editData + const app = getApp() + const user = app.globalData.user + + if (!user || !user._id) { + wx.showToast({ title: '请先登录', icon: 'none' }) + return + } + + this.setData({ saving: true }) + wx.showLoading({ title: '保存中...' }) + + const updateData = { + _id: user._id, + profile: { + name: editData.name, + sex: editData.sex, + birth: editData.birth ? new Date(editData.birth).toISOString() : null, + mobile: editData.phone + }, + location: { + province: editData.province, + city: editData.city, + district: editData.district + } + } + + API.user.update(updateData) + .then((data) => { + if (data.code === 0) { + // 回写全局对象:「我的」页 onShow 依赖它刷新头像行与资料 + app.globalData.user = data.data.user + wx.showToast({ title: '保存成功', icon: 'success' }) + // 返回上一页(「我的」),由它的 onShow 重新读取全局对象 + wx.navigateBack() + } else { + wx.showToast({ title: data.msg || '保存失败', icon: 'none' }) + } + }) + .catch(() => { + wx.showToast({ title: '网络请求失败', icon: 'none' }) + }) + .finally(() => { + wx.hideLoading() + this.setData({ saving: false }) + }) + } +}) \ No newline at end of file diff --git a/pages/mine/comp_address/comp_address.json b/pages/mine/profileedit.json similarity index 62% rename from pages/mine/comp_address/comp_address.json rename to pages/mine/profileedit.json index 3fb9848..146f6c7 100644 --- a/pages/mine/comp_address/comp_address.json +++ b/pages/mine/profileedit.json @@ -1,6 +1,6 @@ { - "component": true, + "navigationBarTitleText": "个人信息", "usingComponents": { "t-icon": "tdesign-miniprogram/icon/icon" } -} +} \ No newline at end of file diff --git a/pages/mine/profileedit.wxml b/pages/mine/profileedit.wxml new file mode 100644 index 0000000..e37b9bd --- /dev/null +++ b/pages/mine/profileedit.wxml @@ -0,0 +1,77 @@ + + + + + + 头像 + + + + + + + + + + + 姓名 + + + + + 性别 + + + + 男 + + + 女 + + + + + + 出生日期 + + + {{editData.birth || '请选择出生日期'}} + + + + + + 所在地区 + + + {{editData.province || '请选择地区'}}{{editData.city ? ',' + editData.city : ''}}{{editData.district ? ',' + editData.district : ''}} + + + + + + + 手机号 + {{editData.phone || '未绑定'}} + + + + + + + + \ No newline at end of file diff --git a/pages/mine/profileedit.wxss b/pages/mine/profileedit.wxss new file mode 100644 index 0000000..8f6dae5 --- /dev/null +++ b/pages/mine/profileedit.wxss @@ -0,0 +1,184 @@ +page { + /* 配色令牌来自 app.wxss(全站唯一来源),本页不留副本 */ + background-color: var(--paper); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.container { + min-height: 100vh; + box-sizing: border-box; + /* 底部留出固定保存栏的高度 + 安全区,避免最后一行被盖住 */ + padding: 40rpx 32rpx calc(180rpx + env(safe-area-inset-bottom)); +} + +/* 通栏白块:与「我的」页 .menu-list 同源——白面 + 上下各 2rpx 发丝线 + + 中性阴影,圆角 0。负向 margin 抵消容器 32rpx 内距后左右顶到屏幕两侧, + 行内再用 32rpx 内距回到同一左基线,行分隔线因此自动通到底 */ +.form-card { + margin: 0 -32rpx; + background: var(--white); + border-top: 2rpx solid var(--line); + border-bottom: 2rpx solid var(--line); + box-shadow: var(--shadow-card); +} + +/* 行高约 87rpx(28rpx×1.4 + 上下 24rpx),贴近 iOS 44pt 触控目标 */ +.form-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx 32rpx; + border-bottom: 2rpx solid var(--line); +} + +.form-row-last { + border-bottom: none; +} + +.form-row-hover { + background: var(--brand-soft); +} + +/* 标签走 --muted、值走 --ink 700:层级靠明度与字重拉开, + 不给可点性上色(DESIGN.md §2.2「三档只表示文字层级」) */ +.form-label { + flex-shrink: 0; + margin-right: 24rpx; + font-size: 28rpx; + color: var(--muted); + letter-spacing: 1rpx; +} + +.form-input { + flex: 1; + min-width: 0; + text-align: right; + font-size: 28rpx; + font-weight: 700; + color: var(--ink); +} + +.form-placeholder { + color: var(--muted); + font-weight: 400; +} + +.form-value { + font-size: 28rpx; + font-weight: 700; + color: var(--ink); +} + +/* picker 需占满剩余宽度,值才能靠右对齐 */ +.form-picker { + flex: 1; + min-width: 0; +} + +.picker-value { + display: block; + text-align: right; + font-size: 28rpx; + font-weight: 700; + color: var(--ink); +} + +.picker-placeholder { + color: var(--muted); + font-weight: 400; +} + +.form-arrow { + flex-shrink: 0; + margin-left: 12rpx; + color: var(--muted); +} + +/* 头像:默认底座 = 浅橙衬底 + 2rpx --brand-line 描边(靠描边托形,不加阴影), + 与「我的」页 128rpx 头像同构,只是这里收到 96rpx */ +.avatar-wrap { + display: flex; + align-items: center; +} + +.avatar { + width: 96rpx; + height: 96rpx; + border-radius: 20rpx; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; +} + +.avatar-default { + background: var(--brand-soft); + border: 2rpx solid var(--brand-line); +} + +/* 性别:未选中是纸面底 + 发丝边 + --muted 字, + 选中翻转成软橙底 + 实心橙描边 + --brand-ink 字 */ +.sex-wrap { + display: flex; + gap: 20rpx; +} + +.sex-tag { + display: flex; + align-items: center; + justify-content: center; + min-width: 120rpx; + padding: 12rpx 0; + border-radius: 999rpx; + background: var(--paper); + border: 2rpx solid var(--line); +} + +.sex-text { + font-size: 28rpx; + color: var(--muted); +} + +.sex-tag-active { + background: var(--brand-soft); + border-color: var(--brand-strong); +} + +.sex-tag-active .sex-text { + color: var(--brand-ink); + font-weight: 700; +} + +/* 底部固定保存栏:纸面与白面填充差只有 1.05:1,靠一道小偏移小模糊的 + 上向暖墨阴影托住边界(与 itemdetail 的 --shadow-bar 同值) */ +.footer-bar { + position: fixed; + left: 0; + right: 0; + bottom: 0; + padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)); + background: var(--white); + box-shadow: 0 -2rpx 8rpx rgba(63, 43, 25, 0.07); +} + +.primary-btn { + margin: 0; + padding: 24rpx 0; + border: none; + border-radius: 999rpx; + background: var(--brand-strong); + box-shadow: var(--shadow-cta); + color: var(--white); + font-size: 30rpx; + font-weight: 700; + letter-spacing: 2rpx; + line-height: 1.4; +} + +.primary-btn::after { + border: none; +} + +.primary-btn-disabled { + opacity: 0.6; +} \ No newline at end of file