更新了AI页和我的页,一级页面都重构完成。
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
+2
-1
@@ -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` 系 | 暖橙令牌 + 状态四档 |
|
||||
|
||||
+8
-8
@@ -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` 待处理)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+38
-12
@@ -10,11 +10,11 @@
|
||||
<!-- 欢迎区:空会话时的落点 -->
|
||||
<view class="welcome-area" wx:if="{{messages.length === 0}}">
|
||||
<view class="welcome-icon">
|
||||
<t-icon name="chat" size="72rpx" color="#9B4508" />
|
||||
<image class="welcome-icon-img" src="/images/ai-ink.png" mode="aspectFit" />
|
||||
</view>
|
||||
<text class="welcome-title">AI陪诊助手</text>
|
||||
<text class="welcome-title">陪诊助手</text>
|
||||
<text class="welcome-desc">我是您的智能陪诊助手,可以为您解答陪诊相关问题</text>
|
||||
<view class="quick-questions">
|
||||
<view class="question-list">
|
||||
<view
|
||||
class="quick-item"
|
||||
wx:for="{{quickQuestions}}"
|
||||
@@ -48,7 +48,7 @@
|
||||
</view>
|
||||
|
||||
<view class="message-content">
|
||||
<view class="message-bubble">
|
||||
<view class="message-bubble" wx:if="{{item.contentType !== 'questions'}}">
|
||||
<t-chat-markdown
|
||||
wx:if="{{item.type === 'ai' && item.contentType === 'text'}}"
|
||||
class="message-markdown"
|
||||
@@ -64,9 +64,28 @@
|
||||
data-src="{{item.content}}"
|
||||
/>
|
||||
</view>
|
||||
<!-- 常见问题:由「常见问题」按钮触发,把问题清单直接铺进会话流里。
|
||||
复用欢迎区的白卡 + chevron 语言,点一下即发送该问题 -->
|
||||
<view wx:else class="question-list">
|
||||
<view
|
||||
class="quick-item"
|
||||
wx:for="{{item.content}}"
|
||||
wx:for-item="question"
|
||||
wx:key="*this"
|
||||
bindtap="onQuickQuestionTap"
|
||||
data-question="{{question}}"
|
||||
>
|
||||
<text>{{question}}</text>
|
||||
<t-icon name="chevron-right" size="26rpx" color="#9B4508" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- AI 声明:挂在每条 AI 回复的气泡正下方,替掉原来页面底部那行常驻声明
|
||||
(它太占底部空间)。放在 .message-content 内,与气泡同宽同左边缘 -->
|
||||
<text wx:if="{{item.type === 'ai'}}" class="message-note">内容由AI生成,仅供参考</text>
|
||||
(它太占底部空间)。放在 .message-content 内,与气泡同宽同左边缘。
|
||||
问题清单由本地生成、非 AI 内容,故不挂声明 -->
|
||||
<text
|
||||
wx:if="{{item.type === 'ai' && item.contentType !== 'questions'}}"
|
||||
class="message-note"
|
||||
>内容由AI生成,仅供参考</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -110,11 +129,18 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 清空入口:紧贴输入区下方——拇指最容易够到的位置,操作方便。
|
||||
低频但破坏性,故做成只描边不填充的暖橙小按钮(圆角与输入框同为 20rpx),
|
||||
与输入区右侧浅橙底的发送键区分开,不做成裸文字链 -->
|
||||
<view class="clear-btn" wx:if="{{messages.length > 0}}" bindtap="clearChat">
|
||||
<t-icon name="delete" size="26rpx" color="#9B4508" />
|
||||
<text class="clear-btn-text">清空聊天</text>
|
||||
<!-- 辅助入口:紧贴输入区下方——拇指最容易够到的位置,操作方便。
|
||||
两者都是低频动作,故做成只描边不填充的暖橙小按钮(圆角与输入框同为 20rpx),
|
||||
与输入区右侧浅橙底的发送键区分开,不做成裸文字链。
|
||||
「常见问题」在左、「清空聊天」在右:破坏性动作收在最右端 -->
|
||||
<view class="tool-bar" wx:if="{{messages.length > 0}}">
|
||||
<view class="tool-btn" bindtap="showQuickQuestions">
|
||||
<t-icon name="help-circle" size="26rpx" color="#9B4508" />
|
||||
<text class="tool-btn-text">常见问题</text>
|
||||
</view>
|
||||
<view class="tool-btn" bindtap="clearChat">
|
||||
<t-icon name="delete" size="26rpx" color="#9B4508" />
|
||||
<text class="tool-btn-text">清空聊天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
+44
-19
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"navigationBarTitleText": "邮寄地址",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<view class="container">
|
||||
<!-- 原先这里是 mine 页上的 comp_address 浮层(fixed + 遮罩 + 底部 sheet),
|
||||
不占页面栈,系统返回键会直接退出小程序;改为真页面后由微信原生
|
||||
导航栏接管返回。列表 / 表单两个状态仍在同页切换(外层不再有壳),
|
||||
切换时同步改导航栏标题 -->
|
||||
|
||||
<!-- ===== 列表态 ===== -->
|
||||
<block wx:if="{{!isEditing}}">
|
||||
<view class="addr-list" wx:if="{{addresses.length > 0}}">
|
||||
<view
|
||||
class="addr-card {{index === addresses.length - 1 ? 'addr-card-last' : ''}}"
|
||||
wx:for="{{addresses}}"
|
||||
wx:key="index"
|
||||
>
|
||||
<!-- 主体可点进编辑:右侧 chevron 是点击暗示,不做无暗示的整卡可点 -->
|
||||
<view class="addr-main" bindtap="onEditAddress" data-index="{{index}}" hover-class="addr-main-hover">
|
||||
<view class="addr-info">
|
||||
<view class="addr-top">
|
||||
<text class="addr-label">{{item.label}}</text>
|
||||
<text class="addr-default-tag" wx:if="{{item.isDefault}}">默认</text>
|
||||
</view>
|
||||
<text class="addr-region">{{item.province}} {{item.city}} {{item.district}}</text>
|
||||
<text class="addr-detail">{{item.address}}</text>
|
||||
<text class="addr-postcode" wx:if="{{item.postcode}}">邮编:{{item.postcode}}</text>
|
||||
</view>
|
||||
<t-icon name="chevron-right" class="addr-arrow" size="32rpx" />
|
||||
</view>
|
||||
|
||||
<view class="addr-actions">
|
||||
<view class="addr-action" bindtap="onSetDefault" data-index="{{index}}">
|
||||
<!-- 状态同时用图形与文字传达,不只靠颜色(WCAG 1.4.1);
|
||||
t-icon 的 color 读不到变量,故用 CSS 类着色而不是写死 hex -->
|
||||
<t-icon wx:if="{{item.isDefault}}" name="check-circle-filled" class="addr-action-icon addr-action-icon-active" size="32rpx" />
|
||||
<t-icon wx:else name="circle" class="addr-action-icon" size="32rpx" />
|
||||
<text class="addr-action-text {{item.isDefault ? 'addr-action-text-active' : ''}}">{{item.isDefault ? '默认地址' : '设为默认'}}</text>
|
||||
</view>
|
||||
<view class="addr-action" bindtap="onDeleteAddress" data-index="{{index}}">
|
||||
<t-icon name="delete" class="addr-action-icon" size="32rpx" />
|
||||
<text class="addr-action-text">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="addr-empty" wx:else>
|
||||
<t-icon name="location" class="addr-empty-icon" size="80rpx" />
|
||||
<text class="addr-empty-text">还没有邮寄地址,添加后下单即可选用</text>
|
||||
</view>
|
||||
|
||||
<view class="footer-bar">
|
||||
<button class="primary-btn" bindtap="onAddAddress">添加地址</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- ===== 表单态 ===== -->
|
||||
<block wx:else>
|
||||
<view class="form-card">
|
||||
<view class="form-row">
|
||||
<text class="form-label">地址标签</text>
|
||||
<input class="form-input" value="{{editData.label}}" placeholder="如:家庭、公司" placeholder-class="form-placeholder"
|
||||
maxlength="20" bindinput="onFieldInput" data-field="label" />
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">所在地区</text>
|
||||
<picker class="form-picker" mode="region" value="{{[editData.province, editData.city, editData.district]}}"
|
||||
bindchange="onRegionChange">
|
||||
<view class="picker-value {{editData.province ? '' : 'picker-placeholder'}}">
|
||||
{{editData.province || '请选择地区'}}{{editData.city ? ' ' + editData.city : ''}}{{editData.district ? ' ' + editData.district : ''}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">详细地址</text>
|
||||
<input class="form-input" value="{{editData.address}}" placeholder="请输入详细地址" placeholder-class="form-placeholder"
|
||||
maxlength="100" bindinput="onFieldInput" data-field="address" />
|
||||
</view>
|
||||
|
||||
<view class="form-row form-row-last">
|
||||
<text class="form-label">设为默认地址</text>
|
||||
<!-- switch 的 color 属性读不到 CSS 变量,只能硬编码,取值须与令牌同步 -->
|
||||
<switch checked="{{editData.isDefault}}" bindchange="onDefaultChange" color="#B8520A" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单态双按钮:取消走空心(--sep 描边 + 透明底),保存是本页唯一实心橙块 -->
|
||||
<view class="footer-bar footer-bar-duo">
|
||||
<button class="ghost-btn" bindtap="onBackToList">取消</button>
|
||||
<button class="primary-btn" bindtap="onSaveEdit">保存</button>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,85 +0,0 @@
|
||||
<view class="address-overlay" wx:if="{{visible}}" bindtap="onClose">
|
||||
<view class="address-sheet" catchtap="onSheetTap">
|
||||
<view class="address-header">
|
||||
<view class="address-back" wx:if="{{isEditing}}" bindtap="onBackToList">
|
||||
<t-icon name="chevron-left" size="36rpx" color="#999" />
|
||||
</view>
|
||||
<text class="address-title">{{isEditing ? (editData._index >= 0 ? '编辑地址' : '新增地址') : '邮寄地址'}}</text>
|
||||
<view class="address-close" bindtap="onClose">
|
||||
<t-icon name="close" size="36rpx" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="address-body" wx:if="{{!isEditing}}">
|
||||
<view class="address-empty" wx:if="{{addresses.length === 0}}">
|
||||
<t-icon name="location" size="80rpx" color="#CCCCCC" />
|
||||
<text class="address-empty-text">暂无地址,点击添加</text>
|
||||
</view>
|
||||
|
||||
<view class="address-list" wx:if="{{addresses.length > 0}}">
|
||||
<view class="address-card" wx:for="{{addresses}}" wx:key="index">
|
||||
<view class="address-card-main" bindtap="onEditAddress" data-index="{{index}}">
|
||||
<view class="address-card-top">
|
||||
<text class="address-label">{{item.label}}</text>
|
||||
<text class="address-default-tag" wx:if="{{item.isDefault}}">默认</text>
|
||||
</view>
|
||||
<view class="address-card-content">
|
||||
<text class="address-region">{{item.province}} {{item.city}} {{item.district}}</text>
|
||||
<text class="address-detail">{{item.address}}</text>
|
||||
<text class="address-postcode" wx:if="{{item.postcode}}">邮编:{{item.postcode}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="address-card-actions">
|
||||
<view class="address-action-default" bindtap="onSetDefault" data-index="{{index}}">
|
||||
<t-icon name="{{item.isDefault ? 'check-circle-filled' : 'circle'}}" size="32rpx" color="{{item.isDefault ? '#FF8500' : '#999'}}" />
|
||||
<text class="action-text {{item.isDefault ? 'active' : ''}}">{{item.isDefault ? '默认地址' : '设为默认'}}</text>
|
||||
</view>
|
||||
<view class="address-action-delete" bindtap="onDeleteAddress" data-index="{{index}}">
|
||||
<t-icon name="delete" size="32rpx" color="#999" />
|
||||
<text class="action-text">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="address-body" wx:if="{{isEditing}}">
|
||||
<view class="address-form">
|
||||
<view class="form-item">
|
||||
<text class="form-label">地址标签</text>
|
||||
<input class="form-input" value="{{editData.label}}" placeholder="如:家庭、公司" maxlength="20" bindinput="onFieldInput" data-field="label" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">所在地区</text>
|
||||
<picker mode="region" value="{{[editData.province, editData.city, editData.district]}}" bindchange="onRegionChange">
|
||||
<view class="form-picker">
|
||||
{{editData.province || '请选择地区'}}{{editData.city ? ' ' + editData.city : ''}}{{editData.district ? ' ' + editData.district : ''}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">详细地址</text>
|
||||
<input class="form-input" value="{{editData.address}}" placeholder="请输入详细地址" maxlength="100" bindinput="onFieldInput" data-field="address" />
|
||||
</view>
|
||||
|
||||
<view class="form-item form-item-switch">
|
||||
<text class="form-label">设为默认地址</text>
|
||||
<switch checked="{{editData.isDefault}}" bindchange="onDefaultChange" color="#FF8500" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="address-footer">
|
||||
<view class="address-list-footer" wx:if="{{!isEditing}}">
|
||||
<button class="address-cancel-btn" bindtap="onClose">返回</button>
|
||||
<button class="address-add-btn" bindtap="onAddAddress">添加地址</button>
|
||||
</view>
|
||||
<view class="address-edit-footer" wx:if="{{isEditing}}">
|
||||
<button class="address-cancel-btn" bindtap="onBackToList">返回</button>
|
||||
<button class="address-save-btn" bindtap="onSaveEdit">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
<view class="profile-overlay" wx:if="{{visible}}" bindtap="onClose">
|
||||
<view class="profile-sheet" catchtap="onSheetTap">
|
||||
<view class="profile-header">
|
||||
<text class="profile-title">个人信息</text>
|
||||
<view class="profile-close" bindtap="onClose">
|
||||
<t-icon name="close" size="36rpx" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-body">
|
||||
<view class="profile-item profile-item-avatar" bindtap="onChooseAvatar">
|
||||
<text class="profile-label">头像</text>
|
||||
<view class="profile-avatar-wrap">
|
||||
<image class="profile-avatar" src="{{editData.avatar}}" mode="aspectFill" />
|
||||
<t-icon name="chevron-right" size="32rpx" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-item">
|
||||
<text class="profile-label">姓名</text>
|
||||
<input class="profile-input" value="{{editData.name}}" placeholder="请输入姓名" maxlength="20" bindinput="onFieldInput" data-field="name" />
|
||||
</view>
|
||||
|
||||
<view class="profile-item">
|
||||
<text class="profile-label">性别</text>
|
||||
<view class="profile-sex-wrap">
|
||||
<view class="profile-sex-tag {{editData.sex === 'male' ? 'profile-sex-tag-active' : ''}}" bindtap="onSelectSex" data-sex="male">
|
||||
<text class="profile-sex-text">男</text>
|
||||
</view>
|
||||
<view class="profile-sex-tag {{editData.sex === 'female' ? 'profile-sex-tag-active' : ''}}" bindtap="onSelectSex" data-sex="female">
|
||||
<text class="profile-sex-text">女</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-item">
|
||||
<text class="profile-label">出生日期</text>
|
||||
<picker mode="date" value="{{editData.birth}}" start="1930-01-01" end="{{today}}" bindchange="onBirthChange">
|
||||
<view class="picker">
|
||||
{{editData.birth || '请选择出生日期'}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="profile-item">
|
||||
<text class="profile-label">所在地区</text>
|
||||
<picker mode="region" value="{{[editData.province, editData.city, editData.district]}}" bindchange="onRegionChange">
|
||||
<view class="picker">
|
||||
{{editData.province || '请选择地区'}}{{editData.city ? ',' + editData.city : ''}}{{editData.district ? ',' + editData.district : ''}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="profile-item">
|
||||
<text class="profile-label">手机号</text>
|
||||
<text class="profile-value">{{editData.phone || '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="profile-footer">
|
||||
<button class="profile-cancel-btn" bindtap="onClose">返回</button>
|
||||
<button class="profile-save-btn" bindtap="onSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
+120
-110
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
+80
-27
@@ -1,33 +1,100 @@
|
||||
<view class="container">
|
||||
<view class="header-card" bindtap="onTapProfile">
|
||||
<image class="avatar" src="{{userInfo.avatar}}" mode="aspectFill"></image>
|
||||
<text class="user-name">{{userInfo.name}}</text>
|
||||
<!-- 顶部身份行:头像 + 昵称左对齐浮在纸面上,与下方三个区块共用同一条
|
||||
左基线;无卡片,与下方三张功能卡形成"无框 / 有框"的区分
|
||||
(按硬约束 3,区分优先改结构而非加颜色)。
|
||||
上距 176rpx 是微信胶囊/状态栏安全距(navigationStyle: custom),
|
||||
下限约 160rpx,不是设计留白;省下来的高度只来自头像与纵向内距。
|
||||
已登录:整行可点,右侧 chevron 即点击暗示——它同时承接「个人信息」,
|
||||
所以「我的服务」里不再重复放个人信息项(同一意图只留一个入口);
|
||||
未登录时 onTapProfile 直接 return,不会弹出要求登录的资料窗。
|
||||
未登录:昵称位改显「未登录」,不用"用户"冒充真昵称;
|
||||
右侧放本页唯一的实心暖橙胶囊 -->
|
||||
<view
|
||||
class="header-card"
|
||||
hover-class="{{userInfo.isLoggedIn ? 'header-card-hover' : 'none'}}"
|
||||
bindtap="onTapProfile"
|
||||
>
|
||||
<!-- 默认头像:浅橙衬底 + 居中暖陶土 user 线标,走项目既有的「图标衬底」档。
|
||||
t-icon 的 color 读不到 CSS 变量,只能硬编码,
|
||||
取值须与令牌同步(DESIGN.md §2.2 允许的三个值之一 #9B4508);
|
||||
有真头像时才换成位图 -->
|
||||
<view class="avatar avatar-default" wx:if="{{!userInfo.avatar}}">
|
||||
<t-icon name="user" size="72rpx" color="#9B4508" />
|
||||
</view>
|
||||
<image class="avatar" wx:else src="{{userInfo.avatar}}" mode="aspectFill"></image>
|
||||
<!-- 文字块为上下两行(昵称 / 账号),与微信「我」页同一结构:
|
||||
主行深墨加粗、贴近头像上沿,副行 muted 小字、贴近头像下沿;
|
||||
未登录时只剩一行,加 header-info-single 改回居中 -->
|
||||
<view class="header-info {{userInfo.isLoggedIn ? '' : 'header-info-single'}}">
|
||||
<text class="user-name">{{userInfo.isLoggedIn ? userInfo.name : '未登录'}}</text>
|
||||
<text class="user-account" wx:if="{{userInfo.isLoggedIn}}">{{userInfo.account}}</text>
|
||||
</view>
|
||||
<t-icon
|
||||
wx:if="{{userInfo.isLoggedIn}}"
|
||||
class="header-arrow"
|
||||
name="chevron-right"
|
||||
size="36rpx"
|
||||
/>
|
||||
<button
|
||||
wx:else
|
||||
class="login-btn"
|
||||
open-type="getPhoneNumber"
|
||||
bindgetphonenumber="handleLogin"
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="menu-section" wx:for="{{menuGroups}}" wx:key="groupName" wx:if="{{userInfo.isLoggedIn || item.groupName === '其他'}}">
|
||||
<!-- 菜单按用途分三组,组之间靠"通栏白块 + 纸面留白 + 上下发丝线"分界,
|
||||
不再另设区块抬头:卡片改通栏后,边界本身已经承担了分组功能,
|
||||
再加「我的服务 / 帮助与支持 / 其他」这类泛称标题只是重复页面自己的名字,
|
||||
还要占掉约 276rpx 首屏。groupName 仍保留,作用转为列表 key。
|
||||
requiresLogin 的组在未登录时整组隐藏(而不是只放行「其他」组) -->
|
||||
<view
|
||||
class="section"
|
||||
wx:for="{{menuGroups}}"
|
||||
wx:key="groupName"
|
||||
wx:if="{{!item.requiresLogin || userInfo.isLoggedIn}}"
|
||||
>
|
||||
<!-- 通栏白块:白面 + 上下 2rpx 发丝线 + 紧阴影(无圆角) -->
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" wx:for="{{item.items}}" wx:for-item="menuItem" wx:key="name" hover-class="menu-item-hover" bindtap="{{menuItem.bindtap}}">
|
||||
<t-icon name="{{menuItem.icon}}" class="menu-icon" size="40rpx" />
|
||||
<view
|
||||
class="menu-item"
|
||||
wx:for="{{item.items}}"
|
||||
wx:for-item="menuItem"
|
||||
wx:key="name"
|
||||
hover-class="menu-item-hover"
|
||||
bindtap="{{menuItem.bindtap}}"
|
||||
>
|
||||
<t-icon name="{{menuItem.icon}}" class="menu-icon" size="40rpx" />
|
||||
<text class="menu-text">{{menuItem.name}}</text>
|
||||
<t-icon name="chevron-right" class="menu-arrow" size="36rpx" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-section" wx:if="{{!userInfo.isLoggedIn}}">
|
||||
<button class="login-btn" open-type="getPhoneNumber" bindgetphonenumber="handleLogin">
|
||||
登录
|
||||
</button>
|
||||
<!-- 退出登录:它是一个"动作",不是导航入口。按硬约束「需区分时先改结构,
|
||||
不要加颜色」,它不进上面三组菜单——那里每行都是"图标 + 名称 + chevron
|
||||
= 去另一个页面",混进去会被读成又一个导航项,且与「关于我们」同形、
|
||||
同热区,是最容易点错的位置。
|
||||
状态色不得用于按钮(DESIGN.md §三),所以退出不走红/橙,
|
||||
区分只靠两件事:独立一块 + 居中一行、无图标无 chevron。
|
||||
未登录时整块隐藏(没登录就无所谓"退出") -->
|
||||
<view class="logout-block" wx:if="{{userInfo.isLoggedIn}}" bindtap="onLogout" hover-class="logout-block-hover">
|
||||
<text class="logout-text">退出登录</text>
|
||||
</view>
|
||||
|
||||
<view class="footer-section">
|
||||
<!-- B 端合作入口:从首页移入,避免干扰患者/家属的主流程 -->
|
||||
<!-- B 端合作入口:与首页页脚取同一组三条。
|
||||
原先本页是「企业/机构合作 · 陪诊师入驻 · 商务合作」,
|
||||
其中前两条同义(都转同一个服务电话),
|
||||
违反「同一意图只保留一个入口」,故整体对齐首页那组 -->
|
||||
<view class="footer-links">
|
||||
<text class="footer-link" bindtap="onFooterTap">企业/机构合作</text>
|
||||
<text class="footer-link" bindtap="onFooterTap">商务合作</text>
|
||||
<view class="footer-sep"></view>
|
||||
<text class="footer-link" bindtap="onFooterTap">陪诊师入驻</text>
|
||||
<view class="footer-sep"></view>
|
||||
<text class="footer-link" bindtap="onFooterTap">商务合作</text>
|
||||
<text class="footer-link" bindtap="onFooterTap">陪诊师考证</text>
|
||||
</view>
|
||||
<!-- 版本与版权合并为一行:两行元信息体量都很小,分行只制造空白;
|
||||
中间复用 .footer-sep 细竖线,与上方链接行同一分隔语言(不用中点字符) -->
|
||||
@@ -37,18 +104,4 @@
|
||||
<text class="version">版本号:V 1.1.3</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<comp-profile
|
||||
id="profileComp"
|
||||
visible="{{profileVisible}}"
|
||||
bind:close="onProfileClose"
|
||||
bind:save="onProfileSave"
|
||||
/>
|
||||
|
||||
<comp-address
|
||||
id="addressComp"
|
||||
visible="{{addressVisible}}"
|
||||
bind:close="onAddressClose"
|
||||
bind:save="onAddressSave"
|
||||
/>
|
||||
</view>
|
||||
+168
-33
@@ -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 端合作文字链 + 版本信息,低调可读 */
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"component": true,
|
||||
"navigationBarTitleText": "个人信息",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<view class="container">
|
||||
<!-- 表单收在一张通栏白块里,与「我的」页的菜单白块同源:
|
||||
白面 + 上下各 2rpx 发丝线 + 中性阴影,圆角 0、左右顶到屏幕两侧。
|
||||
原先这里是 mine 页上的 comp_profile 浮层(fixed + 半透明遮罩 + 底部
|
||||
sheet),不占页面栈,系统返回键会直接退出小程序;改为真页面后
|
||||
由微信原生导航栏接管返回,不再有这个问题。
|
||||
行内沿用列表页/弹窗表单的语言:标签左走 --muted,值右走 --ink 加粗,
|
||||
行间隔用 2rpx --line 发丝线 -->
|
||||
<view class="form-card">
|
||||
<!-- 头像:右侧 chevron 是"可换图"的点击暗示。
|
||||
无真头像时渲染与「我的」页同一个默认底座(浅橙底 + 暖陶土 user 线标),
|
||||
不再用 home-ink 位图——位图着色读不到令牌,换主色要整图重上色。
|
||||
注:新选的图是微信本地临时路径,utils/api.js 无文件上传接口,
|
||||
目前不会落库,只在本次编辑中生效 -->
|
||||
<view class="form-row" bindtap="onChooseAvatar" hover-class="form-row-hover">
|
||||
<text class="form-label">头像</text>
|
||||
<view class="avatar-wrap">
|
||||
<view class="avatar avatar-default" wx:if="{{!editData.avatar}}">
|
||||
<t-icon name="user" size="48rpx" color="#9B4508" />
|
||||
</view>
|
||||
<image class="avatar" wx:else src="{{editData.avatar}}" mode="aspectFill"></image>
|
||||
<t-icon name="chevron-right" class="form-arrow" size="32rpx" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">姓名</text>
|
||||
<input class="form-input" value="{{editData.name}}" placeholder="请输入姓名" placeholder-class="form-placeholder"
|
||||
maxlength="20" bindinput="onFieldInput" data-field="name" />
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">性别</text>
|
||||
<!-- 选中态用软橙底 + 实心橙描边(项目既有档),未选中是纸面底 + 发丝边,
|
||||
不引入第二个色相 -->
|
||||
<view class="sex-wrap">
|
||||
<view class="sex-tag {{editData.sex === 'male' ? 'sex-tag-active' : ''}}" bindtap="onSelectSex" data-sex="male">
|
||||
<text class="sex-text">男</text>
|
||||
</view>
|
||||
<view class="sex-tag {{editData.sex === 'female' ? 'sex-tag-active' : ''}}" bindtap="onSelectSex" data-sex="female">
|
||||
<text class="sex-text">女</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">出生日期</text>
|
||||
<picker class="form-picker" mode="date" value="{{editData.birth}}" start="1930-01-01" end="{{today}}"
|
||||
bindchange="onBirthChange">
|
||||
<view class="picker-value {{editData.birth ? '' : 'picker-placeholder'}}">
|
||||
{{editData.birth || '请选择出生日期'}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-row">
|
||||
<text class="form-label">所在地区</text>
|
||||
<picker class="form-picker" mode="region" value="{{[editData.province, editData.city, editData.district]}}"
|
||||
bindchange="onRegionChange">
|
||||
<view class="picker-value {{editData.province ? '' : 'picker-placeholder'}}">
|
||||
{{editData.province || '请选择地区'}}{{editData.city ? ',' + editData.city : ''}}{{editData.district ? ',' + editData.district : ''}}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 手机号来自微信授权,本页只读展示 -->
|
||||
<view class="form-row form-row-last">
|
||||
<text class="form-label">手机号</text>
|
||||
<text class="form-value">{{editData.phone || '未绑定'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存是本页唯一的实心暖橙块,独占「全页同类实心块不超过一处」的额度 -->
|
||||
<view class="footer-bar">
|
||||
<button class="primary-btn {{saving ? 'primary-btn-disabled' : ''}}" bindtap="onSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user