新增功能
This commit is contained in:
@@ -1,178 +0,0 @@
|
||||
# 小程序API配置说明
|
||||
|
||||
## 问题解决
|
||||
|
||||
已修复 `process is not defined` 错误。小程序环境不支持 Node.js 的 `process` 对象。
|
||||
|
||||
## API 基础URL 配置
|
||||
|
||||
### 当前配置
|
||||
|
||||
API 基础 URL 在 `utils/api.js` 中定义:
|
||||
|
||||
```javascript
|
||||
const API_BASE_URL = 'http://api.zzzhengyangtang.cn'
|
||||
```
|
||||
|
||||
### 修改 API 地址
|
||||
|
||||
根据你的实际后端地址修改 `TUICallKit-Vue3/utils/api.js` 中的 `API_BASE_URL`:
|
||||
|
||||
```javascript
|
||||
// 开发环境
|
||||
const API_BASE_URL = 'http://localhost:8000/api'
|
||||
|
||||
// 生产环境
|
||||
const API_BASE_URL = 'https://your-production-api.com/api'
|
||||
```
|
||||
|
||||
## 医生API端点
|
||||
|
||||
### 1. 获取医生列表
|
||||
|
||||
```javascript
|
||||
import { doctorApi } from '../utils/api'
|
||||
|
||||
const response = await doctorApi.getDoctorList({
|
||||
page_no: 1,
|
||||
page_size: 10,
|
||||
keyword: '医生' // 可选
|
||||
})
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"lists": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张医生",
|
||||
"account": "doctor1",
|
||||
"avatar": "http://example.com/avatar.jpg",
|
||||
"mobile": "13800138000",
|
||||
"email": "doctor@example.com",
|
||||
"specialty": "医生",
|
||||
"title": "医生",
|
||||
"rating": "4.8"
|
||||
}
|
||||
],
|
||||
"count": 10,
|
||||
"page_no": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取医生详情
|
||||
|
||||
```javascript
|
||||
const doctor = await doctorApi.getDoctorDetail(1)
|
||||
```
|
||||
|
||||
### 3. 获取医生排班
|
||||
|
||||
```javascript
|
||||
const roster = await doctorApi.getDoctorRoster(1, '2024-03-11')
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
API 请求会自动处理以下情况:
|
||||
|
||||
1. **网络错误**: 显示 "网络错误" 提示
|
||||
2. **请求失败**: 显示 "获取医生列表失败" 提示
|
||||
3. **登录失效**: 自动清除 token
|
||||
|
||||
## 首页集成
|
||||
|
||||
首页 (`pages/index/index.vue`) 会在加载时自动调用 `fetchDoctors()` 方法获取医生列表。
|
||||
|
||||
### 首页流程
|
||||
|
||||
1. 页面加载时调用 `onMounted()`
|
||||
2. `onMounted()` 调用 `fetchDoctors()`
|
||||
3. `fetchDoctors()` 调用 `doctorApi.getDoctorList()`
|
||||
4. 获取成功后更新 `doctors` 数据
|
||||
5. 医生卡片自动渲染
|
||||
|
||||
### 点击医生卡片
|
||||
|
||||
点击医生卡片会跳转到挂号页面:
|
||||
|
||||
```javascript
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
|
||||
})
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看网络请求
|
||||
|
||||
在小程序开发者工具中:
|
||||
1. 打开 "Network" 标签
|
||||
2. 查看 API 请求的 URL、参数和响应
|
||||
|
||||
### 2. 查看控制台日志
|
||||
|
||||
```javascript
|
||||
console.error('请求失败:', error)
|
||||
```
|
||||
|
||||
### 3. 测试 API
|
||||
|
||||
使用 Postman 或 curl 测试 API:
|
||||
|
||||
```bash
|
||||
# 获取医生列表
|
||||
curl "http://localhost:8000/api/doctor/lists?page_no=1&page_size=10"
|
||||
|
||||
# 获取医生详情
|
||||
curl "http://localhost:8000/api/doctor/detail?id=1"
|
||||
|
||||
# 获取医生排班
|
||||
curl "http://localhost:8000/api/doctor/roster?doctor_id=1&date=2024-03-11"
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 医生列表为空?
|
||||
|
||||
A: 检查以下几点:
|
||||
1. API 地址是否正确
|
||||
2. 后端是否有医生数据(role_id = 1)
|
||||
3. 医生的 disable 字段是否为 0
|
||||
4. 网络连接是否正常
|
||||
|
||||
### Q: 头像无法显示?
|
||||
|
||||
A:
|
||||
1. 检查 avatar URL 是否完整
|
||||
2. 检查图片服务器是否可访问
|
||||
3. 检查小程序是否有网络权限
|
||||
|
||||
### Q: 如何修改 API 地址?
|
||||
|
||||
A: 修改 `TUICallKit-Vue3/utils/api.js` 中的 `API_BASE_URL` 常量。
|
||||
|
||||
### Q: 如何添加请求头?
|
||||
|
||||
A: 修改 `request()` 函数中的 `header` 对象:
|
||||
|
||||
```javascript
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token,
|
||||
'Custom-Header': 'value' // 添加自定义请求头
|
||||
}
|
||||
```
|
||||
|
||||
## 相关文件
|
||||
|
||||
- API 服务: `TUICallKit-Vue3/utils/api.js`
|
||||
- 首页组件: `TUICallKit-Vue3/pages/index/index.vue`
|
||||
- 后端控制器: `server/app/api/controller/DoctorController.php`
|
||||
- 后端逻辑: `server/app/api/logic/DoctorLogic.php`
|
||||
@@ -1,361 +0,0 @@
|
||||
# 小程序 API 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
小程序使用统一的 API 请求方式,通过 `main.js` 中定义的全局 `apiUrl` 方法进行请求。
|
||||
|
||||
## 全局 apiUrl 方法
|
||||
|
||||
### 定义位置
|
||||
|
||||
`TUICallKit-Vue3/main.js` 中定义了全局的 `apiUrl` 方法:
|
||||
|
||||
```javascript
|
||||
function apiUrl(promise, Loading = true) {
|
||||
const token = uni.getStorageSync('token')
|
||||
const header = {
|
||||
token: token,
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
...(promise.header || {})
|
||||
}
|
||||
const data = promise.data || {}
|
||||
const url = baseUrl + (promise.url || '')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Loading) {
|
||||
uni.showLoading({ title: '加载中...', mask: true })
|
||||
}
|
||||
|
||||
uni.request({
|
||||
url: url,
|
||||
data: data,
|
||||
timeout: 15000,
|
||||
method: promise.method || 'POST',
|
||||
header: header,
|
||||
sslVerify: false,
|
||||
success: (res) => {
|
||||
const data = res.data
|
||||
if (data.code === -1) {
|
||||
uni.removeStorageSync('token')
|
||||
}
|
||||
resolve(data)
|
||||
},
|
||||
fail: (e) => {
|
||||
reject(e)
|
||||
},
|
||||
complete: (e) => {
|
||||
if (Loading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| promise.url | string | 请求URL(相对路径) |
|
||||
| promise.method | string | 请求方法(GET/POST,默认POST) |
|
||||
| promise.data | object | 请求数据 |
|
||||
| promise.header | object | 自定义请求头 |
|
||||
| Loading | boolean | 是否显示加载中(默认true) |
|
||||
|
||||
## API 服务层
|
||||
|
||||
### 文件位置
|
||||
|
||||
`TUICallKit-Vue3/utils/api.js`
|
||||
|
||||
### 基础方法
|
||||
|
||||
```javascript
|
||||
// GET 请求
|
||||
export const get = (url, data = {}, loading = false) => {
|
||||
return request(url, 'GET', data, loading)
|
||||
}
|
||||
|
||||
// POST 请求
|
||||
export const post = (url, data = {}, loading = true) => {
|
||||
return request(url, 'POST', data, loading)
|
||||
}
|
||||
```
|
||||
|
||||
### 医生 API
|
||||
|
||||
```javascript
|
||||
export const doctorApi = {
|
||||
// 获取医生列表
|
||||
getDoctorList: (params) => get('/api/doctor/lists', params, false),
|
||||
|
||||
// 获取医生详情
|
||||
getDoctorDetail: (id) => get('/api/doctor/detail', { id }, false),
|
||||
|
||||
// 获取医生排班
|
||||
getDoctorRoster: (doctorId, date) => get('/api/doctor/roster', { doctor_id: doctorId, date }, false)
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 在组件中使用
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { doctorApi } from '../utils/api'
|
||||
|
||||
const doctors = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
// 获取医生列表
|
||||
const fetchDoctors = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await doctorApi.getDoctorList({
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
})
|
||||
|
||||
if (response && response.code === 1) {
|
||||
doctors.value = response.data.lists || []
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '获取失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error)
|
||||
uni.showToast({
|
||||
title: '网络错误',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDoctors()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 创建新的 API 模块
|
||||
|
||||
```javascript
|
||||
// utils/api.js 中添加
|
||||
|
||||
export const appointmentApi = {
|
||||
// 获取预约列表
|
||||
getAppointmentList: (params) => get('/api/appointment/lists', params, false),
|
||||
|
||||
// 创建预约
|
||||
createAppointment: (data) => post('/api/appointment/create', data, true),
|
||||
|
||||
// 取消预约
|
||||
cancelAppointment: (id) => post('/api/appointment/cancel', { id }, true)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用自定义请求头
|
||||
|
||||
```javascript
|
||||
import { request } from '../utils/api'
|
||||
|
||||
// 发送带自定义请求头的请求
|
||||
const response = await request(
|
||||
'/api/custom/endpoint',
|
||||
'POST',
|
||||
{ data: 'value' },
|
||||
true,
|
||||
{ 'X-Custom-Header': 'custom-value' }
|
||||
)
|
||||
```
|
||||
|
||||
## 响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"lists": [...],
|
||||
"count": 10,
|
||||
"page_no": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "错误信息"
|
||||
}
|
||||
```
|
||||
|
||||
### 登录失效
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -1,
|
||||
"msg": "登录已失效"
|
||||
}
|
||||
```
|
||||
|
||||
当 `code === -1` 时,会自动清除 token。
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 自动处理
|
||||
|
||||
- 登录失效(code === -1):自动清除 token
|
||||
- 网络错误:自动显示加载中/隐藏加载中
|
||||
|
||||
### 手动处理
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const response = await doctorApi.getDoctorList({ page_no: 1 })
|
||||
|
||||
if (response.code === 1) {
|
||||
// 成功处理
|
||||
console.log(response.data)
|
||||
} else if (response.code === -1) {
|
||||
// 登录失效,跳转到登录页
|
||||
uni.redirectTo({ url: '/pages/login/login' })
|
||||
} else {
|
||||
// 其他错误
|
||||
uni.showToast({
|
||||
title: response.msg || '请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络错误
|
||||
console.error('网络错误:', error)
|
||||
uni.showToast({
|
||||
title: '网络错误,请检查连接',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 基础 URL
|
||||
|
||||
在 `main.js` 中定义:
|
||||
|
||||
```javascript
|
||||
var baseUrl = 'http://api.zzzhengyangtang.cn'
|
||||
```
|
||||
|
||||
修改此值来改变 API 基础地址。
|
||||
|
||||
### 超时时间
|
||||
|
||||
在 `main.js` 中的 `uni.request` 中定义:
|
||||
|
||||
```javascript
|
||||
timeout: 15000 // 15秒超时
|
||||
```
|
||||
|
||||
### 请求头
|
||||
|
||||
默认请求头:
|
||||
|
||||
```javascript
|
||||
{
|
||||
token: 'user_token',
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何添加新的 API?
|
||||
|
||||
A: 在 `utils/api.js` 中添加新的 API 对象:
|
||||
|
||||
```javascript
|
||||
export const newApi = {
|
||||
getList: (params) => get('/api/new/lists', params, false),
|
||||
create: (data) => post('/api/new/create', data, true)
|
||||
}
|
||||
```
|
||||
|
||||
### Q: 如何修改请求头?
|
||||
|
||||
A: 在 `request` 函数中修改 `header` 对象,或在调用时传入自定义请求头。
|
||||
|
||||
### Q: 如何处理文件上传?
|
||||
|
||||
A: 使用 `uni.uploadFile` 而不是 `uni.request`:
|
||||
|
||||
```javascript
|
||||
export const uploadFile = (filePath, url) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: baseUrl + url,
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
token: uni.getStorageSync('token')
|
||||
},
|
||||
success: (res) => {
|
||||
resolve(JSON.parse(res.data))
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Q: 如何取消请求?
|
||||
|
||||
A: 小程序的 `uni.request` 不支持直接取消,但可以使用标志位:
|
||||
|
||||
```javascript
|
||||
let requestAborted = false
|
||||
|
||||
const fetchData = async () => {
|
||||
const response = await doctorApi.getDoctorList({ page_no: 1 })
|
||||
|
||||
if (requestAborted) {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
}
|
||||
|
||||
// 取消请求
|
||||
const abortRequest = () => {
|
||||
requestAborted = true
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **统一 API 管理**:所有 API 调用都通过 `utils/api.js` 进行
|
||||
2. **错误处理**:始终使用 try-catch 处理异步请求
|
||||
3. **加载状态**:在请求时显示加载中,完成后隐藏
|
||||
4. **响应验证**:检查 `code` 字段判断请求是否成功
|
||||
5. **token 管理**:自动处理 token 过期情况
|
||||
|
||||
## 相关文件
|
||||
|
||||
- API 服务: `TUICallKit-Vue3/utils/api.js`
|
||||
- 全局配置: `TUICallKit-Vue3/main.js`
|
||||
- 首页示例: `TUICallKit-Vue3/pages/index/index.vue`
|
||||
- API 配置: `TUICallKit-Vue3/API_CONFIG.md`
|
||||
@@ -1,302 +0,0 @@
|
||||
# 老年人友好的首页设计指南
|
||||
|
||||
## 设计理念
|
||||
|
||||
针对60岁左右的用户群体,设计简洁、清晰、易操作的界面。
|
||||
|
||||
### 核心原则
|
||||
1. **字体大** - 所有文字都要足够大,便于阅读
|
||||
2. **操作简单** - 减少复杂操作,一键直达
|
||||
3. **色彩清晰** - 高对比度,清晰易辨
|
||||
4. **布局简洁** - 信息不过载,重点突出
|
||||
5. **反馈明确** - 每个操作都有清晰的反馈
|
||||
|
||||
## 页面结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 养生堂 ⋯ ◯ │ <- 顶部标题栏
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ │
|
||||
│ [轮播图] │
|
||||
│ │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ [头像] 赵建欣 │
|
||||
│ 咨询 8次 │
|
||||
│ │
|
||||
│ 主任医师 中医内科 │
|
||||
│ │
|
||||
│ ⭐ 名医中医 │
|
||||
│ │
|
||||
│ 👍 好评率 95% ❤️ 服务人数 256人│
|
||||
│ │
|
||||
│ 🌐 互联网医院执业许可 │
|
||||
│ 📋 医师执业许可证 │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ [头像] 李医生 │
|
||||
│ ... │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 🏠 首页 👤 我的 │ <- 底部导航
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 字体规范
|
||||
|
||||
### 字体大小
|
||||
- **顶部标题**: 36rpx (大标题,易识别)
|
||||
- **医生名字**: 32rpx (重要信息)
|
||||
- **职位/科室**: 24rpx (次要信息)
|
||||
- **其他文字**: 22-24rpx (保证可读性)
|
||||
- **底部导航**: 24rpx (便于点击)
|
||||
|
||||
### 字体权重
|
||||
- **标题**: 加粗 (700)
|
||||
- **重要信息**: 加粗 (600)
|
||||
- **正常文字**: 正常 (400)
|
||||
|
||||
## 色彩方案
|
||||
|
||||
### 主色系
|
||||
- **绿色**: #4CAF50 - 健康、生命力
|
||||
- **浅绿**: #E8F5E9 - 背景色
|
||||
- **黄色**: #FFF9C4 - 强调色
|
||||
- **灰色**: #f5f5f5 - 页面背景
|
||||
|
||||
### 文字色
|
||||
- **主文字**: #333 - 深灰色,高对比度
|
||||
- **次文字**: #666 - 中灰色
|
||||
- **辅助**: #999 - 浅灰色
|
||||
|
||||
## 组件设计
|
||||
|
||||
### 1. 顶部标题栏
|
||||
```scss
|
||||
.header {
|
||||
padding: 24rpx 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
```
|
||||
- 标题: 36rpx,加粗
|
||||
- 按钮: 50x50rpx,便于点击
|
||||
- 高对比度
|
||||
|
||||
### 2. 轮播图
|
||||
```scss
|
||||
.banner {
|
||||
height: 300rpx;
|
||||
}
|
||||
```
|
||||
- 高度: 300rpx
|
||||
- 自动轮播,5秒切换
|
||||
- 圆角: 12rpx
|
||||
- 阴影: 0 2rpx 8rpx
|
||||
|
||||
### 3. 医生卡片
|
||||
```scss
|
||||
.doctor-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
```
|
||||
- 白色背景,清晰易读
|
||||
- 内边距: 24rpx (充足的空间)
|
||||
- 圆角: 12rpx
|
||||
- 阴影: 0 2rpx 8rpx
|
||||
|
||||
### 4. 医生头部
|
||||
```
|
||||
[头像] 医生名字 (32rpx)
|
||||
咨询 8次 (24rpx, 绿色)
|
||||
```
|
||||
- 头像: 100x100rpx
|
||||
- 名字: 32rpx,加粗
|
||||
- 描述: 24rpx,绿色
|
||||
|
||||
### 5. 医生标签
|
||||
```
|
||||
主任医师 中医内科
|
||||
```
|
||||
- 绿色背景,绿色文字
|
||||
- 字体: 24rpx
|
||||
- 圆角: 20rpx
|
||||
- 高对比度
|
||||
|
||||
### 6. 专家标签
|
||||
```
|
||||
⭐ 名医中医
|
||||
```
|
||||
- 黄色背景
|
||||
- 字体: 22rpx
|
||||
- 圆角: 16rpx
|
||||
|
||||
### 7. 评分和服务
|
||||
```
|
||||
┌──────────────┬──────────────┐
|
||||
│ 👍 好评率 │ ❤️ 服务人数 │
|
||||
│ 95% │ 256人 │
|
||||
└──────────────┴──────────────┘
|
||||
```
|
||||
- 两列布局
|
||||
- 绿色背景
|
||||
- 字体: 28rpx (数值),22rpx (标签)
|
||||
- 图标: 32rpx
|
||||
|
||||
### 8. 资质信息
|
||||
```
|
||||
🌐 互联网医院执业许可
|
||||
📋 医师执业许可证
|
||||
```
|
||||
- 列表展示
|
||||
- 字体: 22rpx
|
||||
- 图标: 24rpx
|
||||
|
||||
### 9. 底部导航
|
||||
```
|
||||
🏠 首页 👤 我的
|
||||
```
|
||||
- 固定底部
|
||||
- 图标: 36-40rpx
|
||||
- 文字: 24rpx
|
||||
- 高对比度
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 点击区域
|
||||
- **最小点击区域**: 44x44rpx
|
||||
- **医生卡片**: 整个卡片可点击
|
||||
- **底部导航**: 每个导航项 >= 60rpx
|
||||
|
||||
### 反馈效果
|
||||
- **卡片按下**: 阴影增强
|
||||
- **按钮按下**: 背景变深
|
||||
- **导航切换**: 颜色和大小变化
|
||||
|
||||
### 动画
|
||||
- **过渡时间**: 300ms
|
||||
- **轮播**: 5秒自动切换
|
||||
- **缓动函数**: ease
|
||||
|
||||
## 易用性设计
|
||||
|
||||
### 1. 大字体
|
||||
- 所有文字 >= 22rpx
|
||||
- 标题 >= 32rpx
|
||||
- 便于老年人阅读
|
||||
|
||||
### 2. 高对比度
|
||||
- 文字和背景对比度 >= 4.5:1
|
||||
- 清晰易辨
|
||||
|
||||
### 3. 简单操作
|
||||
- 一键咨询
|
||||
- 直接进入医生详情
|
||||
- 无复杂交互
|
||||
|
||||
### 4. 清晰反馈
|
||||
- 每个操作都有视觉反馈
|
||||
- 按下时有阴影变化
|
||||
- 导航切换有颜色变化
|
||||
|
||||
### 5. 充足空间
|
||||
- 卡片内边距: 24rpx
|
||||
- 元素间距: 16-20rpx
|
||||
- 不拥挤
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 使用 rpx 单位
|
||||
- 自动适配不同屏幕
|
||||
- 1rpx = 0.5px (375px 宽度)
|
||||
|
||||
### 布局适配
|
||||
- 医生卡片宽度 100%
|
||||
- 自动适应屏幕宽度
|
||||
- 文字自动换行
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 使用 CSS 动画
|
||||
- 图片自动缩放
|
||||
- 减少重排和重绘
|
||||
|
||||
## 无障碍设计
|
||||
|
||||
- 足够的色彩对比度
|
||||
- 清晰的文字标签
|
||||
- 合理的点击区域
|
||||
- 支持屏幕阅读器
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
页面加载
|
||||
↓
|
||||
调用 fetchDoctors()
|
||||
↓
|
||||
获取医生列表
|
||||
↓
|
||||
处理数据
|
||||
↓
|
||||
渲染医生卡片
|
||||
```
|
||||
|
||||
## API 集成
|
||||
|
||||
### 获取医生列表
|
||||
```javascript
|
||||
const response = await proxy.apiUrl({
|
||||
url: '/api/doctor/lists',
|
||||
method: 'GET',
|
||||
data: {
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 点击事件
|
||||
|
||||
### 医生卡片点击
|
||||
```javascript
|
||||
selectDoctor(doctor) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
这个设计方案特点:
|
||||
- ✅ 字体大,易于阅读
|
||||
- ✅ 操作简单,一键直达
|
||||
- ✅ 色彩清晰,高对比度
|
||||
- ✅ 布局简洁,信息清晰
|
||||
- ✅ 反馈明确,易于理解
|
||||
- ✅ 完全适合60岁左右的用户
|
||||
|
||||
整个页面设计充分考虑了老年人的使用习惯和需求,提供了友好、易用的医疗服务体验。
|
||||
@@ -1,250 +0,0 @@
|
||||
# 全局属性使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
小程序通过 `main.js` 中的 `app.config.globalProperties` 定义全局属性,包括 `$url` 和 `apiUrl` 方法。
|
||||
|
||||
## 全局属性定义
|
||||
|
||||
### 在 main.js 中
|
||||
|
||||
```javascript
|
||||
// Vue3 部分
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// 定义全局 baseUrl
|
||||
app.config.globalProperties.$url = baseUrl
|
||||
|
||||
// 定义全局 apiUrl 方法
|
||||
app.config.globalProperties.apiUrl = apiUrl
|
||||
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 全局属性列表
|
||||
|
||||
| 属性名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `$url` | string | API 基础 URL,默认值:`http://api.zzzhengyangtang.cn` |
|
||||
| `apiUrl` | function | API 请求方法 |
|
||||
|
||||
## 在 API 服务中使用
|
||||
|
||||
### 获取全局属性
|
||||
|
||||
```javascript
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
const getGlobalProperties = () => {
|
||||
try {
|
||||
const instance = getCurrentInstance()
|
||||
if (instance) {
|
||||
return instance.appContext.config.globalProperties
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('无法获取 getCurrentInstance')
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
// 使用
|
||||
const globalProps = getGlobalProperties()
|
||||
const baseUrl = globalProps.$url
|
||||
const apiUrl = globalProps.apiUrl
|
||||
```
|
||||
|
||||
### 在 request 函数中使用
|
||||
|
||||
```javascript
|
||||
export const request = (url, method = 'POST', data = {}, loading = true) => {
|
||||
// 获取全局属性
|
||||
const globalProps = getGlobalProperties()
|
||||
const apiUrl = globalProps.apiUrl
|
||||
const baseUrl = globalProps.$url || 'http://api.zzzhengyangtang.cn'
|
||||
|
||||
if (!apiUrl) {
|
||||
// 降级方案:直接使用 uni.request
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `${baseUrl}${url}`,
|
||||
method,
|
||||
data,
|
||||
// ...
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 使用全局 apiUrl 方法
|
||||
return apiUrl({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
header: {
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}, loading)
|
||||
}
|
||||
```
|
||||
|
||||
## 在组件中使用
|
||||
|
||||
### 方式1:通过 getCurrentInstance
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
const baseUrl = instance.appContext.config.globalProperties.$url
|
||||
const apiUrl = instance.appContext.config.globalProperties.apiUrl
|
||||
|
||||
console.log('Base URL:', baseUrl)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 方式2:通过 this(Vue2 风格)
|
||||
|
||||
```vue
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getBaseUrl() {
|
||||
return this.$url
|
||||
},
|
||||
async fetchData() {
|
||||
const response = await this.apiUrl({
|
||||
url: '/api/data',
|
||||
method: 'GET'
|
||||
})
|
||||
console.log(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 方式3:通过 inject(推荐)
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { inject } from 'vue'
|
||||
|
||||
// 在 main.js 中提供
|
||||
// app.provide('$url', baseUrl)
|
||||
// app.provide('apiUrl', apiUrl)
|
||||
|
||||
const baseUrl = inject('$url')
|
||||
const apiUrl = inject('apiUrl')
|
||||
</script>
|
||||
```
|
||||
|
||||
## 修改全局属性
|
||||
|
||||
### 修改 baseUrl
|
||||
|
||||
编辑 `TUICallKit-Vue3/main.js`:
|
||||
|
||||
```javascript
|
||||
var baseUrl = 'http://your-api-domain/api'
|
||||
```
|
||||
|
||||
### 修改 apiUrl 方法
|
||||
|
||||
编辑 `TUICallKit-Vue3/main.js` 中的 `apiUrl` 函数。
|
||||
|
||||
## 全局属性的生命周期
|
||||
|
||||
1. **初始化**:在 `main.js` 中通过 `app.config.globalProperties` 定义
|
||||
2. **可用**:应用创建后,所有组件都可以访问
|
||||
3. **销毁**:应用销毁时自动清理
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 统一管理全局属性
|
||||
|
||||
在 `main.js` 中集中定义所有全局属性:
|
||||
|
||||
```javascript
|
||||
// 定义全局属性对象
|
||||
const globalConfig = {
|
||||
$url: baseUrl,
|
||||
apiUrl: apiUrl,
|
||||
$appName: '医疗小程序',
|
||||
$version: '1.0.0'
|
||||
}
|
||||
|
||||
// 批量注册
|
||||
Object.keys(globalConfig).forEach(key => {
|
||||
app.config.globalProperties[key] = globalConfig[key]
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 创建辅助函数
|
||||
|
||||
```javascript
|
||||
// utils/global.js
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
export const useGlobal = () => {
|
||||
const instance = getCurrentInstance()
|
||||
const globalProps = instance.appContext.config.globalProperties
|
||||
|
||||
return {
|
||||
baseUrl: globalProps.$url,
|
||||
apiUrl: globalProps.apiUrl,
|
||||
appName: globalProps.$appName,
|
||||
version: globalProps.$version
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在组件中使用
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useGlobal } from '@/utils/global'
|
||||
|
||||
const { baseUrl, apiUrl } = useGlobal()
|
||||
|
||||
console.log('Base URL:', baseUrl)
|
||||
</script>
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何在非组件中使用全局属性?
|
||||
|
||||
A: 在 API 服务中使用 `getCurrentInstance()` 获取全局属性。
|
||||
|
||||
### Q: 如何动态修改全局属性?
|
||||
|
||||
A: 直接修改 `app.config.globalProperties` 中的值:
|
||||
|
||||
```javascript
|
||||
const instance = getCurrentInstance()
|
||||
instance.appContext.config.globalProperties.$url = 'http://new-url'
|
||||
```
|
||||
|
||||
### Q: 全局属性和 provide/inject 的区别?
|
||||
|
||||
A:
|
||||
- 全局属性:所有组件都可以访问,通过 `this.$property` 或 `getCurrentInstance()` 获取
|
||||
- provide/inject:父组件提供,子组件注入,更灵活但需要显式提供
|
||||
|
||||
### Q: 如何在 Composition API 中使用全局属性?
|
||||
|
||||
A: 使用 `getCurrentInstance()` 获取实例,然后访问 `appContext.config.globalProperties`。
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 全局配置: `TUICallKit-Vue3/main.js`
|
||||
- API 服务: `TUICallKit-Vue3/utils/api.js`
|
||||
- 全局工具: `TUICallKit-Vue3/utils/global.js`(可选)
|
||||
|
||||
## 总结
|
||||
|
||||
通过 `app.config.globalProperties` 定义的全局属性可以在整个应用中访问,是管理全局配置和方法的最佳方式。
|
||||
@@ -1,309 +0,0 @@
|
||||
# 小程序 API 集成总结
|
||||
|
||||
## 核心架构
|
||||
|
||||
```
|
||||
main.js (全局配置)
|
||||
├── $url: 'http://api.zzzhengyangtang.cn'
|
||||
├── apiUrl: function(promise, loading)
|
||||
└── app.config.globalProperties
|
||||
|
||||
utils/global.js (全局工具)
|
||||
├── getGlobalProperties()
|
||||
├── getBaseUrl()
|
||||
├── getApiUrl()
|
||||
├── useGlobal()
|
||||
└── getFullUrl()
|
||||
|
||||
utils/api.js (API 服务)
|
||||
├── request(url, method, data, loading)
|
||||
├── get(url, data, loading)
|
||||
├── post(url, data, loading)
|
||||
└── doctorApi { getDoctorList, getDoctorDetail, getDoctorRoster }
|
||||
|
||||
pages/index/index.vue (首页)
|
||||
└── fetchDoctors() → doctorApi.getDoctorList()
|
||||
```
|
||||
|
||||
## 文件说明
|
||||
|
||||
### 1. main.js - 全局配置
|
||||
|
||||
**定义全局属性**:
|
||||
```javascript
|
||||
app.config.globalProperties.$url = baseUrl
|
||||
app.config.globalProperties.apiUrl = apiUrl
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 定义 API 基础 URL
|
||||
- 定义 apiUrl 方法(处理 token、加载中、登录失效等)
|
||||
- 支持 Vue2 和 Vue3
|
||||
|
||||
### 2. utils/global.js - 全局工具
|
||||
|
||||
**提供的函数**:
|
||||
- `getGlobalProperties()` - 获取全局属性对象
|
||||
- `getBaseUrl()` - 获取 API 基础 URL
|
||||
- `getApiUrl()` - 获取 apiUrl 方法
|
||||
- `useGlobal()` - Composition API Hook
|
||||
- `getFullUrl(path)` - 获取完整 URL
|
||||
- `setGlobalProperty(key, value)` - 设置全局属性
|
||||
- `getGlobalProperty(key, defaultValue)` - 获取特定全局属性
|
||||
|
||||
**使用示例**:
|
||||
```javascript
|
||||
import { useGlobal, getBaseUrl } from '@/utils/global'
|
||||
|
||||
const { baseUrl, apiUrl } = useGlobal()
|
||||
const url = getBaseUrl()
|
||||
```
|
||||
|
||||
### 3. utils/api.js - API 服务
|
||||
|
||||
**提供的方法**:
|
||||
- `request(url, method, data, loading)` - 基础请求方法
|
||||
- `get(url, data, loading)` - GET 请求
|
||||
- `post(url, data, loading)` - POST 请求
|
||||
- `doctorApi` - 医生相关 API
|
||||
|
||||
**医生 API**:
|
||||
```javascript
|
||||
doctorApi.getDoctorList(params) // 获取医生列表
|
||||
doctorApi.getDoctorDetail(id) // 获取医生详情
|
||||
doctorApi.getDoctorRoster(id, date) // 获取医生排班
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 自动使用全局 `$url` 和 `apiUrl`
|
||||
- 降级方案:当全局方法不可用时,直接使用 `uni.request`
|
||||
- 自动处理 token 和请求头
|
||||
|
||||
### 4. pages/index/index.vue - 首页
|
||||
|
||||
**功能**:
|
||||
- 展示古典卷轴风格幻灯片
|
||||
- 动态加载医生列表
|
||||
- 医生卡片点击跳转
|
||||
|
||||
**流程**:
|
||||
```
|
||||
onMounted()
|
||||
↓
|
||||
fetchDoctors()
|
||||
↓
|
||||
doctorApi.getDoctorList()
|
||||
↓
|
||||
request() → apiUrl() → uni.request()
|
||||
↓
|
||||
更新 doctors 数据
|
||||
↓
|
||||
医生卡片渲染
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 在组件中使用 API
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { doctorApi } from '../utils/api'
|
||||
|
||||
const doctors = ref([])
|
||||
|
||||
const fetchDoctors = async () => {
|
||||
try {
|
||||
const response = await doctorApi.getDoctorList({
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
})
|
||||
|
||||
if (response.code === 1) {
|
||||
doctors.value = response.data.lists || []
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: response.msg || '获取失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDoctors()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 使用全局工具
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { useGlobal } from '@/utils/global'
|
||||
|
||||
const { baseUrl, apiUrl } = useGlobal()
|
||||
|
||||
console.log('Base URL:', baseUrl)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 3. 添加新的 API
|
||||
|
||||
在 `utils/api.js` 中添加:
|
||||
|
||||
```javascript
|
||||
export const appointmentApi = {
|
||||
getList: (params) => get('/api/appointment/lists', params, false),
|
||||
create: (data) => post('/api/appointment/create', data, true),
|
||||
cancel: (id) => post('/api/appointment/cancel', { id }, true)
|
||||
}
|
||||
```
|
||||
|
||||
## 请求流程详解
|
||||
|
||||
### 正常流程
|
||||
|
||||
```
|
||||
组件调用 doctorApi.getDoctorList()
|
||||
↓
|
||||
调用 get('/api/doctor/lists', params, false)
|
||||
↓
|
||||
调用 request('/api/doctor/lists', 'GET', params, false)
|
||||
↓
|
||||
获取全局属性 ($url, apiUrl)
|
||||
↓
|
||||
调用 apiUrl({ url, method, data, header }, loading)
|
||||
↓
|
||||
main.js 中的 apiUrl 方法处理
|
||||
↓
|
||||
获取 token,合并请求头
|
||||
↓
|
||||
调用 uni.request()
|
||||
↓
|
||||
返回响应数据
|
||||
↓
|
||||
检查 code 字段
|
||||
├─ code === 1: 成功,返回数据
|
||||
├─ code === -1: 登录失效,清除 token
|
||||
└─ code === 0: 失败,返回错误信息
|
||||
```
|
||||
|
||||
### 降级流程
|
||||
|
||||
当全局 `apiUrl` 不可用时:
|
||||
|
||||
```
|
||||
request() 检查 apiUrl 是否存在
|
||||
↓
|
||||
apiUrl 不存在
|
||||
↓
|
||||
使用降级方案:直接调用 uni.request()
|
||||
↓
|
||||
获取 token,构建完整 URL
|
||||
↓
|
||||
调用 uni.request()
|
||||
↓
|
||||
返回响应数据
|
||||
```
|
||||
|
||||
## 配置修改
|
||||
|
||||
### 修改 API 基础 URL
|
||||
|
||||
编辑 `main.js`:
|
||||
|
||||
```javascript
|
||||
var baseUrl = 'http://your-api-domain/api'
|
||||
```
|
||||
|
||||
### 修改超时时间
|
||||
|
||||
编辑 `main.js` 中的 `uni.request`:
|
||||
|
||||
```javascript
|
||||
timeout: 20000 // 改为 20 秒
|
||||
```
|
||||
|
||||
### 修改请求头
|
||||
|
||||
编辑 `main.js` 中的 `header` 对象:
|
||||
|
||||
```javascript
|
||||
const header = {
|
||||
token: token,
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'X-Custom-Header': 'custom-value'
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 自动处理
|
||||
|
||||
1. **登录失效** (code === -1)
|
||||
- 自动清除 token
|
||||
- 可选:跳转到登录页
|
||||
|
||||
2. **网络错误**
|
||||
- 自动显示/隐藏加载中
|
||||
- 返回错误信息
|
||||
|
||||
### 手动处理
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const response = await doctorApi.getDoctorList({ page_no: 1 })
|
||||
|
||||
if (response.code === 1) {
|
||||
// 成功
|
||||
console.log(response.data)
|
||||
} else if (response.code === -1) {
|
||||
// 登录失效
|
||||
uni.redirectTo({ url: '/pages/login/login' })
|
||||
} else {
|
||||
// 其他错误
|
||||
uni.showToast({
|
||||
title: response.msg || '请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络错误
|
||||
console.error('网络错误:', error)
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **统一 API 管理**:所有 API 调用都通过 `utils/api.js`
|
||||
2. **错误处理**:始终使用 try-catch 处理异步请求
|
||||
3. **加载状态**:在请求时显示加载中,完成后隐藏
|
||||
4. **响应验证**:检查 `code` 字段判断请求是否成功
|
||||
5. **token 管理**:自动处理 token 过期情况
|
||||
6. **全局属性**:使用 `utils/global.js` 中的工具函数访问全局属性
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 全局配置: `TUICallKit-Vue3/main.js`
|
||||
- 全局工具: `TUICallKit-Vue3/utils/global.js`
|
||||
- API 服务: `TUICallKit-Vue3/utils/api.js`
|
||||
- 首页组件: `TUICallKit-Vue3/pages/index/index.vue`
|
||||
- 全局属性说明: `TUICallKit-Vue3/GLOBAL_PROPERTIES_USAGE.md`
|
||||
- API 使用指南: `TUICallKit-Vue3/API_USAGE_GUIDE.md`
|
||||
- API 配置说明: `TUICallKit-Vue3/API_CONFIG.md`
|
||||
|
||||
## 总结
|
||||
|
||||
已完成小程序与后端 API 的完整集成:
|
||||
|
||||
1. ✅ 全局配置管理(main.js)
|
||||
2. ✅ 全局工具函数(utils/global.js)
|
||||
3. ✅ API 服务层封装(utils/api.js)
|
||||
4. ✅ 首页医生列表展示(pages/index/index.vue)
|
||||
5. ✅ 完整的错误处理和加载状态
|
||||
6. ✅ 详细的文档和使用指南
|
||||
|
||||
小程序现在可以正常调用后端 API 获取医生列表,并且所有 API 请求都使用 `main.js` 中定义的全局 `$url` 和 `apiUrl` 方法。
|
||||
@@ -1,290 +0,0 @@
|
||||
# 现代中医医院首页设计指南
|
||||
|
||||
## 设计理念
|
||||
|
||||
采用现代医疗应用的设计风格,结合中医特色,为60岁左右的用户提供专业、易用的医疗服务体验。
|
||||
|
||||
## 页面结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 成都双流蜀养堂互联网医院 ⋯ ◯ │ <- 顶部信息栏
|
||||
│ 📍 郑州市金水区 │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 🔍 输入科室、疾病、医生搜索 搜索 │ <- 搜索栏
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ [轮播图] │ <- 轮播图
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 问诊服务 │
|
||||
├─────────────────────────────────┤
|
||||
│ 专家问诊 │ 健康咨询 │ 快速问诊 │ <- 服务卡片
|
||||
│ 去找专家 │ 前往咨询 │ 前往问诊 │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 特色专科医师 更多 > │
|
||||
├─────────────────────────────────┤
|
||||
│ [头像] [头像] [头像] [头像] │ <- 医生横向滚动
|
||||
│ 赵建欣 雷惧昱 稳骨 ... │
|
||||
│ 主任医师 主治医师 主治医师 │
|
||||
│ 内科 内科 内科 │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 🏠 首页 📢 消息 👤 我的 │ <- 底部导航
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 色彩方案
|
||||
|
||||
### 主色系
|
||||
- **棕色**: #8B6F47 - 中医传统色
|
||||
- **绿色**: #4CAF50 - 健康、生命力
|
||||
- **背景**: #f5f5f5 - 页面背景
|
||||
- **卡片**: #fff - 内容卡片
|
||||
|
||||
### 服务卡片色
|
||||
- **专家问诊**: 橙色系 (#FFF3E0 - #FFE0B2)
|
||||
- **健康咨询**: 绿色系 (#E8F5E9 - #C8E6C9)
|
||||
- **快速问诊**: 紫色系 (#F3E5F5 - #E1BEE7)
|
||||
|
||||
## 组件设计
|
||||
|
||||
### 1. 顶部信息栏
|
||||
```scss
|
||||
.top-bar {
|
||||
background: linear-gradient(135deg, #8B6F47 0%, #A0826D 100%);
|
||||
padding: 16rpx 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
```
|
||||
- 棕色渐变背景
|
||||
- 显示医院名称和位置
|
||||
- 右侧显示菜单和设置按钮
|
||||
- 高度: 60rpx
|
||||
|
||||
### 2. 搜索栏
|
||||
```scss
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 24rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: #4CAF50;
|
||||
color: #fff;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
```
|
||||
- 灰色背景,圆角搜索框
|
||||
- 绿色搜索按钮
|
||||
- 支持输入科室、疾病、医生
|
||||
|
||||
### 3. 轮播图
|
||||
```scss
|
||||
.banner {
|
||||
height: 200rpx;
|
||||
}
|
||||
```
|
||||
- 高度: 200rpx
|
||||
- 自动轮播,5秒切换
|
||||
- 圆角: 12rpx
|
||||
- 阴影: 0 2rpx 8rpx
|
||||
|
||||
### 4. 问诊服务卡片
|
||||
```scss
|
||||
.service-card {
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.service-1 { background: linear-gradient(135deg, #FFF3E0 0%, #FFE0B2 100%); }
|
||||
.service-2 { background: linear-gradient(135deg, #E8F5E9 0%, #C8E6C9 100%); }
|
||||
.service-3 { background: linear-gradient(135deg, #F3E5F5 0%, #E1BEE7 100%); }
|
||||
```
|
||||
- 3 列网格布局
|
||||
- 渐变背景
|
||||
- 标题 + 描述 + 按钮
|
||||
- 按下有缩放效果
|
||||
|
||||
### 5. 医生卡片
|
||||
```scss
|
||||
.doctor-card {
|
||||
flex-shrink: 0;
|
||||
width: 120rpx;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.doctor-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
```
|
||||
- 横向滚动布局
|
||||
- 宽度: 120rpx
|
||||
- 头像: 80x80rpx
|
||||
- 显示名字、职位、科室
|
||||
|
||||
### 6. 底部导航
|
||||
```scss
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 12rpx 0 16rpx 0;
|
||||
}
|
||||
```
|
||||
- 固定底部
|
||||
- 3 个导航项
|
||||
- 图标 + 文字
|
||||
- 高度: 80rpx
|
||||
|
||||
## 字体规范
|
||||
|
||||
### 字体大小
|
||||
- **医院名称**: 22rpx
|
||||
- **服务标题**: 24rpx
|
||||
- **医生名字**: 22rpx
|
||||
- **职位/科室**: 18rpx
|
||||
- **搜索框**: 24rpx
|
||||
- **按钮**: 18-22rpx
|
||||
|
||||
### 字体权重
|
||||
- **标题**: 加粗 (600-700)
|
||||
- **重要信息**: 加粗 (600)
|
||||
- **正常文字**: 正常 (400)
|
||||
|
||||
## 间距规范
|
||||
|
||||
- **页面外边距**: 20rpx
|
||||
- **卡片内边距**: 12-16rpx
|
||||
- **元素间距**: 8-12rpx
|
||||
- **底部空间**: 100rpx (留给导航)
|
||||
|
||||
## 圆角规范
|
||||
|
||||
- **卡片**: 12rpx
|
||||
- **头像**: 8rpx
|
||||
- **按钮**: 16-24rpx
|
||||
- **搜索框**: 24rpx
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 轮播图
|
||||
- 自动轮播,5秒切换
|
||||
- 支持手动滑动
|
||||
- 底部指示器
|
||||
|
||||
### 服务卡片
|
||||
- 点击进入对应服务
|
||||
- 按下有缩放效果 (scale 0.95)
|
||||
- 300ms 过渡动画
|
||||
|
||||
### 医生卡片
|
||||
- 横向滚动
|
||||
- 点击进入医生详情
|
||||
- 按下有阴影效果
|
||||
|
||||
### 导航切换
|
||||
- 点击切换导航
|
||||
- 颜色和大小变化
|
||||
- 300ms 过渡动画
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 使用 rpx 单位
|
||||
- 自动适配不同屏幕
|
||||
- 1rpx = 0.5px (375px 宽度)
|
||||
|
||||
### 布局适配
|
||||
- 服务卡片 3 列
|
||||
- 医生卡片横向滚动
|
||||
- 自动适应屏幕宽度
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 使用 CSS 动画
|
||||
- 图片自动缩放
|
||||
- 减少重排和重绘
|
||||
- 支持图片懒加载
|
||||
|
||||
## 无障碍设计
|
||||
|
||||
- 足够的色彩对比度
|
||||
- 清晰的文字标签
|
||||
- 合理的点击区域 (最小 44x44rpx)
|
||||
- 支持屏幕阅读器
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
页面加载
|
||||
↓
|
||||
调用 fetchDoctors()
|
||||
↓
|
||||
获取医生列表
|
||||
↓
|
||||
处理数据
|
||||
↓
|
||||
渲染医生卡片
|
||||
```
|
||||
|
||||
## API 集成
|
||||
|
||||
### 获取医生列表
|
||||
```javascript
|
||||
const response = await proxy.apiUrl({
|
||||
url: '/api/doctor/lists',
|
||||
method: 'GET',
|
||||
data: {
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 点击事件
|
||||
|
||||
### 医生卡片点击
|
||||
```javascript
|
||||
selectDoctor(doctor) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
这个设计方案特点:
|
||||
- ✅ 现代医疗应用风格
|
||||
- ✅ 中医特色色彩搭配
|
||||
- ✅ 清晰的信息层级
|
||||
- ✅ 多种问诊服务选择
|
||||
- ✅ 医生横向滚动展示
|
||||
- ✅ 易用的导航设计
|
||||
- ✅ 适合60岁左右的用户
|
||||
|
||||
整个页面设计专业、现代、易用,为用户提供了多种医疗服务选择和便捷的医生查询体验。
|
||||
@@ -1,252 +0,0 @@
|
||||
# 中医医院小程序首页 - 简化设计
|
||||
|
||||
## 页面结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 中医医院 │ <- 顶部导航栏
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ │
|
||||
│ [轮播图片 1] │
|
||||
│ 中医药文化 │
|
||||
│ 传承千年中医精粹,守护您的健康 │
|
||||
│ │
|
||||
│ ● ○ ○ │ <- 指示器
|
||||
└─────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────┐
|
||||
│ 名医推荐 │
|
||||
├─────────────────────────────────┤
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ [头像] 张医生 [咨询] │ │
|
||||
│ │ 专家 主任医师 │ │
|
||||
│ │ 中医内科 │ │
|
||||
│ │ ★★★★★ 4.9 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ [头像] 李医生 [咨询] │ │
|
||||
│ │ 副主任医师 │ │
|
||||
│ │ 中医妇科 │ │
|
||||
│ │ ★★★★★ 4.8 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ [头像] 王医生 [咨询] │ │
|
||||
│ │ 主治医师 │ │
|
||||
│ │ 中医儿科 │ │
|
||||
│ │ ★★★★★ 4.7 │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 设计规范
|
||||
|
||||
### 色彩方案
|
||||
- **主色**: `#8B6914` (棕色) - 中医传统色
|
||||
- **辅助色**: `#A0826D` (浅棕) - 渐变用
|
||||
- **强调色**: `#FFB800` (金色) - 星级评分
|
||||
- **背景**: `#f8f8f8` (浅灰)
|
||||
- **卡片**: `#fff` (白色)
|
||||
- **文字**: `#333` (深灰)
|
||||
|
||||
### 字体规范
|
||||
- **标题**: 32rpx, 加粗
|
||||
- **医生名字**: 28rpx, 加粗
|
||||
- **职位/科室**: 22rpx, 正常
|
||||
- **评分**: 20rpx, 正常
|
||||
|
||||
### 间距规范
|
||||
- **页面外边距**: 20rpx
|
||||
- **卡片内边距**: 16rpx
|
||||
- **元素间距**: 4-8rpx
|
||||
|
||||
### 圆角规范
|
||||
- **卡片**: 12rpx
|
||||
- **头像**: 8rpx
|
||||
- **按钮**: 20rpx
|
||||
|
||||
## 组件详解
|
||||
|
||||
### 1. 顶部导航栏
|
||||
```scss
|
||||
.header {
|
||||
background: linear-gradient(135deg, #8B6914 0%, #A0826D 100%);
|
||||
padding: 20rpx 30rpx;
|
||||
color: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
```
|
||||
- 固定顶部,棕色渐变背景
|
||||
- 显示医院名称
|
||||
- 高度: 60rpx
|
||||
|
||||
### 2. 轮播图
|
||||
```scss
|
||||
.banner {
|
||||
height: 320rpx;
|
||||
}
|
||||
|
||||
.banner-overlay {
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
```
|
||||
- 高度: 320rpx
|
||||
- 自动轮播,5秒切换
|
||||
- 底部显示标题和描述
|
||||
- 使用渐变遮罩
|
||||
|
||||
### 3. 医生卡片
|
||||
```scss
|
||||
.doctor-item {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #f0f0f0;
|
||||
}
|
||||
```
|
||||
- 左侧: 头像 (100x100rpx) + 专家标签
|
||||
- 中间: 医生信息 (名字、职位、科室、评分)
|
||||
- 右侧: 咨询按钮
|
||||
- 按下有反馈效果
|
||||
|
||||
### 4. 医生信息
|
||||
```
|
||||
医生名字 (28rpx, 加粗)
|
||||
职位 (22rpx, 棕色)
|
||||
科室 (20rpx, 灰色)
|
||||
★★★★★ 4.9 (20rpx, 金色)
|
||||
```
|
||||
|
||||
### 5. 咨询按钮
|
||||
```scss
|
||||
.consult-btn {
|
||||
background: #8B6914;
|
||||
color: #fff;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
```
|
||||
- 棕色背景,白色文字
|
||||
- 圆角按钮
|
||||
- 点击跳转到挂号页面
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 轮播图
|
||||
- 自动轮播,5秒切换一次
|
||||
- 支持手动滑动
|
||||
- 显示指示器点
|
||||
|
||||
### 医生卡片
|
||||
- 点击整个卡片进入医生详情
|
||||
- 按下时有缩放和阴影效果
|
||||
- 咨询按钮点击进入挂号页面
|
||||
|
||||
### 按钮反馈
|
||||
- 按下时背景变深
|
||||
- 有 300ms 过渡动画
|
||||
- 提供视觉反馈
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 使用 rpx 单位
|
||||
- 1rpx = 0.5px (在 375px 宽度下)
|
||||
- 自动适配不同屏幕尺寸
|
||||
- 无需手动计算
|
||||
|
||||
### 布局适配
|
||||
- 医生卡片使用 flex 布局
|
||||
- 自动适应屏幕宽度
|
||||
- 文字自动换行
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 图片优化
|
||||
- 使用 `mode="aspectFill"` 保持宽高比
|
||||
- 图片自动缩放
|
||||
- 支持懒加载
|
||||
|
||||
### 动画优化
|
||||
- 使用 CSS 过渡而非 JS 动画
|
||||
- 轮播使用原生 swiper 组件
|
||||
- 减少重排和重绘
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
页面加载
|
||||
↓
|
||||
调用 fetchDoctors()
|
||||
↓
|
||||
调用 proxy.apiUrl() 获取医生列表
|
||||
↓
|
||||
处理响应数据
|
||||
↓
|
||||
添加 isExpert 标记
|
||||
↓
|
||||
渲染医生卡片
|
||||
```
|
||||
|
||||
## API 集成
|
||||
|
||||
### 获取医生列表
|
||||
```javascript
|
||||
const response = await proxy.apiUrl({
|
||||
url: '/api/doctor/lists',
|
||||
method: 'GET',
|
||||
data: {
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 响应格式
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"lists": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "张医生",
|
||||
"title": "主任医师",
|
||||
"specialty": "中医内科",
|
||||
"avatar": "http://...",
|
||||
"rating": "4.9"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 点击事件
|
||||
|
||||
### 医生卡片点击
|
||||
```javascript
|
||||
selectDoctor(doctor) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
这个简化设计包含:
|
||||
- ✅ 专业的顶部导航栏
|
||||
- ✅ 吸引人的轮播图
|
||||
- ✅ 清晰的医生列表
|
||||
- ✅ 便捷的咨询按钮
|
||||
- ✅ 中医风格的色彩方案
|
||||
- ✅ 流畅的交互体验
|
||||
|
||||
整个页面简洁明了,重点突出,用户可以快速找到想要的医生并进行咨询。
|
||||
@@ -1,308 +0,0 @@
|
||||
# 中医风格首页设计指南
|
||||
|
||||
## 设计理念
|
||||
|
||||
采用绿色系中医风格,结合卡片式布局,营造专业、温和、信任的医疗氛围。
|
||||
|
||||
## 页面结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 背景装饰(绿色渐变) │
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐│
|
||||
│ │ 轮播图 ││
|
||||
│ │ ● ○ ○ ││
|
||||
│ └─────────────────────────────┘│
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐│
|
||||
│ │ [头像] 赵建欣 咨询 > ││
|
||||
│ │ 主任医师 ││
|
||||
│ │ 中医内科 ││
|
||||
│ │ ││
|
||||
│ │ ⭐ 名医中医 ││
|
||||
│ │ ││
|
||||
│ │ 👍 好评率 95% ❤️ 服务人数 ││
|
||||
│ │ ││
|
||||
│ │ 🌐 互联网医院执业许可 ││
|
||||
│ │ 📋 医师执业许可证 ││
|
||||
│ └─────────────────────────────┘│
|
||||
│ │
|
||||
│ ┌─────────────────────────────┐│
|
||||
│ │ [头像] 李医生 咨询 > ││
|
||||
│ │ ... ││
|
||||
│ └─────────────────────────────┘│
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 色彩方案
|
||||
|
||||
### 主色系
|
||||
- **主绿色**: `#4CAF50` - 中医健康、生命力
|
||||
- **浅绿色**: `#81C784` - 柔和、温暖
|
||||
- **背景绿**: `#E8F5E9` - 清爽、舒适
|
||||
- **浅背景**: `#F1F8F6` - 卡片背景
|
||||
|
||||
### 辅助色
|
||||
- **金色**: `#FFB800` - 强调、重要
|
||||
- **黄色**: `#FFF9C4` - 标签背景
|
||||
- **文字**: `#333` - 主文字
|
||||
- **辅助**: `#999` - 次要文字
|
||||
|
||||
## 组件设计
|
||||
|
||||
### 1. 背景装饰
|
||||
```scss
|
||||
.bg-decoration {
|
||||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1) 0%, rgba(129, 199, 132, 0.05) 100%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: 400rpx;
|
||||
}
|
||||
```
|
||||
- 固定背景,绿色渐变
|
||||
- 营造整体氛围
|
||||
- 不影响内容交互
|
||||
|
||||
### 2. 轮播图
|
||||
```scss
|
||||
.banner-section {
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.15);
|
||||
}
|
||||
|
||||
.banner {
|
||||
height: 280rpx;
|
||||
}
|
||||
```
|
||||
- 高度: 280rpx
|
||||
- 圆角: 16rpx
|
||||
- 绿色阴影
|
||||
- 自动轮播,5秒切换
|
||||
- 底部指示器
|
||||
|
||||
### 3. 医生卡片
|
||||
```scss
|
||||
.doctor-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(76, 175, 80, 0.1);
|
||||
border: 1rpx solid rgba(76, 175, 80, 0.08);
|
||||
}
|
||||
```
|
||||
- 白色背景,绿色阴影
|
||||
- 圆角: 16rpx
|
||||
- 内边距: 20rpx
|
||||
- 按下有上浮效果
|
||||
|
||||
### 4. 医生头部
|
||||
```
|
||||
[头像] 医生名字
|
||||
咨询 >
|
||||
```
|
||||
- 左侧: 80x80rpx 头像
|
||||
- 右侧: 名字 + 咨询按钮
|
||||
- 头像圆角: 12rpx
|
||||
|
||||
### 5. 医生详情
|
||||
```
|
||||
┌──────────────┬──────────────┐
|
||||
│ 主任医师 │ 中医内科 │
|
||||
└──────────────┴──────────────┘
|
||||
```
|
||||
- 两个标签,绿色背景
|
||||
- 圆角: 20rpx
|
||||
- 字体: 20rpx
|
||||
|
||||
### 6. 医生标签
|
||||
```
|
||||
⭐ 名医中医
|
||||
```
|
||||
- 黄色背景
|
||||
- 圆角: 16rpx
|
||||
- 显示医生特殊身份
|
||||
|
||||
### 7. 医生评分
|
||||
```
|
||||
┌──────────────┬──────────────┐
|
||||
│ 👍 好评率 │ ❤️ 服务人数 │
|
||||
│ 95% │ 256人 │
|
||||
└──────────────┴──────────────┘
|
||||
```
|
||||
- 两列布局
|
||||
- 绿色背景
|
||||
- 显示关键指标
|
||||
|
||||
### 8. 医生资质
|
||||
```
|
||||
🌐 互联网医院执业许可
|
||||
📋 医师执业许可证
|
||||
```
|
||||
- 列表展示
|
||||
- 图标 + 文字
|
||||
- 灰色文字
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 轮播图
|
||||
- 自动轮播,5秒切换
|
||||
- 支持手动滑动
|
||||
- 底部指示器显示当前位置
|
||||
- 指示器动画: 圆形 → 胶囊形
|
||||
|
||||
### 医生卡片
|
||||
- 点击整个卡片进入医生详情
|
||||
- 按下时上浮 2rpx
|
||||
- 阴影增强
|
||||
- 300ms 过渡动画
|
||||
|
||||
### 咨询按钮
|
||||
- 绿色文字,表示可点击
|
||||
- 点击进入挂号页面
|
||||
- 带有 ">" 箭头提示
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 使用 rpx 单位
|
||||
- 自动适配不同屏幕
|
||||
- 1rpx = 0.5px (375px 宽度)
|
||||
|
||||
### 布局适配
|
||||
- 医生卡片宽度 100%
|
||||
- 自动适应屏幕宽度
|
||||
- 文字自动换行
|
||||
|
||||
## 动画效果
|
||||
|
||||
### 轮播指示器
|
||||
```scss
|
||||
.dot {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.active {
|
||||
width: 24rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
```
|
||||
- 圆形 → 胶囊形
|
||||
- 300ms 过渡
|
||||
|
||||
### 卡片交互
|
||||
```scss
|
||||
.doctor-card {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 4rpx 16rpx rgba(76, 175, 80, 0.15);
|
||||
}
|
||||
}
|
||||
```
|
||||
- 上浮 2rpx
|
||||
- 阴影增强
|
||||
- 300ms 过渡
|
||||
|
||||
## 阴影规范
|
||||
|
||||
- **轻**: `0 2rpx 8rpx rgba(76, 175, 80, 0.1)`
|
||||
- **中**: `0 4rpx 12rpx rgba(76, 175, 80, 0.15)`
|
||||
- **重**: `0 4rpx 16rpx rgba(76, 175, 80, 0.15)`
|
||||
|
||||
## 字体规范
|
||||
|
||||
- **医生名字**: 28rpx, 加粗
|
||||
- **职位/科室**: 20rpx, 正常
|
||||
- **咨询按钮**: 22rpx, 加粗
|
||||
- **标签文字**: 18rpx, 正常
|
||||
- **评分数值**: 22rpx, 加粗
|
||||
- **资质文字**: 18rpx, 正常
|
||||
|
||||
## 间距规范
|
||||
|
||||
- **页面外边距**: 20rpx
|
||||
- **卡片内边距**: 20rpx
|
||||
- **元素间距**: 8-16rpx
|
||||
- **底部空间**: 20rpx
|
||||
|
||||
## 圆角规范
|
||||
|
||||
- **卡片**: 16rpx
|
||||
- **头像**: 12rpx
|
||||
- **标签**: 16-20rpx
|
||||
- **小元素**: 12rpx
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
页面加载
|
||||
↓
|
||||
调用 fetchDoctors()
|
||||
↓
|
||||
获取医生列表
|
||||
↓
|
||||
处理数据(添加 isExpert 标记)
|
||||
↓
|
||||
渲染医生卡片
|
||||
```
|
||||
|
||||
## API 集成
|
||||
|
||||
### 获取医生列表
|
||||
```javascript
|
||||
const response = await proxy.apiUrl({
|
||||
url: '/api/doctor/lists',
|
||||
method: 'GET',
|
||||
data: {
|
||||
page_no: 1,
|
||||
page_size: 10
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 响应数据处理
|
||||
```javascript
|
||||
doctors.value = doctorList.map((doctor, index) => ({
|
||||
...doctor,
|
||||
isExpert: index < 2,
|
||||
reviewCount: Math.floor(Math.random() * 500) + 100
|
||||
}))
|
||||
```
|
||||
|
||||
## 点击事件
|
||||
|
||||
### 医生卡片点击
|
||||
```javascript
|
||||
selectDoctor(doctor) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/index?doctorId=${doctor.id}&doctorName=${doctor.name}`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 使用 CSS 动画而非 JS 动画
|
||||
- 图片使用 `mode="aspectFill"`
|
||||
- 减少重排和重绘
|
||||
- 支持图片懒加载
|
||||
|
||||
## 无障碍设计
|
||||
|
||||
- 足够的色彩对比度
|
||||
- 清晰的文字标签
|
||||
- 合理的点击区域 (最小 44x44rpx)
|
||||
- 支持屏幕阅读器
|
||||
|
||||
## 总结
|
||||
|
||||
这个设计方案特点:
|
||||
- ✅ 绿色系中医风格
|
||||
- ✅ 卡片式布局,清晰明了
|
||||
- ✅ 完整的医生信息展示
|
||||
- ✅ 医生资质认证显示
|
||||
- ✅ 流畅的交互体验
|
||||
- ✅ 专业的医疗感
|
||||
|
||||
整个页面简洁专业,重点突出,用户可以快速了解医生信息并进行咨询。
|
||||
@@ -1,8 +1,8 @@
|
||||
import LibGenerateTestUserSig from './lib-generate-test-usersig-es.min.js';
|
||||
|
||||
let SDKAPPID = 1600127710;
|
||||
let SDKAPPID = 1600131325;
|
||||
|
||||
let SECRETKEY = '8a6b3ce533b0e46b9d6e17d5c77bac240bc0ccdce4e3db93a0d6a1e55160c73d';
|
||||
let SECRETKEY = 'adc84842a817f6b9d124a1a15a6de4d0b3a940bef678820ed46d40db474c8ea4';
|
||||
|
||||
/**
|
||||
* Expiration time for the signature, it is recommended not to set it too short.
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
# 中医医院小程序首页 UI 设计指南
|
||||
|
||||
## 设计理念
|
||||
|
||||
### 核心特点
|
||||
1. **中医文化融合** - 采用中医传统色彩(棕色、金色)
|
||||
2. **专业医疗感** - 简洁现代的布局,突出医疗专业性
|
||||
3. **用户友好** - 清晰的信息层级,便捷的操作流程
|
||||
4. **视觉层次** - 通过色彩、大小、间距创建清晰的视觉层次
|
||||
|
||||
## 色彩方案
|
||||
|
||||
### 主色系
|
||||
- **主色**: `#8B6914` (棕色) - 代表中医传统、稳重、专业
|
||||
- **辅助色**: `#A0826D` (浅棕) - 用于渐变和背景
|
||||
- **强调色**: `#FFB800` (金色) - 用于星级评分、重要信息
|
||||
- **警告色**: `#FF6B6B` (红色) - 用于通知、提醒
|
||||
|
||||
### 中性色
|
||||
- **背景**: `#f8f8f8` - 整体背景
|
||||
- **卡片**: `#fff` - 内容卡片
|
||||
- **文字**: `#333` - 主文字
|
||||
- **辅助**: `#999` - 辅助文字
|
||||
|
||||
## 页面结构
|
||||
|
||||
### 1. 顶部导航栏 (Header)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 中医医院 🔔 (3) │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 固定顶部,背景为棕色渐变
|
||||
- 左侧显示医院名称
|
||||
- 右侧显示通知图标和未读数
|
||||
|
||||
### 2. 搜索栏 (Search Bar)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 🔍 搜索医生或科室 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 圆角搜索框,背景为浅灰色
|
||||
- 支持搜索医生和科室
|
||||
|
||||
### 3. 快速导航 (Quick Navigation)
|
||||
```
|
||||
┌──────┬──────┬──────┬──────┐
|
||||
│ 📋 │ 💬 │ 📄 │ ❤️ │
|
||||
│ 挂号 │ 咨询 │ 记录 │ 档案 │
|
||||
└──────┴──────┴──────┴──────┘
|
||||
```
|
||||
- 4 个快速入口
|
||||
- 图标 + 文字组合
|
||||
- 点击跳转到对应页面
|
||||
|
||||
### 4. 轮播区域 (Banner)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ │
|
||||
│ [轮播图片] │
|
||||
│ │
|
||||
│ 中医药文化 │
|
||||
│ 传承千年中医精粹,守护您的健康 │
|
||||
│ [了解更多] │
|
||||
│ │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 高度: 300rpx
|
||||
- 自动轮播,5秒切换
|
||||
- 底部显示标题、描述和按钮
|
||||
- 使用渐变遮罩
|
||||
|
||||
### 5. 科室推荐 (Departments)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 科室推荐 查看全部 > │
|
||||
├─────────────────────────────────┤
|
||||
│ 🫀 👩 👶 │
|
||||
│ 内科 妇科 儿科 │
|
||||
│ 中医内科 中医妇科 中医儿科 │
|
||||
│ │
|
||||
│ 🦴 🧴 💉 │
|
||||
│ 骨科 皮肤科 针灸科 │
|
||||
│ 中医骨科 中医皮肤 针灸推拿 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 3 列网格布局
|
||||
- 6 个科室卡片
|
||||
- 卡片背景为浅米色
|
||||
- 点击进入科室详情
|
||||
|
||||
### 6. 医生推荐 (Doctors)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 名医推荐 查看全部 > │
|
||||
├─────────────────────────────────┤
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ [头像] 张医生 [咨询] │ │
|
||||
│ │ 专家 主任医师 │ │
|
||||
│ │ 中医内科 │ │
|
||||
│ │ ★★★★★ 4.9 (256) │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ [头像] 李医生 [咨询] │ │
|
||||
│ │ 副主任医师 │ │
|
||||
│ │ 中医妇科 │ │
|
||||
│ │ ★★★★★ 4.8 (189) │ │
|
||||
│ └─────────────────────────────┘ │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 列表布局,每行一个医生
|
||||
- 左侧: 头像 + 专家标签
|
||||
- 中间: 医生信息(名字、职位、科室、评分)
|
||||
- 右侧: 咨询按钮
|
||||
- 点击进入医生详情
|
||||
|
||||
### 7. 健康资讯 (News)
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 健康资讯 更多 > │
|
||||
├─────────────────────────────────┤
|
||||
│ [图] 春季养生:如何调理脾胃 │
|
||||
│ 中医养生 2小时前 │
|
||||
│ │
|
||||
│ [图] 中医药在预防疾病中的作用 │
|
||||
│ 医学资讯 4小时前 │
|
||||
│ │
|
||||
│ [图] 穴位按摩缓解颈椎疲劳 │
|
||||
│ 健康指南 6小时前 │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
- 列表布局
|
||||
- 左侧: 新闻图片
|
||||
- 右侧: 标题、来源、时间
|
||||
- 标题最多显示 2 行
|
||||
|
||||
## 交互设计
|
||||
|
||||
### 按钮状态
|
||||
- **正常**: 棕色背景,白色文字
|
||||
- **按下**: 背景变深,有缩放动画
|
||||
- **禁用**: 灰色背景,不可点击
|
||||
|
||||
### 卡片交互
|
||||
- **正常**: 浅色背景,细边框
|
||||
- **按下**: 背景变深,有阴影效果
|
||||
- **悬停**: 轻微缩放
|
||||
|
||||
### 动画效果
|
||||
- **轮播**: 500ms 切换动画
|
||||
- **卡片**: 300ms 过渡效果
|
||||
- **按钮**: 100ms 按下效果
|
||||
|
||||
## 响应式设计
|
||||
|
||||
### 断点
|
||||
- **小屏幕** (< 375px): 调整间距和字体
|
||||
- **中屏幕** (375-414px): 标准设计
|
||||
- **大屏幕** (> 414px): 增加间距
|
||||
|
||||
### 适配方案
|
||||
- 使用 rpx 单位(响应式像素)
|
||||
- 1rpx = 0.5px (在 375px 宽度下)
|
||||
- 自动适配不同屏幕尺寸
|
||||
|
||||
## 字体规范
|
||||
|
||||
### 字体大小
|
||||
- **标题**: 32rpx (主标题), 28rpx (副标题)
|
||||
- **正文**: 24rpx (主要内容), 22rpx (次要内容)
|
||||
- **辅助**: 20rpx (提示信息), 18rpx (标签)
|
||||
|
||||
### 字体权重
|
||||
- **加粗**: 500-700 (标题、重要信息)
|
||||
- **正常**: 400 (正文)
|
||||
- **轻细**: 300 (辅助信息)
|
||||
|
||||
## 间距规范
|
||||
|
||||
### 外间距 (Margin)
|
||||
- **大**: 30rpx (主要分区)
|
||||
- **中**: 20rpx (卡片间距)
|
||||
- **小**: 10rpx (元素间距)
|
||||
|
||||
### 内间距 (Padding)
|
||||
- **大**: 30rpx (卡片内部)
|
||||
- **中**: 20rpx (按钮、输入框)
|
||||
- **小**: 10rpx (文字间距)
|
||||
|
||||
## 圆角规范
|
||||
|
||||
- **大**: 12rpx (卡片、按钮)
|
||||
- **中**: 8rpx (小卡片、图片)
|
||||
- **小**: 4rpx (标签、徽章)
|
||||
- **圆形**: 50% (头像、通知徽章)
|
||||
|
||||
## 阴影规范
|
||||
|
||||
- **轻**: `0 2rpx 8rpx rgba(0, 0, 0, 0.08)`
|
||||
- **中**: `0 4rpx 12rpx rgba(0, 0, 0, 0.12)`
|
||||
- **重**: `0 8rpx 24rpx rgba(0, 0, 0, 0.15)`
|
||||
|
||||
## 图片规范
|
||||
|
||||
### 尺寸
|
||||
- **轮播图**: 750x300px
|
||||
- **医生头像**: 100x100rpx
|
||||
- **新闻图片**: 140x100rpx
|
||||
- **科室图标**: 48rpx (emoji)
|
||||
|
||||
### 处理
|
||||
- 使用 `mode="aspectFill"` 保持宽高比
|
||||
- 圆角处理: 8-12rpx
|
||||
- 加载占位符: 灰色背景
|
||||
|
||||
## 无障碍设计
|
||||
|
||||
- 足够的色彩对比度
|
||||
- 清晰的文字标签
|
||||
- 合理的点击区域 (最小 44x44rpx)
|
||||
- 支持屏幕阅读器
|
||||
|
||||
## 性能优化
|
||||
|
||||
- 图片懒加载
|
||||
- 列表虚拟滚动
|
||||
- 减少重排和重绘
|
||||
- 使用 CSS 动画而非 JS 动画
|
||||
|
||||
## 扩展建议
|
||||
|
||||
### 可以添加的功能
|
||||
1. **个性化推荐** - 根据用户历史推荐医生
|
||||
2. **预约提醒** - 显示即将到来的预约
|
||||
3. **健康数据** - 显示用户的健康指标
|
||||
4. **在线客服** - 右下角浮窗
|
||||
5. **活动促销** - 轮播中添加促销信息
|
||||
|
||||
### 可以优化的方面
|
||||
1. **深色模式** - 支持系统深色模式
|
||||
2. **多语言** - 支持多种语言
|
||||
3. **主题切换** - 允许用户自定义主题色
|
||||
4. **动画增强** - 添加更多微交互
|
||||
5. **数据统计** - 追踪用户行为
|
||||
|
||||
## 总结
|
||||
|
||||
这个设计方案结合了中医文化特色和现代医疗应用的需求,通过合理的色彩、布局和交互设计,为用户提供专业、友好的医疗服务体验。
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from './App'
|
||||
var baseUrl ='https://admin.zhenyangtang.com.cn/';
|
||||
var baseUrl ='https://www.d.com/';
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
|
||||
@@ -61,6 +61,12 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/order",
|
||||
"style": {
|
||||
"navigationBarTitleText": "付款"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
|
||||
@@ -59,12 +59,65 @@
|
||||
<text class="label">糖尿病期数</text>
|
||||
<text class="value">{{ getDiabetesTypeName() || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">婚姻状态</text>
|
||||
<text class="value">{{ getMaritalStatusName() || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">身高</text>
|
||||
<text class="value">{{ formData.height ? formData.height + ' cm' : '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">体重</text>
|
||||
<text class="value">{{ formData.weight ? formData.weight + ' kg' : '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">地区</text>
|
||||
<text class="value">{{ formData.region || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">收缩压</text>
|
||||
<text class="value">{{ formData.systolic_pressure ? formData.systolic_pressure + ' mmHg' : '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">舒张压</text>
|
||||
<text class="value">{{ formData.diastolic_pressure ? formData.diastolic_pressure + ' mmHg' : '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">空腹血糖</text>
|
||||
<text class="value">{{ formData.fasting_blood_sugar ? formData.fasting_blood_sugar + ' mmol/L' : '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 现病史 -->
|
||||
<view class="section">
|
||||
<view class="section-title">现病史</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">患病年数</text>
|
||||
<text class="value">{{ formData.diabetes_discovery_year ? formData.diabetes_discovery_year + ' 年' : '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">当地诊断结果</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in (formData.local_hospital_diagnosis || [])" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="!formData.local_hospital_diagnosis || formData.local_hospital_diagnosis.length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">当地就诊医院</text>
|
||||
<text class="value">{{ formData.local_hospital_name || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">口腔感觉</text>
|
||||
<text class="value">{{ getAppetiteName() || '-' }}</text>
|
||||
@@ -108,6 +161,62 @@
|
||||
<text v-if="getSleepConditionNames().length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">眼睛情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.eye_condition, eyeConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.eye_condition, eyeConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">头部感觉</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.head_feeling, headFeelingOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.head_feeling, headFeelingOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">出汗情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.sweat_condition, sweatConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.sweat_condition, sweatConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">皮肤情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.skin_condition, skinConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.skin_condition, skinConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">小便情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.urine_condition, urineConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.urine_condition, urineConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">大便情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.stool_condition, stoolConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.stool_condition, stoolConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">腰肾情况</text>
|
||||
<view class="tag-list">
|
||||
<text v-for="item in getMultiNames(formData.kidney_condition, kidneyConditionOptions)" :key="item" class="tag">{{ item }}</text>
|
||||
<text v-if="getMultiNames(formData.kidney_condition, kidneyConditionOptions).length === 0" class="value">-</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 既往史 -->
|
||||
@@ -141,6 +250,16 @@
|
||||
<text class="label">过敏史</text>
|
||||
<text class="value">{{ formData.allergy_history === 1 ? '有' : '无' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">家族病史</text>
|
||||
<text class="value">{{ formData.family_history === 1 ? '有' : '无' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">妊娠哺乳史</text>
|
||||
<text class="value">{{ formData.pregnancy_history === 1 ? '有' : '无' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 诊断信息 -->
|
||||
@@ -190,6 +309,25 @@
|
||||
<text class="label">医嘱</text>
|
||||
<text class="value multi-line">{{ formData.doctor_advice || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="formData.report_files && formData.report_files.length > 0">
|
||||
<text class="label">检查报告</text>
|
||||
<view class="image-list">
|
||||
<image
|
||||
v-for="(img, index) in formData.report_files"
|
||||
:key="index"
|
||||
:src="img"
|
||||
class="tongue-image"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(img)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">备注</text>
|
||||
<text class="value multi-line">{{ formData.remark || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -230,24 +368,45 @@ const formData = ref({
|
||||
diagnosis_type: '',
|
||||
syndrome_type: '',
|
||||
diabetes_type: '',
|
||||
marital_status: '',
|
||||
height: '',
|
||||
weight: '',
|
||||
region: '',
|
||||
systolic_pressure: '',
|
||||
diastolic_pressure: '',
|
||||
fasting_blood_sugar: '',
|
||||
diabetes_discovery_year: '',
|
||||
local_hospital_diagnosis: [],
|
||||
local_hospital_name: '',
|
||||
appetite: '',
|
||||
water_intake: '',
|
||||
diet_condition: [],
|
||||
weight_change: '',
|
||||
body_feeling: [],
|
||||
sleep_condition: [],
|
||||
eye_condition: [],
|
||||
head_feeling: [],
|
||||
sweat_condition: [],
|
||||
skin_condition: [],
|
||||
urine_condition: [],
|
||||
stool_condition: [],
|
||||
kidney_condition: [],
|
||||
fatty_liver_degree: '',
|
||||
past_history: [],
|
||||
trauma_history: 0,
|
||||
surgery_history: 0,
|
||||
allergy_history: 0,
|
||||
family_history: 0,
|
||||
pregnancy_history: 0,
|
||||
tongue_images: [],
|
||||
report_files: [],
|
||||
symptoms: '',
|
||||
tongue_coating: '',
|
||||
pulse: '',
|
||||
treatment_principle: '',
|
||||
prescription: '',
|
||||
doctor_advice: ''
|
||||
doctor_advice: '',
|
||||
remark: ''
|
||||
});
|
||||
let dingdan_ok=ref(false)
|
||||
// 字典选项
|
||||
@@ -262,6 +421,13 @@ const weightChangeOptions = ref([]);
|
||||
const bodyFeelingOptions = ref([]);
|
||||
const sleepConditionOptions = ref([]);
|
||||
const fattyLiverDegreeOptions = ref([]);
|
||||
const eyeConditionOptions = ref([]);
|
||||
const headFeelingOptions = ref([]);
|
||||
const sweatConditionOptions = ref([]);
|
||||
const skinConditionOptions = ref([]);
|
||||
const urineConditionOptions = ref([]);
|
||||
const stoolConditionOptions = ref([]);
|
||||
const kidneyConditionOptions = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
getDictOptions();
|
||||
@@ -282,7 +448,14 @@ const getDictOptions = async () => {
|
||||
weightChange,
|
||||
bodyFeeling,
|
||||
sleepCondition,
|
||||
fattyLiverDegree
|
||||
fattyLiverDegree,
|
||||
eyeCondition,
|
||||
headFeeling,
|
||||
sweatCondition,
|
||||
skinCondition,
|
||||
urineCondition,
|
||||
stoolCondition,
|
||||
kidneyCondition
|
||||
] = await Promise.all([
|
||||
getDictData('diagnosis_type'),
|
||||
getDictData('syndrome_type'),
|
||||
@@ -294,7 +467,14 @@ const getDictOptions = async () => {
|
||||
getDictData('weight_change'),
|
||||
getDictData('body_feeling'),
|
||||
getDictData('sleep_condition'),
|
||||
getDictData('fatty_liver_degree')
|
||||
getDictData('fatty_liver_degree'),
|
||||
getDictData('eye_condition'),
|
||||
getDictData('head_feeling'),
|
||||
getDictData('sweat_condition'),
|
||||
getDictData('skin_condition'),
|
||||
getDictData('urine_condition'),
|
||||
getDictData('stool_condition'),
|
||||
getDictData('kidney_condition')
|
||||
]);
|
||||
|
||||
diagnosisTypeOptions.value = diagnosisType?.diagnosis_type || [];
|
||||
@@ -308,6 +488,13 @@ const getDictOptions = async () => {
|
||||
bodyFeelingOptions.value = bodyFeeling?.body_feeling || [];
|
||||
sleepConditionOptions.value = sleepCondition?.sleep_condition || [];
|
||||
fattyLiverDegreeOptions.value = fattyLiverDegree?.fatty_liver_degree || [];
|
||||
eyeConditionOptions.value = eyeCondition?.eye_condition || [];
|
||||
headFeelingOptions.value = headFeeling?.head_feeling || [];
|
||||
sweatConditionOptions.value = sweatCondition?.sweat_condition || [];
|
||||
skinConditionOptions.value = skinCondition?.skin_condition || [];
|
||||
urineConditionOptions.value = urineCondition?.urine_condition || [];
|
||||
stoolConditionOptions.value = stoolCondition?.stool_condition || [];
|
||||
kidneyConditionOptions.value = kidneyCondition?.kidney_condition || [];
|
||||
} catch (error) {
|
||||
console.error('获取字典数据失败:', error);
|
||||
}
|
||||
@@ -487,17 +674,31 @@ const getSleepConditionNames = () => {
|
||||
|
||||
const getPastHistoryNames = () => {
|
||||
if (!formData.value.past_history || formData.value.past_history.length === 0) return [];
|
||||
// 处理字符串类型(逗号分隔)或数组类型
|
||||
const values = Array.isArray(formData.value.past_history)
|
||||
? formData.value.past_history
|
||||
: formData.value.past_history.split(',');
|
||||
|
||||
return values.map(val => {
|
||||
const item = pastHistoryOptions.value.find(i => i.value === val);
|
||||
return item ? item.name : val;
|
||||
}).filter(Boolean);
|
||||
};
|
||||
|
||||
// 通用多选名称获取
|
||||
const getMultiNames = (fieldValues, options) => {
|
||||
if (!fieldValues || fieldValues.length === 0) return [];
|
||||
const values = Array.isArray(fieldValues) ? fieldValues : fieldValues.split(',');
|
||||
return values.map(val => {
|
||||
const item = options.find(i => i.value === val);
|
||||
return item ? item.name : val;
|
||||
}).filter(Boolean);
|
||||
};
|
||||
|
||||
const getMaritalStatusName = () => {
|
||||
const map = { 0: '未婚', 1: '已婚', 2: '离异' };
|
||||
const v = formData.value.marital_status;
|
||||
return (v !== '' && v !== undefined && v !== null) ? (map[v] || '-') : '';
|
||||
};
|
||||
|
||||
// 预览图片
|
||||
const previewImage = (current) => {
|
||||
uni.previewImage({
|
||||
@@ -553,6 +754,7 @@ const confirmDiagnosis = async () => {
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
dingdan_ok.value=res.data.is_view?true:false
|
||||
uni.setStorageSync('dingdan_ok', true);
|
||||
dingdan_ok.value=true;
|
||||
setTimeout(() => {
|
||||
@@ -573,34 +775,37 @@ const confirmDiagnosis = async () => {
|
||||
<style lang="scss">
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
padding: 20rpx;
|
||||
padding-bottom: 140rpx; /* 为底部按钮留出空间 */
|
||||
padding: 24rpx;
|
||||
padding-bottom: 180rpx; /* 为底部按钮留出空间 */
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-size: 40rpx; /* 大字体,老年人易读 */
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 36rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 3rpx solid #e0e0e0;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 24rpx;
|
||||
line-height: 1.6;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 36rpx; /* 加大行间距,减少视觉拥挤 */
|
||||
line-height: 1.8;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -609,21 +814,23 @@ const confirmDiagnosis = async () => {
|
||||
|
||||
.label {
|
||||
flex-shrink: 0;
|
||||
width: 180rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
width: 200rpx;
|
||||
font-size: 34rpx; /* 放大标签字体 */
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
padding-top: 2rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-size: 34rpx; /* 放大内容字体 */
|
||||
color: #111; /* 深色,高对比度 */
|
||||
word-break: break-all;
|
||||
line-height: 1.8;
|
||||
|
||||
&.multi-line {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.8;
|
||||
line-height: 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,29 +838,32 @@ const confirmDiagnosis = async () => {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #f0f5ff;
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
padding: 12rpx 28rpx; /* 更大的点击区域 */
|
||||
background-color: #e8f4ff;
|
||||
color: #0066cc; /* 深蓝,对比度更高 */
|
||||
font-size: 32rpx; /* 标签字体也放大 */
|
||||
font-weight: 500;
|
||||
border-radius: 10rpx;
|
||||
border: 1rpx solid #b3d9ff;
|
||||
}
|
||||
|
||||
.image-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.tongue-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 8rpx;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.fixed-bottom {
|
||||
@@ -661,22 +871,23 @@ const confirmDiagnosis = async () => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
padding: 24rpx 40rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.95) 80%, rgba(255, 255, 255, 0) 100%);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
height: 110rpx; /* 更高的按钮,老年人更容易点击 */
|
||||
background: linear-gradient(135deg, #2e7d32 0%, #43a047 100%); /* 绿色,更直观 */
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 44rpx;
|
||||
font-size: 40rpx; /* 按钮字体放大 */
|
||||
font-weight: bold;
|
||||
border-radius: 55rpx;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
box-shadow: 0 8rpx 24rpx rgba(46, 125, 50, 0.35);
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
/* 确认成功页面样式 */
|
||||
@@ -685,16 +896,16 @@ const confirmDiagnosis = async () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: linear-gradient(135deg, #2e7d32 0%, #43a047 100%);
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.success-content {
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
max-width: 620rpx;
|
||||
background: #fff;
|
||||
border-radius: 32rpx;
|
||||
padding: 80rpx 40rpx 60rpx;
|
||||
padding: 90rpx 50rpx 70rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
|
||||
animation: successFadeIn 0.5s ease-out;
|
||||
@@ -712,19 +923,19 @@ const confirmDiagnosis = async () => {
|
||||
}
|
||||
|
||||
.success-icon-wrapper {
|
||||
margin-bottom: 40rpx;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.success-icon-circle {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
background: linear-gradient(135deg, #2e7d32 0%, #66bb6a 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 12rpx 40rpx rgba(82, 196, 26, 0.3);
|
||||
box-shadow: 0 12rpx 40rpx rgba(46, 125, 50, 0.35);
|
||||
animation: successScale 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@@ -743,18 +954,19 @@ const confirmDiagnosis = async () => {
|
||||
}
|
||||
|
||||
.success-icon-check {
|
||||
font-size: 100rpx;
|
||||
font-size: 110rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
font-size: 48rpx;
|
||||
font-size: 56rpx; /* 成功标题大字 */
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 28rpx;
|
||||
animation: successSlideIn 0.6s ease-out 0.2s both;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
@keyframes successSlideIn {
|
||||
@@ -769,16 +981,16 @@ const confirmDiagnosis = async () => {
|
||||
}
|
||||
|
||||
.success-message {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
font-size: 36rpx; /* 提示文字也放大 */
|
||||
color: #444;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 16rpx;
|
||||
animation: successSlideIn 0.6s ease-out 0.3s both;
|
||||
}
|
||||
|
||||
.success-tips {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-size: 30rpx;
|
||||
color: #777;
|
||||
animation: successSlideIn 0.6s ease-out 0.4s both;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loading" class="loading-wrapper">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 支付成功 -->
|
||||
<view v-else-if="paySuccess" class="success-container">
|
||||
<view class="success-content">
|
||||
<view class="success-icon-wrapper">
|
||||
<view class="success-icon-circle">
|
||||
<text class="success-icon-check">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="success-title">支付成功</view>
|
||||
<view class="success-amount">¥ {{ orderInfo.amount }}</view>
|
||||
<view class="success-message">订单号:{{ orderInfo.order_no }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单不存在 / 错误 -->
|
||||
<view v-else-if="errorMsg" class="error-wrapper">
|
||||
<view class="error-icon">!</view>
|
||||
<text class="error-text">{{ errorMsg }}</text>
|
||||
<button class="retry-btn" @click="loadOrderDetail">重试</button>
|
||||
</view>
|
||||
|
||||
<!-- 订单详情 & 付款 -->
|
||||
<view v-else-if="orderInfo" class="pay-wrapper">
|
||||
<!-- 商户信息 -->
|
||||
<view class="merchant-info">
|
||||
<text class="merchant-title">付款给甄养堂医院</text>
|
||||
</view>
|
||||
|
||||
<!-- 金额区域 -->
|
||||
<view class="amount-section">
|
||||
<text class="amount-label">付款金额</text>
|
||||
<view class="amount-row">
|
||||
<text class="amount-symbol">¥</text>
|
||||
<text class="amount-value">{{ formatAmount(orderInfo.amount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 订单信息 -->
|
||||
<view class="order-info-section">
|
||||
<view class="info-row">
|
||||
<text class="info-label">订单号</text>
|
||||
<text class="info-value">{{ orderInfo.order_no }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">订单类型</text>
|
||||
<text class="info-value">{{ orderInfo.order_type_desc }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="orderInfo.remark">
|
||||
<text class="info-label">备注</text>
|
||||
<text class="info-value">{{ orderInfo.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 订单状态 -->
|
||||
<view v-if="orderInfo.status == 2" class="paid-notice">
|
||||
<view class="paid-icon">✓</view>
|
||||
<text class="paid-text">该订单已支付</text>
|
||||
</view>
|
||||
|
||||
<!-- 付款按钮 -->
|
||||
<view v-else class="pay-btn-wrapper">
|
||||
<button
|
||||
class="pay-btn"
|
||||
:disabled="paying"
|
||||
:loading="paying"
|
||||
@click="handlePay"
|
||||
>
|
||||
{{ paying ? '支付中...' : '付款' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const loading = ref(true)
|
||||
const paying = ref(false)
|
||||
const paySuccess = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const orderInfo = ref(null)
|
||||
const orderNo = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
loadOrderDetail()
|
||||
})
|
||||
|
||||
const parsePageParams = (options) => {
|
||||
const params = {}
|
||||
if (options.scene) {
|
||||
try {
|
||||
const decodedScene = decodeURIComponent(options.scene)
|
||||
decodedScene.split('&').forEach(item => {
|
||||
const [key, value] = item.split('=')
|
||||
if (key && value) {
|
||||
params[key] = value
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('解析scene参数失败:', error)
|
||||
}
|
||||
}
|
||||
Object.keys(options).forEach(key => {
|
||||
if (key !== 'scene' && options[key]) {
|
||||
params[key] = options[key]
|
||||
}
|
||||
})
|
||||
return params
|
||||
}
|
||||
|
||||
const loadOrderDetail = async () => {
|
||||
loading.value = true
|
||||
errorMsg.value = ''
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const params = parsePageParams(currentPage.options || {})
|
||||
orderNo.value = params.order_no || ''
|
||||
|
||||
if (!orderNo.value) {
|
||||
loading.value = false
|
||||
errorMsg.value = '缺少订单号'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/tcm/getOrderByNo',
|
||||
method: 'GET',
|
||||
data: { order_no: orderNo.value }
|
||||
}, false)
|
||||
|
||||
if (res.code === 1) {
|
||||
orderInfo.value = res.data
|
||||
} else {
|
||||
errorMsg.value = res.msg || '订单加载失败'
|
||||
}
|
||||
} catch (err) {
|
||||
errorMsg.value = '网络请求失败'
|
||||
console.error('加载订单失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const formatAmount = (amount) => {
|
||||
const num = parseFloat(amount)
|
||||
if (isNaN(num)) return '0.00'
|
||||
return num.toFixed(2)
|
||||
}
|
||||
|
||||
const ensureLogin = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = uni.getStorageSync('token')
|
||||
if (token) {
|
||||
resolve(token)
|
||||
return
|
||||
}
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: async (loginRes) => {
|
||||
try {
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/login/mnpLogin',
|
||||
method: 'POST',
|
||||
data: { code: loginRes.code }
|
||||
}, false)
|
||||
if (res.code === 1 && res.data && res.data.token) {
|
||||
uni.setStorageSync('token', res.data.token)
|
||||
uni.setStorageSync('userData', res.data)
|
||||
resolve(res.data.token)
|
||||
} else {
|
||||
reject(new Error(res.msg || '登录失败'))
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handlePay = async () => {
|
||||
if (paying.value || !orderInfo.value) return
|
||||
if (orderInfo.value.status == 2) {
|
||||
uni.showToast({ title: '订单已支付', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
paying.value = true
|
||||
|
||||
try {
|
||||
await ensureLogin()
|
||||
|
||||
const res = await proxy.apiUrl({
|
||||
url: '/api/pay/prepay',
|
||||
method: 'POST',
|
||||
data: {
|
||||
from: 'order',
|
||||
order_id: orderInfo.value.id,
|
||||
pay_way: 2,
|
||||
redirect: '/pages/order/order'
|
||||
}
|
||||
}, false)
|
||||
|
||||
if (res.code !== 1 || !res.data) {
|
||||
uni.showToast({ title: res.msg || '发起支付失败', icon: 'none' })
|
||||
paying.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const payParams = res.data
|
||||
wx.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType || 'RSA',
|
||||
paySign: payParams.paySign,
|
||||
success: () => {
|
||||
paySuccess.value = true
|
||||
paying.value = false
|
||||
},
|
||||
fail: (err) => {
|
||||
paying.value = false
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败,请重试', icon: 'none' })
|
||||
}
|
||||
console.error('支付失败:', err)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
paying.value = false
|
||||
uni.showToast({ title: '支付异常,请重试', icon: 'none' })
|
||||
console.error('支付异常:', err)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.loading-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 6rpx solid #e0e0e0;
|
||||
border-top-color: #1989fa;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 错误页 */
|
||||
.error-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
line-height: 120rpx;
|
||||
text-align: center;
|
||||
font-size: 70rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background-color: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
width: 320rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background-color: #1989fa;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
border-radius: 44rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 付款主体 */
|
||||
.pay-wrapper {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.merchant-info {
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.merchant-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 金额区域 */
|
||||
.amount-section {
|
||||
padding: 40rpx 0 50rpx;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.amount-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.amount-symbol {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 80rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: #e8e8e8;
|
||||
margin: 10rpx 0 30rpx;
|
||||
}
|
||||
|
||||
/* 订单信息 */
|
||||
.order-info-section {
|
||||
padding: 10rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 已支付提示 */
|
||||
.paid-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
.paid-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
background-color: #52c41a;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.paid-text {
|
||||
font-size: 32rpx;
|
||||
color: #52c41a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 付款按钮 */
|
||||
.pay-btn-wrapper {
|
||||
padding: 60rpx 40rpx 0;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
text-align: center;
|
||||
background-color: #1989fa;
|
||||
color: #fff;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
letter-spacing: 4rpx;
|
||||
|
||||
&[disabled] {
|
||||
background-color: #a0cfff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 支付成功页 */
|
||||
.success-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1989fa 0%, #4da6ff 100%);
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.success-content {
|
||||
width: 100%;
|
||||
max-width: 620rpx;
|
||||
background: #fff;
|
||||
border-radius: 32rpx;
|
||||
padding: 90rpx 50rpx 70rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(-40rpx); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.success-icon-wrapper {
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.success-icon-circle {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
margin: 0 auto;
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 12rpx 40rpx rgba(82, 196, 26, 0.35);
|
||||
animation: scaleIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
0% { transform: scale(0); opacity: 0; }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.success-icon-check {
|
||||
font-size: 110rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
font-size: 52rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.success-amount {
|
||||
font-size: 56rpx;
|
||||
font-weight: bold;
|
||||
color: #1989fa;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user