Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69c3e6d30e | ||
|
|
f9dd9ad3a2 | ||
|
|
5629955dd3 | ||
|
|
77a690a071 | ||
|
|
8b455b1920 | ||
|
|
2d2a8abfd4 | ||
|
|
2c739324d6 | ||
|
|
cae91485f6 | ||
|
|
b66f47dfbb | ||
|
|
fb0df465a1 | ||
|
|
cef264c692 | ||
|
|
fe7d56027b | ||
|
|
4ff6564ffb | ||
|
|
3f699fa2c6 | ||
|
|
dcf6c37195 | ||
|
|
a2cb9fd8ba | ||
|
|
aee0973a53 | ||
|
|
5564c92d33 | ||
|
|
550332d963 | ||
|
|
105f9eafa2 | ||
|
|
63ea420c03 | ||
|
|
6ec1b72063 | ||
|
|
39843877fd | ||
|
|
6294a3db0c | ||
|
|
cd369ddf09 | ||
|
|
1693b0e236 | ||
|
|
ce5fc2355b | ||
|
|
4f9ba9cf44 | ||
|
|
da8ef2019f | ||
|
|
800aabf5ee | ||
|
|
184ff88bd2 | ||
|
|
d233a42754 | ||
|
|
a130f428b9 | ||
|
|
ca03f17f28 | ||
|
|
e57fd125f7 | ||
|
|
696cb2e0fa | ||
|
|
c4f322edd6 | ||
|
|
7ed22e2d58 | ||
|
|
11ade5d7ae |
12
.cursor/rules/Code-Standards.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
description:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
|
||||
2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行
|
||||
3. 有封装好的方法、组件需要复用,不要重复造轮子
|
||||
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show)
|
||||
5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化
|
||||
6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
|
||||
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
|
||||
12
.cursor/rules/Code-Standards.mdc
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
description:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则
|
||||
2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行
|
||||
3. 有封装好的方法、组件需要复用,不要重复造轮子
|
||||
4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show)
|
||||
5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化
|
||||
6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.`
|
||||
7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定
|
||||
2
.gitignore
vendored
@@ -117,3 +117,5 @@ dist
|
||||
/.hbuilderx/
|
||||
/.idea/
|
||||
/database/
|
||||
/utils/utils.js
|
||||
/.cursor/skills/
|
||||
|
||||
3
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -14,7 +14,7 @@
|
||||
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="myValues">
|
||||
<value>
|
||||
<list size="16">
|
||||
<list size="17">
|
||||
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||
@@ -31,6 +31,7 @@
|
||||
<item index="13" class="java.lang.String" itemvalue="u-form-item" />
|
||||
<item index="14" class="java.lang.String" itemvalue="u-checkbox-group" />
|
||||
<item index="15" class="java.lang.String" itemvalue="u-checkbox" />
|
||||
<item index="16" class="java.lang.String" itemvalue="u-parse" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
|
||||
1
.mimocode/.cron-lock
Normal file
@@ -0,0 +1 @@
|
||||
{"pid":36108,"startedAt":1783929676073}
|
||||
115
App.vue
@@ -12,6 +12,15 @@
|
||||
export default {
|
||||
// //初始化状态-如果登录过,保持登录状态
|
||||
onLaunch: async function() {
|
||||
|
||||
silentLogin().then((loginResult) => {
|
||||
if (loginResult.success) {
|
||||
} else {
|
||||
console.error('静默登录失败:', loginResult.message);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('静默登录异常:', error);
|
||||
});
|
||||
const version = 4 // 如果当前版本和后台返回的版本一致,那就是审核中,审核通过后 后台设置加1,如果后台的比本地的大 那就是生产版本
|
||||
const res = await getVersion()
|
||||
const val = Number(res.data.data.value)
|
||||
@@ -34,54 +43,54 @@
|
||||
// 存储当前版本,也可以使用globalData
|
||||
uni.setStorageSync('isShow_envVersion', isShow)
|
||||
|
||||
// 检查是否已登录,如果未登录才检查配置并决定是否静默登录
|
||||
const token = uni.getStorageSync('token');
|
||||
const userInfo = uni.getStorageSync('userinfo');
|
||||
// // 检查是否已登录,如果未登录才检查配置并决定是否静默登录
|
||||
// const token = uni.getStorageSync('token');
|
||||
// const userInfo = uni.getStorageSync('userinfo');
|
||||
|
||||
if (!token || !userInfo) {
|
||||
// if (!token || !userInfo) {
|
||||
// 未登录,获取系统配置,判断是否需要静默登录
|
||||
try {
|
||||
const configRes = await getSystemConfig({
|
||||
method: "get",
|
||||
data: {
|
||||
store_id: uni.getStorageSync('store_id') || '11001'
|
||||
}
|
||||
});
|
||||
|
||||
// try {
|
||||
// const configRes = getSystemConfig({
|
||||
// method: "get",
|
||||
// data: {
|
||||
// store_id: uni.getStorageSync('store_id') || '11001'
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// 根据配置决定是否静默登录
|
||||
// 注意:需要根据实际接口返回格式解析 need_login 字段
|
||||
const isGuest = isGuestMode();
|
||||
let needLogin = true; // 默认需要登录页
|
||||
|
||||
if (isGuest && (configRes.data.code === 0 || configRes.data.code === '0')) {
|
||||
// 新格式:从 result 中获取
|
||||
const configData = configRes.data.result || {};
|
||||
needLogin = configData.need_login !== false; // false 表示静默登录
|
||||
} else if (!isGuest && (configRes.data.errcode === 0 || configRes.data.errcode === '0')) {
|
||||
// 旧格式:从 data 中获取
|
||||
const configData = configRes.data.data || {};
|
||||
needLogin = configData.need_login !== false; // false 表示静默登录
|
||||
}
|
||||
|
||||
// 如果配置为静默登录(need_login: false),直接执行静默登录
|
||||
if (!needLogin) {
|
||||
// 执行静默登录(不显示提示,不跳转)
|
||||
silentLogin().then((loginResult) => {
|
||||
if (loginResult.success) {
|
||||
console.log('静默登录成功');
|
||||
} else {
|
||||
console.error('静默登录失败:', loginResult.message);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('静默登录异常:', error);
|
||||
});
|
||||
}
|
||||
// 如果配置为需要登录页(need_login: true),不执行任何操作,等待用户手动登录
|
||||
} catch (error) {
|
||||
// 配置接口调用失败,不影响应用启动,仅记录日志
|
||||
console.error('获取系统配置失败:', error);
|
||||
}
|
||||
}
|
||||
// const isGuest = isGuestMode();
|
||||
// let needLogin = false; // 默认需要登录页
|
||||
//
|
||||
// if (isGuest && (configRes.data.code === 0 || configRes.data.code === '0')) {
|
||||
// // 新格式:从 result 中获取
|
||||
// const configData = configRes.data.result || {};
|
||||
// needLogin = configData.need_login !== false; // false 表示静默登录
|
||||
// } else if (!isGuest && (configRes.data?.errcode === 0 || configRes.data?.errcode === '0')) {
|
||||
// // 旧格式:从 data 中获取
|
||||
// const configData = configRes.data.data || {};
|
||||
// needLogin = configData.need_login !== false; // false 表示静默登录
|
||||
// }
|
||||
//
|
||||
// // 如果配置为静默登录(need_login: false),直接执行静默登录
|
||||
// if (!needLogin) {
|
||||
// // 执行静默登录(不显示提示,不跳转)
|
||||
// silentLogin().then((loginResult) => {
|
||||
// if (loginResult.success) {
|
||||
// console.log('静默登录成功');
|
||||
// } else {
|
||||
// console.error('静默登录失败:', loginResult.message);
|
||||
// }
|
||||
// }).catch((error) => {
|
||||
// console.error('静默登录异常:', error);
|
||||
// });
|
||||
// }
|
||||
// // 如果配置为需要登录页(need_login: true),不执行任何操作,等待用户手动登录
|
||||
// } catch (error) {
|
||||
// // 配置接口调用失败,不影响应用启动,仅记录日志
|
||||
// console.error('获取系统配置失败:', error);
|
||||
// }
|
||||
// }
|
||||
|
||||
console.log('获取更新机制', uni.canIUse('getUpdateManager'))
|
||||
// 获取小程序更新机制兼容
|
||||
@@ -145,8 +154,6 @@
|
||||
},
|
||||
|
||||
onShow: function(e) {
|
||||
// console.log('App Show')
|
||||
// this.$store.dispatch('initLogin')
|
||||
// 解析scene里面的参数
|
||||
if (e === undefined)
|
||||
return {};
|
||||
@@ -185,6 +192,20 @@
|
||||
console.log('userId', userId, st_id[0].split('su_id=')[1], res);
|
||||
})
|
||||
}
|
||||
// 5.3 处理门店切换/设置
|
||||
// const storeId = uni.getStorageSync('store_id');
|
||||
// if (storeId) {
|
||||
// storeChange({
|
||||
// method: "post",
|
||||
// data: {
|
||||
// store_id: storeId
|
||||
// }
|
||||
// }).catch((err) => {
|
||||
// console.error('切换门店失败:', err);
|
||||
// });
|
||||
// } else {
|
||||
// uni.setStorageSync('store_id', res.data.data.data.store_id);
|
||||
// }
|
||||
// console.log(st_id, '4444111111111');
|
||||
// console.log(scene, '4444');
|
||||
}
|
||||
@@ -246,7 +267,7 @@
|
||||
|
||||
// 从消息中提取发送者信息
|
||||
let senderName = '新消息';
|
||||
let senderAvatar = '/static/xx/ysxx.png';
|
||||
let senderAvatar = 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png';
|
||||
|
||||
// 尝试解析发送者ID获取更多信息
|
||||
const senderId = message.sender_user_id || '';
|
||||
|
||||
@@ -21,7 +21,7 @@ export function isGuestMode() {
|
||||
export function setGuestMode() {
|
||||
uni.setStorageSync(GUEST_MODE_KEY, true);
|
||||
// 同时设置store_id为11001
|
||||
uni.setStorageSync('store_id', GUEST_STORE_ID);
|
||||
// uni.setStorageSync('store_id', GUEST_STORE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
30
common/utils/drug.js
Normal file
@@ -0,0 +1,30 @@
|
||||
export function isWesternRxDrug(drug) {
|
||||
return drug && drug.type == 2 && (drug.is_otc === 0 || drug.is_otc === '0');
|
||||
}
|
||||
|
||||
export function isHealthFood(drug) {
|
||||
return drug && (drug.type == 3 || drug.category_type === 'health_food');
|
||||
}
|
||||
|
||||
export function canShowFunction(drug, prescriptionApproved) {
|
||||
if (isHealthFood(drug)) return false;
|
||||
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function canShowUsage(drug, prescriptionApproved) {
|
||||
if (isHealthFood(drug)) return false;
|
||||
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
|
||||
return drug && drug.type != 3;
|
||||
}
|
||||
|
||||
export function canShowInstruction(drug, prescriptionApproved) {
|
||||
if (isWesternRxDrug(drug)) return !!prescriptionApproved;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isPrescriptionApprovedStatus(data) {
|
||||
if (!data) return false;
|
||||
const ps = data.prescription_status ?? data.status;
|
||||
return data.status === true || ps === 1 || ps === '1' || ps === 3 || ps === '3' || ps === 4 || ps === '4';
|
||||
}
|
||||
0
components/HealthTeaCard/HealthTeaCard.vue
Normal file
@@ -2,7 +2,7 @@
|
||||
<view v-if="visible" class="notification-wrapper" :class="{ 'slide-in': visible }" :style="{ paddingTop: notificationTop + 'px' }">
|
||||
<view class="notification" @click="handleClick">
|
||||
<!-- 头像 -->
|
||||
<image class="avatar" :src="avatar || '/static/xx/ysxx.png'" mode="aspectFill" />
|
||||
<image class="avatar" :src="avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png'" mode="aspectFill" />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view class="content">
|
||||
|
||||
539
components/c-upload/c-upload.vue
Normal file
@@ -0,0 +1,539 @@
|
||||
<template>
|
||||
<view class="u-upload" v-if="!disabled">
|
||||
<view
|
||||
v-if="showUploadList"
|
||||
class="u-list-item u-preview-wrap"
|
||||
v-for="(item, index) in lists"
|
||||
:key="index"
|
||||
:style="{
|
||||
width: $u.addUnit(width),
|
||||
height: $u.addUnit(height)
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-if="deletable"
|
||||
class="u-delete-icon"
|
||||
@tap.stop="deleteItem(index)"
|
||||
:style="{
|
||||
background: delBgColor
|
||||
}"
|
||||
>
|
||||
<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
|
||||
</view>
|
||||
<u-line-progress
|
||||
v-if="showProgress && item.progress > 0 && item.progress != 100 && !item.error"
|
||||
:show-percent="false"
|
||||
height="16"
|
||||
class="u-progress"
|
||||
:percent="item.progress"
|
||||
></u-line-progress>
|
||||
<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
|
||||
<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
|
||||
</view>
|
||||
<slot name="file" :file="lists"></slot>
|
||||
<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
|
||||
<slot name="addBtn"></slot>
|
||||
<view
|
||||
v-if="!customBtn"
|
||||
class="u-list-item u-add-wrap"
|
||||
hover-class="u-add-wrap__hover"
|
||||
hover-stay-time="150"
|
||||
:style="{
|
||||
width: $u.addUnit(width),
|
||||
height: $u.addUnit(height)
|
||||
}"
|
||||
>
|
||||
<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
|
||||
<view class="u-add-tips">{{ uploadText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { prepareImagePath } from '@/utils/image-compress.js';
|
||||
|
||||
export default {
|
||||
name: 'c-upload',
|
||||
props: {
|
||||
showUploadList: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: 52
|
||||
},
|
||||
showProgress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
imageMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
header: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
formData: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
},
|
||||
sizeType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['original', 'compressed'];
|
||||
}
|
||||
},
|
||||
sourceType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['album', 'camera'];
|
||||
}
|
||||
},
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
deletable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maxSize: {
|
||||
type: [String, Number],
|
||||
default: Number.MAX_VALUE
|
||||
},
|
||||
fileList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
uploadText: {
|
||||
type: String,
|
||||
default: '选择图片'
|
||||
},
|
||||
autoUpload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 200
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 200
|
||||
},
|
||||
delBgColor: {
|
||||
type: String,
|
||||
default: '#fa3534'
|
||||
},
|
||||
delColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
delIcon: {
|
||||
type: String,
|
||||
default: 'close'
|
||||
},
|
||||
toJson: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
beforeRemove: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
limitType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image'];
|
||||
}
|
||||
},
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
isInCount: true,
|
||||
uploading: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
fileList: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
val.map(value => {
|
||||
let tmp = this.lists.some(val => {
|
||||
return val.url == value.url;
|
||||
})
|
||||
!tmp && this.lists.push({ url: value.url, error: false, progress: 100 });
|
||||
});
|
||||
}
|
||||
},
|
||||
lists(n) {
|
||||
this.$emit('on-list-change', n, this.index);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this.lists = [];
|
||||
},
|
||||
reUpload() {
|
||||
this.uploadFile();
|
||||
},
|
||||
selectFile() {
|
||||
if (this.disabled) return;
|
||||
const { maxCount, multiple, maxSize, sizeType, lists, sourceType } = this;
|
||||
const newMaxCount = maxCount - lists.length;
|
||||
uni.chooseImage({
|
||||
count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
|
||||
sourceType: sourceType,
|
||||
sizeType,
|
||||
success: async (res) => {
|
||||
const listOldLength = this.lists.length;
|
||||
for (let index = 0; index < res.tempFiles.length; index++) {
|
||||
const val = res.tempFiles[index];
|
||||
if (!this.checkFileExt(val)) continue;
|
||||
if (!multiple && index >= 1) continue;
|
||||
if (val.size > maxSize) {
|
||||
this.$emit('on-oversize', val, this.lists, this.index);
|
||||
this.showToast('超出允许的文件大小');
|
||||
continue;
|
||||
}
|
||||
if (maxCount <= lists.length) {
|
||||
this.$emit('on-exceed', val, this.lists, this.index);
|
||||
this.showToast('超出最大允许的文件个数');
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const prepared = await prepareImagePath({
|
||||
path: val.path,
|
||||
size: val.size,
|
||||
});
|
||||
if (!prepared) continue;
|
||||
|
||||
lists.push({
|
||||
url: prepared.path,
|
||||
progress: 0,
|
||||
error: false,
|
||||
file: {
|
||||
...val,
|
||||
path: prepared.path,
|
||||
size: prepared.size,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.$emit('on-choose-fail', error);
|
||||
this.showToast('图片处理失败');
|
||||
}
|
||||
}
|
||||
this.$emit('on-choose-complete', this.lists, this.index);
|
||||
if (this.autoUpload) this.uploadFile(listOldLength);
|
||||
},
|
||||
fail: (error) => {
|
||||
this.$emit('on-choose-fail', error);
|
||||
}
|
||||
});
|
||||
},
|
||||
showToast(message, force = false) {
|
||||
if (this.showTips || force) {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
upload() {
|
||||
this.uploadFile();
|
||||
},
|
||||
retry(index) {
|
||||
this.lists[index].progress = 0;
|
||||
this.lists[index].error = false;
|
||||
this.lists[index].response = null;
|
||||
uni.showLoading({
|
||||
title: '重新上传'
|
||||
});
|
||||
this.uploadFile(index);
|
||||
},
|
||||
async uploadFile(index = 0) {
|
||||
if (this.disabled) return;
|
||||
if (this.uploading) return;
|
||||
if (index >= this.lists.length) {
|
||||
this.$emit('on-uploaded', this.lists, this.index);
|
||||
return;
|
||||
}
|
||||
if (this.lists[index].progress == 100) {
|
||||
if (this.autoUpload == false) this.uploadFile(index + 1);
|
||||
return;
|
||||
}
|
||||
if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
|
||||
let beforeResponse = this.beforeUpload.bind(this.$u.$parent.call(this))(index, this.lists);
|
||||
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
|
||||
await beforeResponse.then(res => {
|
||||
}).catch(err => {
|
||||
return this.uploadFile(index + 1);
|
||||
})
|
||||
} else if (beforeResponse === false) {
|
||||
return this.uploadFile(index + 1);
|
||||
}
|
||||
}
|
||||
if (!this.action) {
|
||||
this.showToast('请配置上传地址', true);
|
||||
return;
|
||||
}
|
||||
this.lists[index].error = false;
|
||||
this.uploading = true;
|
||||
const task = uni.uploadFile({
|
||||
url: this.action,
|
||||
filePath: this.lists[index].url,
|
||||
name: this.name,
|
||||
formData: this.formData,
|
||||
header: this.header,
|
||||
// #ifdef MP-ALIPAY
|
||||
fileType:'image',
|
||||
// #endif
|
||||
success: res => {
|
||||
let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
|
||||
if (![200, 201, 204].includes(res.statusCode)) {
|
||||
this.uploadError(index, data);
|
||||
} else {
|
||||
this.lists[index].response = data;
|
||||
this.lists[index].progress = 100;
|
||||
this.lists[index].error = false;
|
||||
this.$emit('on-success', data, index, this.lists, this.index);
|
||||
}
|
||||
},
|
||||
fail: e => {
|
||||
this.uploadError(index, e);
|
||||
},
|
||||
complete: res => {
|
||||
uni.hideLoading();
|
||||
this.uploading = false;
|
||||
this.uploadFile(index + 1);
|
||||
this.$emit('on-change', res, index, this.lists, this.index);
|
||||
}
|
||||
});
|
||||
task.onProgressUpdate(res => {
|
||||
if (res.progress > 0) {
|
||||
this.lists[index].progress = res.progress;
|
||||
this.$emit('on-progress', res, index, this.lists, this.index);
|
||||
}
|
||||
});
|
||||
},
|
||||
uploadError(index, err) {
|
||||
this.lists[index].progress = 0;
|
||||
this.lists[index].error = true;
|
||||
this.lists[index].response = null;
|
||||
this.$emit('on-error', err, index, this.lists, this.index);
|
||||
this.showToast('上传失败,请重试');
|
||||
},
|
||||
deleteItem(index) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '您确定要删除此项吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
|
||||
let beforeResponse = this.beforeRemove.bind(this.$u.$parent.call(this))(index, this.lists);
|
||||
if (!!beforeResponse && typeof beforeResponse.then === 'function') {
|
||||
await beforeResponse.then(res => {
|
||||
this.handlerDeleteItem(index);
|
||||
}).catch(err => {
|
||||
this.showToast('已终止移除');
|
||||
})
|
||||
} else if (beforeResponse === false) {
|
||||
this.showToast('已终止移除');
|
||||
} else {
|
||||
this.handlerDeleteItem(index);
|
||||
}
|
||||
} else {
|
||||
this.handlerDeleteItem(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
handlerDeleteItem(index) {
|
||||
if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
|
||||
typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
|
||||
}
|
||||
this.lists.splice(index, 1);
|
||||
this.$forceUpdate();
|
||||
this.$emit('on-remove', index, this.lists, this.index);
|
||||
this.showToast('移除成功');
|
||||
},
|
||||
remove(index) {
|
||||
if (index >= 0 && index < this.lists.length) {
|
||||
this.lists.splice(index, 1);
|
||||
this.$emit('on-list-change', this.lists, this.index);
|
||||
}
|
||||
},
|
||||
doPreviewImage(url, index) {
|
||||
if (!this.previewFullImage) return;
|
||||
const images = this.lists.map(item => item.url || item.path);
|
||||
uni.previewImage({
|
||||
urls: images,
|
||||
current: url,
|
||||
success: () => {
|
||||
this.$emit('on-preview', url, this.lists, this.index);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '预览图片失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
checkFileExt(file) {
|
||||
let noArrowExt = false;
|
||||
let fileExt = '';
|
||||
const reg = /.+\./;
|
||||
// #ifdef H5
|
||||
fileExt = file.name.replace(reg, "").toLowerCase();
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
fileExt = file.path.replace(reg, "").toLowerCase();
|
||||
// #endif
|
||||
noArrowExt = this.limitType.some(ext => {
|
||||
return ext.toLowerCase() === fileExt;
|
||||
})
|
||||
if (!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
|
||||
return noArrowExt;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'uview-ui/libs/css/style.components.scss';
|
||||
|
||||
.u-upload {
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-list-item {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
overflow: hidden;
|
||||
margin: 10rpx;
|
||||
background: rgb(244, 245, 246);
|
||||
position: relative;
|
||||
border-radius: 10rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-preview-wrap {
|
||||
border: 1px solid rgb(235, 236, 238);
|
||||
}
|
||||
|
||||
.u-add-wrap {
|
||||
flex-direction: column;
|
||||
color: $u-content-color;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.u-add-tips {
|
||||
margin-top: 20rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.u-add-wrap__hover {
|
||||
background-color: rgb(235, 236, 238);
|
||||
}
|
||||
|
||||
.u-preview-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.u-delete-icon {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
z-index: 10;
|
||||
background-color: $u-type-error;
|
||||
border-radius: 100rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-icon {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-progress {
|
||||
position: absolute;
|
||||
bottom: 10rpx;
|
||||
left: 8rpx;
|
||||
right: 8rpx;
|
||||
z-index: 9;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.u-error-btn {
|
||||
color: #ffffff;
|
||||
background-color: $u-type-error;
|
||||
font-size: 20rpx;
|
||||
padding: 4px 0;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
676
components/image-compress-popup/image-compress-popup.vue
Normal file
@@ -0,0 +1,676 @@
|
||||
<template>
|
||||
|
||||
<u-popup v-model="visible" mode="center" width="85%" border-radius="16" :mask-close-able="false">
|
||||
|
||||
<view class="popup-wrap">
|
||||
|
||||
<view class="popup-title">图片较大</view>
|
||||
|
||||
<view v-if="sizeText" class="size-text">{{ sizeText }}</view>
|
||||
|
||||
|
||||
|
||||
<view v-if="status === 'compressing'" class="compressing-panel">
|
||||
|
||||
<image
|
||||
|
||||
v-if="originalPath"
|
||||
|
||||
:src="originalPath"
|
||||
|
||||
mode="aspectFit"
|
||||
|
||||
class="preview-image single"
|
||||
|
||||
@click="previewImage(originalPath)"
|
||||
|
||||
/>
|
||||
|
||||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||||
|
||||
<view class="compressing-status">
|
||||
|
||||
<u-loading mode="circle" />
|
||||
|
||||
<text class="progress-text">{{ progressText }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view v-else-if="status === 'ready'" class="compare-panel">
|
||||
|
||||
<view class="preview-column">
|
||||
|
||||
<text class="preview-label">原图</text>
|
||||
|
||||
<image
|
||||
|
||||
:src="originalPath"
|
||||
|
||||
mode="aspectFit"
|
||||
|
||||
class="preview-image"
|
||||
|
||||
@click="previewImage(originalPath)"
|
||||
|
||||
/>
|
||||
|
||||
<text class="preview-size">{{ formatFileSize(originalSize) }}</text>
|
||||
|
||||
<text class="preview-hint">点击预览</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="preview-column">
|
||||
|
||||
<text class="preview-label">压缩后</text>
|
||||
|
||||
<image
|
||||
|
||||
:src="compressedPath"
|
||||
|
||||
mode="aspectFit"
|
||||
|
||||
class="preview-image"
|
||||
|
||||
@click="previewImage(compressedPath)"
|
||||
|
||||
/>
|
||||
|
||||
<text class="preview-size">{{ formatFileSize(compressedSize) }}</text>
|
||||
|
||||
<text class="preview-hint">点击预览</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view v-else-if="status === 'gifSkipped'" class="gif-panel">
|
||||
|
||||
<image
|
||||
|
||||
v-if="originalPath"
|
||||
|
||||
:src="originalPath"
|
||||
|
||||
mode="aspectFit"
|
||||
|
||||
class="preview-image single"
|
||||
|
||||
@click="previewImage(originalPath)"
|
||||
|
||||
/>
|
||||
|
||||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||||
|
||||
<text class="hint-text">GIF 不支持压缩,将上传原图。</text>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view v-else class="error-panel">
|
||||
|
||||
<image
|
||||
|
||||
v-if="originalPath"
|
||||
|
||||
:src="originalPath"
|
||||
|
||||
mode="aspectFit"
|
||||
|
||||
class="preview-image single"
|
||||
|
||||
@click="previewImage(originalPath)"
|
||||
|
||||
/>
|
||||
|
||||
<text v-if="originalPath" class="preview-hint">点击预览</text>
|
||||
|
||||
<text class="error-text">{{ errorMessage }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="footer-actions">
|
||||
|
||||
<view class="btn btn-default" @click="finish('cancel')">取消</view>
|
||||
|
||||
<view
|
||||
|
||||
v-if="status === 'ready' || status === 'error'"
|
||||
|
||||
class="btn btn-default"
|
||||
|
||||
@click="finish('original')"
|
||||
|
||||
>
|
||||
|
||||
{{ originalButtonLabel }}
|
||||
|
||||
</view>
|
||||
|
||||
<view
|
||||
|
||||
v-if="status === 'ready'"
|
||||
|
||||
class="btn btn-primary"
|
||||
|
||||
@click="finish('compressed')"
|
||||
|
||||
>
|
||||
|
||||
{{ compressedButtonLabel }}
|
||||
|
||||
</view>
|
||||
|
||||
<view
|
||||
|
||||
v-if="status === 'gifSkipped'"
|
||||
|
||||
class="btn btn-primary"
|
||||
|
||||
@click="finish('original')"
|
||||
|
||||
>
|
||||
|
||||
{{ originalButtonLabel }}
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</u-popup>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import {
|
||||
|
||||
formatFileSize,
|
||||
|
||||
formatSavingsPercent,
|
||||
|
||||
isGifPath,
|
||||
|
||||
tryCompressImage,
|
||||
|
||||
} from '@/utils/image-compress.js';
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
name: 'ImageCompressPopup',
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
visible: false,
|
||||
|
||||
status: 'compressing',
|
||||
|
||||
progressText: '正在压缩,请稍候…',
|
||||
|
||||
errorMessage: '',
|
||||
|
||||
originalPath: '',
|
||||
|
||||
compressedPath: '',
|
||||
|
||||
originalSize: 0,
|
||||
|
||||
compressedSize: 0,
|
||||
|
||||
skipped: false,
|
||||
|
||||
resolver: null,
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
sizeText() {
|
||||
|
||||
if (!this.originalSize) return '';
|
||||
|
||||
const originalText = formatFileSize(this.originalSize);
|
||||
|
||||
if (this.status === 'gifSkipped') {
|
||||
|
||||
return `原图 ${originalText}`;
|
||||
|
||||
}
|
||||
|
||||
if (this.compressedSize && this.status === 'ready') {
|
||||
|
||||
const savings = formatSavingsPercent(this.originalSize, this.compressedSize);
|
||||
|
||||
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}${savings}`;
|
||||
|
||||
}
|
||||
|
||||
if (this.compressedSize) {
|
||||
|
||||
return `原图 ${originalText} → 压缩后 ${formatFileSize(this.compressedSize)}`;
|
||||
|
||||
}
|
||||
|
||||
return `原图 ${originalText}`;
|
||||
|
||||
},
|
||||
|
||||
originalButtonLabel() {
|
||||
|
||||
if (!this.originalSize) return '使用原图';
|
||||
|
||||
return `使用原图 (${formatFileSize(this.originalSize)})`;
|
||||
|
||||
},
|
||||
|
||||
compressedButtonLabel() {
|
||||
|
||||
if (!this.compressedSize) return '使用压缩图';
|
||||
|
||||
return `使用压缩图 (${formatFileSize(this.compressedSize)})`;
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
formatFileSize,
|
||||
|
||||
previewImage(current) {
|
||||
|
||||
if (!this.originalPath) return;
|
||||
|
||||
const urls =
|
||||
|
||||
this.status === 'ready' && this.compressedPath
|
||||
|
||||
? [this.originalPath, this.compressedPath]
|
||||
|
||||
: [this.originalPath];
|
||||
|
||||
uni.previewImage({
|
||||
|
||||
urls,
|
||||
|
||||
current,
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
open({ path, size }) {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
this.resolver = resolve;
|
||||
|
||||
this.originalPath = path;
|
||||
|
||||
this.originalSize = size;
|
||||
|
||||
this.compressedPath = '';
|
||||
|
||||
this.compressedSize = 0;
|
||||
|
||||
this.skipped = false;
|
||||
|
||||
this.errorMessage = '';
|
||||
|
||||
this.progressText = '正在压缩,请稍候…';
|
||||
|
||||
this.visible = true;
|
||||
|
||||
this.startCompress(path, size);
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
async startCompress(path, size) {
|
||||
|
||||
if (isGifPath(path)) {
|
||||
|
||||
this.skipped = true;
|
||||
|
||||
this.status = 'gifSkipped';
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.status = 'compressing';
|
||||
|
||||
this.progressText = '正在压缩(质量 80%)…';
|
||||
|
||||
|
||||
|
||||
try {
|
||||
|
||||
const compressed = await tryCompressImage(path, [80, 60, 40], (quality) => {
|
||||
|
||||
this.progressText = `正在压缩(质量 ${quality}%)…`;
|
||||
|
||||
});
|
||||
|
||||
this.compressedPath = compressed.path;
|
||||
|
||||
this.compressedSize = compressed.size;
|
||||
|
||||
this.status = 'ready';
|
||||
|
||||
this.progressText = '';
|
||||
|
||||
} catch (error) {
|
||||
|
||||
this.status = 'error';
|
||||
|
||||
this.errorMessage = '图片压缩失败,请使用原图或取消';
|
||||
|
||||
this.progressText = '';
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
finish(choice) {
|
||||
|
||||
const result = { choice, path: null, size: 0, usedCompress: false };
|
||||
|
||||
|
||||
|
||||
if (choice === 'cancel') {
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.resolver && this.resolver(null);
|
||||
|
||||
this.resolver = null;
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (choice === 'compressed' && this.status === 'ready') {
|
||||
|
||||
result.path = this.compressedPath;
|
||||
|
||||
result.size = this.compressedSize;
|
||||
|
||||
result.usedCompress = true;
|
||||
|
||||
} else {
|
||||
|
||||
result.path = this.originalPath;
|
||||
|
||||
result.size = this.originalSize;
|
||||
|
||||
result.usedCompress = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.visible = false;
|
||||
|
||||
this.resolver && this.resolver(result);
|
||||
|
||||
this.resolver = null;
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.popup-wrap {
|
||||
|
||||
padding: 32rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.popup-title {
|
||||
|
||||
font-size: 32rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.size-text {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
color: #666;
|
||||
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-image {
|
||||
|
||||
width: 100%;
|
||||
|
||||
height: 280rpx;
|
||||
|
||||
background: #fafafa;
|
||||
|
||||
border-radius: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-image.single {
|
||||
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.compare-panel {
|
||||
|
||||
display: flex;
|
||||
|
||||
gap: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-column {
|
||||
|
||||
flex: 1;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-label {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
font-weight: 500;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-size {
|
||||
|
||||
margin-top: 8rpx;
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #999;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.preview-hint {
|
||||
|
||||
margin-top: 4rpx;
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: #bbb;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.compressing-panel,
|
||||
|
||||
.gif-panel,
|
||||
|
||||
.error-panel {
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.compressing-status {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.progress-text,
|
||||
|
||||
.hint-text {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
color: #666;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.error-text {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
color: #fa3534;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.footer-actions {
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: flex-end;
|
||||
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 16rpx;
|
||||
|
||||
margin-top: 32rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn {
|
||||
|
||||
min-width: 160rpx;
|
||||
|
||||
padding: 16rpx 24rpx;
|
||||
|
||||
border-radius: 8rpx;
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn-default {
|
||||
|
||||
background: #f5f5f5;
|
||||
|
||||
color: #333;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn-primary {
|
||||
|
||||
background: #2979ff;
|
||||
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
181
components/unpaid-register-drawer/UnpaidRegisterDrawer.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="popupVisible"
|
||||
mode="bottom"
|
||||
border-radius="24"
|
||||
:safe-area-inset-bottom="true"
|
||||
:closeable="true"
|
||||
@close="onPopupClose"
|
||||
>
|
||||
<view class="drawer-inner">
|
||||
<view class="drawer-header">
|
||||
<text class="drawer-title">选择待支付挂号</text>
|
||||
</view>
|
||||
<view v-if="!list.length" class="drawer-empty">暂无待支付挂号订单</view>
|
||||
<scroll-view v-else scroll-y class="drawer-scroll">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="drawer-item"
|
||||
@click="onSelect(item)"
|
||||
>
|
||||
<view class="item-main">
|
||||
<view class="item-row">
|
||||
<text class="item-label">就诊人</text>
|
||||
<text class="item-value">{{ displayText(item.patient) }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">医生</text>
|
||||
<text class="item-value">{{ displayText(item.doctor) }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">诊所</text>
|
||||
<text class="item-value">{{ displayText(item.store) }}</text>
|
||||
</view>
|
||||
<view class="item-row">
|
||||
<text class="item-label">挂号时间</text>
|
||||
<text class="item-value">{{ displayText(item.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#94A3B8" size="28"></u-icon>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 待支付挂号订单选择抽屉
|
||||
* 首页多条待付款挂号时,供用户选择具体订单前往支付
|
||||
*/
|
||||
export default {
|
||||
name: 'UnpaidRegisterDrawer',
|
||||
props: {
|
||||
// v-model 控制抽屉显示隐藏
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 待支付挂号列表,字段来自 fetchUnpaidRegisterListApi
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
popupVisible: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 字段为空时显示占位符
|
||||
* @param {string} val 展示值
|
||||
*/
|
||||
displayText(val) {
|
||||
const text = val != null ? String(val).trim() : '';
|
||||
return text || '--';
|
||||
},
|
||||
/**
|
||||
* 关闭抽屉
|
||||
*/
|
||||
onPopupClose() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
/**
|
||||
* 选中某条挂号订单,通知父组件跳转支付
|
||||
* @param {Object} item 挂号订单
|
||||
*/
|
||||
onSelect(item) {
|
||||
if (!item || item.id == null) return;
|
||||
this.$emit('select', item.id);
|
||||
this.$emit('input', false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 70vh;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.drawer-header {
|
||||
padding-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.drawer-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.drawer-empty {
|
||||
padding: 48rpx 0 64rpx;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.drawer-scroll {
|
||||
max-height: 60vh;
|
||||
min-height: 200rpx;
|
||||
}
|
||||
|
||||
.drawer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 20rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: linear-gradient(90deg, rgba(65, 117, 238, 0.06) 0%, rgba(65, 117, 238, 0.02) 100%);
|
||||
border: 1rpx solid rgba(65, 117, 238, 0.15);
|
||||
border-radius: 16rpx;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.item-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item-label {
|
||||
flex-shrink: 0;
|
||||
width: 140rpx;
|
||||
font-size: 26rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.item-value {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #0f172a;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
8
main.js
@@ -6,12 +6,20 @@ import Vue from 'vue'
|
||||
// main.js
|
||||
import uView from "uview-ui";
|
||||
import share from '@/common/share.js'
|
||||
import {setGuestMode} from "@/common/utils/auth";
|
||||
Vue.mixin(share)
|
||||
Vue.use(uView);
|
||||
|
||||
//防抖函数
|
||||
import common from './common/common.js'
|
||||
Vue.prototype.$noMultipleClicks = common.noMultipleClicks;
|
||||
|
||||
const token = uni.getStorageSync('token');
|
||||
const userInfo = uni.getStorageSync('userinfo');
|
||||
if (!token || !userInfo) {
|
||||
console.log('未登录')
|
||||
setGuestMode();
|
||||
}
|
||||
//时间戳的处理
|
||||
Vue.filter("formatDate", function(value) {
|
||||
var date = new Date(value * 1000); //时间戳为10位需*1000,时间戳为13位的话不需乘1000
|
||||
|
||||
80
pages.json
@@ -3,19 +3,30 @@
|
||||
// 下载安装方式
|
||||
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
// npm安装方式
|
||||
// "^u-upload$": "@/components/c-upload/c-upload.vue",
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
|
||||
// 消息通知组件
|
||||
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue"
|
||||
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue",
|
||||
"^FollowUpDrugDrawer$": "@/subPackages/doctor/components/FollowUpDrugDrawer.vue",
|
||||
// 列表骨架屏(分包组件,主包页需配合 componentPlaceholder)
|
||||
"^ListSkeleton$": "@/subPackages/common/components/ListSkeleton.vue",
|
||||
// 问诊助手壳(仅分包页使用,放分包以减小主包体积)
|
||||
"^AssistantConsultationShell$": "@/subPackages/common/components/AssistantConsultationShell.vue"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/home/home",
|
||||
"style": {
|
||||
// "navigationBarTitleText": "custom",
|
||||
"navigationStyle": "custom"
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true,
|
||||
// "navigationStyle": "default",
|
||||
// "navigationBarBackgroundColor": "#298DFF",
|
||||
// "navigationBarTextStyle": "white"
|
||||
// 主包异步引用分包 ListSkeleton
|
||||
"componentPlaceholder": {
|
||||
"list-skeleton": "view"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -51,7 +62,12 @@
|
||||
{
|
||||
"path": "pages/cate/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康资讯"
|
||||
"navigationBarTitleText": "健康资讯",
|
||||
"enablePullDownRefresh": true,
|
||||
// 主包异步引用分包 ListSkeleton
|
||||
"componentPlaceholder": {
|
||||
"list-skeleton": "view"
|
||||
}
|
||||
}
|
||||
// "needLogin": true
|
||||
},
|
||||
@@ -65,7 +81,12 @@
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息"
|
||||
"navigationBarTitleText": "消息",
|
||||
"enablePullDownRefresh": true,
|
||||
// 主包异步引用分包 ListSkeleton
|
||||
"componentPlaceholder": {
|
||||
"list-skeleton": "view"
|
||||
}
|
||||
}
|
||||
// "needLogin": true
|
||||
},
|
||||
@@ -185,6 +206,28 @@
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "follow-up/medication-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用药信息",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "special-prescription/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "特色药方",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "special-prescription/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "药方详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -312,6 +355,20 @@
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "my/my-drug/invoice-apply",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请开票",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "my/my-drug/invoice-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "开票详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -466,6 +523,21 @@
|
||||
"path": "chat/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "在线问诊",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "transfer/transfer-message",
|
||||
"style": {
|
||||
"navigationBarTitleText": "转诊消息",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "transfer/transfer-consultation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "转诊咨询",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
<!-- 选择坐诊医生 -->
|
||||
<view class="doctor_list">选择坐诊医生</view>
|
||||
<!-- 列表 -->
|
||||
<view class="list" v-for="(item,index) in doctorList" :key="item.id" @click="goLookDoctor(item.id)">
|
||||
<view class="list" v-for="(item,index) in doctorList" :key="item.id">
|
||||
<!-- 图片部分 -->
|
||||
<view class="list_info">
|
||||
<view class="list_left">
|
||||
<image :src="item.avatar || '/static/mine/avatar_1.png'" mode="医生头像"></image>
|
||||
<view class="list_left" @click="goLookDoctor(item.id)">
|
||||
<image :src="item.avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="医生头像"></image>
|
||||
<!-- 医生 -->
|
||||
<view class="list_title">
|
||||
<view class="titles_name">
|
||||
@@ -61,10 +61,10 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list_right">{{ typeMap[type] }}</view>
|
||||
<view class="list_right" @click.stop="goRegister(item)">{{ typeMap[type] }}</view>
|
||||
</view>
|
||||
<!-- 文字部分 -->
|
||||
<view class="card">
|
||||
<view class="card" @click="goLookDoctor(item.id)">
|
||||
<!-- 擅长 -->
|
||||
<view class="list_going">
|
||||
擅长:{{item.good_at}}
|
||||
@@ -129,7 +129,7 @@
|
||||
import {
|
||||
dDropdown
|
||||
} from '@/components/d-dropdown/d-dropdown'
|
||||
import { isGuestMode } from '@/common/utils/auth.js'
|
||||
import { isGuestMode, isLoggedIn, saveRedirectInfo, buildUrlWithParams } from '@/common/utils/auth.js'
|
||||
export default {
|
||||
components: {
|
||||
dDropdown
|
||||
@@ -172,11 +172,71 @@
|
||||
type: "",
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
async onLoad(e) {
|
||||
this.type = e.type
|
||||
// console.log(e, 'idd');
|
||||
|
||||
// 当 type === 2 (诊所在线问诊) 时,直接获取委托诊所和医生信息并跳转到挂号页面
|
||||
if (this.type === '2') {
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id') || '11001';
|
||||
const { getClinicOnlineConsultationInfoApi } = await import('@/request/api/product');
|
||||
const configRes = await getClinicOnlineConsultationInfoApi({ store_id: storeId });
|
||||
|
||||
if (configRes.data?.code === 0 || configRes.data?.code === '0') {
|
||||
const result = configRes.data?.result;
|
||||
|
||||
if (result?.can_use && result?.delegate_doctor_id) {
|
||||
// 直接跳转到就诊人选择页面,传递医生ID和挂号类型
|
||||
uni.redirectTo({
|
||||
url: `/subPackages/doctor/doctor-userinfo?id=${result.delegate_doctor_id}&r_type=2&delegate_store_id=${result.delegate_store_id || storeId}`
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result?.message || '该门店暂不支持在线问诊功能',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: configRes.data?.message || '获取在线问诊信息失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取在线问诊信息失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取在线问诊信息失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resolveRegisterType(pageType) {
|
||||
const t = String(pageType ?? '0');
|
||||
if (['0', '1', '2', '3'].includes(t)) return t;
|
||||
return '0';
|
||||
},
|
||||
closeDropdown() {
|
||||
this.$refs.uDropdown.close();
|
||||
this.getList()
|
||||
@@ -254,6 +314,60 @@
|
||||
})
|
||||
},
|
||||
|
||||
async goRegister(item) {
|
||||
if (!isLoggedIn()) {
|
||||
const storeId = uni.getStorageSync('store_id') || '11001';
|
||||
saveRedirectInfo('/subPackages/doctor/doctor-userinfo', {
|
||||
id: item.id,
|
||||
r_type: this.resolveRegisterType(this.type),
|
||||
store_id: storeId,
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: buildUrlWithParams('/pages/login/login', {
|
||||
redirect: 'doctor-userinfo',
|
||||
doctor_id: item.id,
|
||||
store_id: storeId,
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const rType = this.resolveRegisterType(this.type);
|
||||
if (rType === '2') {
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id') || '11001';
|
||||
const { getClinicOnlineConsultationInfoApi } = await import('@/request/api/product');
|
||||
const configRes = await getClinicOnlineConsultationInfoApi({ store_id: storeId });
|
||||
if (configRes.data?.code === 0 || configRes.data?.code === '0') {
|
||||
const result = configRes.data?.result;
|
||||
if (result?.can_use && result?.delegate_doctor_id) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/doctor/doctor-userinfo?id=${result.delegate_doctor_id}&r_type=2&delegate_store_id=${result.delegate_store_id || storeId}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showToast({
|
||||
title: result?.message || '该门店暂不支持在线问诊功能',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
uni.showToast({
|
||||
title: configRes.data?.message || '获取在线问诊信息失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取在线问诊信息失败:', error);
|
||||
uni.showToast({ title: '获取在线问诊信息失败', icon: 'none' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/doctor/doctor-userinfo?id=${item.id}&r_type=${rType}`,
|
||||
});
|
||||
},
|
||||
|
||||
goProduct() {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/product/product?id=' + id
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<view class="it_info">{{item.small_info}}</view>
|
||||
<view class="it_price">
|
||||
<text>{{item.drugstore_drug.price}}</text>
|
||||
<image src="/static/home/gwc.png" mode=""
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/gwc.png" mode=""
|
||||
@tap.stop="toCartImg(item.drugstore_drug.drug_id)"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<view class="search">
|
||||
<u-search placeholder="输入文章标题" bg-color="#F5f5f5" color="#DDDDDD" border-color="#F5f5f5"
|
||||
:show-action="false" maxlength="21" height="66" :clearabled="true" shape="round" v-model="keyword"
|
||||
@search="toSearch">
|
||||
@search="toSearch"
|
||||
@clear="clearSearch">
|
||||
</u-search>
|
||||
</view>
|
||||
|
||||
@@ -13,7 +14,9 @@
|
||||
<!-- //药品分类左 -->
|
||||
<view class="goods">
|
||||
<view class="goods_left" @touchmove.stop>
|
||||
<scroll-view scroll-y scroll-with-animation class="u-menu-scroll-view" :scroll-top="scrollTop"
|
||||
<!-- 首次加载分类骨架 -->
|
||||
<ListSkeleton v-if="categoryInitialLoading" type="category" :rows="8" />
|
||||
<scroll-view v-else scroll-y scroll-with-animation class="u-menu-scroll-view" :scroll-top="scrollTop"
|
||||
:scroll-into-view="itemId">
|
||||
<view v-for="(item,index) in categoryLists" :key="item.id" class="u-tab-item"
|
||||
:class="[num == item.id ? 'u-item-active' : '']" @tap.stop="changeStyle(item.id)"
|
||||
@@ -26,15 +29,18 @@
|
||||
<view class="goods_right">
|
||||
<!-- 药品 -->
|
||||
<view class="second">
|
||||
<!-- 商品列表 -->
|
||||
<view class="list" v-for="(item,index) in infoList" @click="toDetail(item.id)" :key="item.id">
|
||||
<view class="list_">
|
||||
<image :src="item.cover||'/static/xx/cp.png'" mode=""></image>
|
||||
<view class="list_title">
|
||||
{{item.title}}
|
||||
<!-- 首次或切分类且当前无文章时展示骨架,有旧文章则保留至新结果 -->
|
||||
<ListSkeleton v-if="showArticleSkeleton" type="article" :rows="4" />
|
||||
<template v-else>
|
||||
<view class="list" v-for="(item,index) in infoList" @click="toDetail(item.id)" :key="item.id">
|
||||
<view class="list_">
|
||||
<image :src="item.cover||'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png'" mode=""></image>
|
||||
<view class="list_title">
|
||||
{{item.title}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -65,16 +71,36 @@
|
||||
selectIndex: 0,
|
||||
arr: [], // 储存距离顶部高度的数组
|
||||
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
|
||||
show: false
|
||||
show: false,
|
||||
// 首次加载骨架
|
||||
categoryInitialLoading: true,
|
||||
articleInitialLoading: true,
|
||||
// 切分类且当前列表为空时拉文章期间展示骨架
|
||||
articleFetchingEmpty: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否展示右侧文章骨架:首次加载,或请求中且当前无文章可保留
|
||||
showArticleSkeleton() {
|
||||
return this.articleInitialLoading || this.articleFetchingEmpty
|
||||
}
|
||||
},
|
||||
onLoad(op) {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 搜索
|
||||
// 搜索:按当前分类 + 标题关键词拉文章(不再误调分类接口)
|
||||
toSearch() {
|
||||
this.getList()
|
||||
this.infoList = []
|
||||
this.articleFetchingEmpty = true
|
||||
this.getArticleList()
|
||||
},
|
||||
// 清空搜索词后重新拉当前分类文章
|
||||
clearSearch() {
|
||||
this.keyword = ''
|
||||
this.infoList = []
|
||||
this.articleFetchingEmpty = true
|
||||
this.getArticleList()
|
||||
},
|
||||
/*切换需要*/
|
||||
changeStyle(id) {
|
||||
@@ -86,48 +112,76 @@
|
||||
url: '/pages/cate/info-detail?id=' + e
|
||||
})
|
||||
},
|
||||
//点击药品一级分类e
|
||||
// 切分类:立即清空文章出骨架
|
||||
handleCategory1(e, index) {
|
||||
this.num = e
|
||||
this.cid = e
|
||||
this.infoList = []
|
||||
this.articleFetchingEmpty = true
|
||||
this.getArticleList()
|
||||
},
|
||||
|
||||
//获取药品分类
|
||||
// 获取分类:返回 Promise 供下拉刷新等待
|
||||
getList() {
|
||||
categoriesList({
|
||||
return categoriesList({
|
||||
method: "post",
|
||||
data: {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'list');
|
||||
if (res.data.errcode == 0) {
|
||||
// console.log(res, 'res');
|
||||
this.categoryLists = res.data.data.list
|
||||
this.cid = res.data.data.list.map(x => x.id)
|
||||
this.num = res.data.data.list[0].id //初始选中
|
||||
// this.current = res.data.data[0].subs[0].id //初始选中2
|
||||
if (res.data.code == 0) {
|
||||
const list = (res.data.result && res.data.result.list) || []
|
||||
this.categoryLists = list
|
||||
if (!list.length) {
|
||||
this.infoList = []
|
||||
this.articleInitialLoading = false
|
||||
this.articleFetchingEmpty = false
|
||||
return
|
||||
}
|
||||
const firstId = list[0].id
|
||||
this.num = firstId
|
||||
this.cid = firstId
|
||||
return this.getArticleList()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.categoryInitialLoading = false
|
||||
})
|
||||
|
||||
this.getArticleList()
|
||||
},
|
||||
// 文章列表
|
||||
// 文章列表:可选 keyword 标题搜索
|
||||
getArticleList() {
|
||||
articleList({
|
||||
if (!this.infoList || this.infoList.length === 0) {
|
||||
this.articleFetchingEmpty = true
|
||||
}
|
||||
const data = {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
cid: this.cid
|
||||
}
|
||||
const kw = (this.keyword || '').trim()
|
||||
if (kw) {
|
||||
data.keyword = kw
|
||||
}
|
||||
return articleList({
|
||||
method: "get",
|
||||
data: {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
cid: this.cid
|
||||
}
|
||||
data
|
||||
}).then((res) => {
|
||||
// console.log(res, 'list');
|
||||
if (res.data.errcode == 0) {
|
||||
this.infoList = res.data.data.list
|
||||
// console.log(res, 'list')
|
||||
if (res.data.code == 0) {
|
||||
this.infoList = (res.data.result && res.data.result.list) || []
|
||||
}
|
||||
|
||||
}).finally(() => {
|
||||
this.articleInitialLoading = false
|
||||
this.articleFetchingEmpty = false
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 下拉刷新:清空分类/文章并重走开骨架
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.categoryLists = []
|
||||
this.infoList = []
|
||||
this.categoryInitialLoading = true
|
||||
this.articleInitialLoading = true
|
||||
this.articleFetchingEmpty = false
|
||||
Promise.resolve(this.getList()).finally(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -135,6 +189,7 @@
|
||||
// this.getList();
|
||||
},
|
||||
onShow() {
|
||||
// 不清空 categoryLists / infoList,成功后再替换
|
||||
this.getList();
|
||||
}
|
||||
}
|
||||
@@ -248,4 +303,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'list');
|
||||
if (res.data.errcode == 0) {
|
||||
this.infoList = res.data.data
|
||||
this.content = res.data.data.content
|
||||
if (res.data.code == 0 && res.data.result) {
|
||||
this.infoList = res.data.result
|
||||
this.content = res.data.result.content
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -77,12 +77,12 @@
|
||||
<!-- 底部 -->
|
||||
<view class="footer">
|
||||
<view class="box">
|
||||
<image src="/static/shop/home.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/home.png" mode=""></image>
|
||||
<text>首页</text>
|
||||
</view>
|
||||
<view class="box" @click="toShop">
|
||||
<u-badge type="error" :count="gwcNum" size="mini" :offset="[0,16]"></u-badge>
|
||||
<image :src="gwcNum==0&&'/static/shop/gwc.png' || gwcNum>0&&'/static/shop/gwc_l.png'" mode=""></image>
|
||||
<image :src="gwcNum==0&&'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/gwc.png' || gwcNum>0&&'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/shop/gwc_l.png'" mode=""></image>
|
||||
<text :class="gwcNum>0?'active':''">购物车</text>
|
||||
</view>
|
||||
<view class="to_btns" @click="toAdd(drugDetailsList.drug_store_relation.drug_id)">
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
<!-- 图片展示 -->
|
||||
<view class="section" @click="gologin">
|
||||
<u-swiper :list="list" :img-mode="aspectFill" :height="300" :interval="4000"></u-swiper>
|
||||
<!-- <image class="banner" mode="aspectFill" src="/static/home/banner.png"></image> -->
|
||||
<!-- <image class="banner" mode="aspectFill" src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png"></image> -->
|
||||
</view>
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav" v-if="isShow_envVersion==false">
|
||||
<view class="nav_service" @click="gologin">
|
||||
<view class="nav_service_img">
|
||||
<image src="../../static/home/bg1.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg1.png" mode=""></image>
|
||||
</view>
|
||||
<view class="nav_service_title">
|
||||
到店挂号
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<view class="nav_service" @click="gologin">
|
||||
<view class="nav_service_img">
|
||||
<image src="../../static/home/bg2.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg2.png" mode=""></image>
|
||||
</view>
|
||||
<view class="nav_service_title">
|
||||
预约购药
|
||||
@@ -105,7 +105,7 @@
|
||||
<!-- 图片部分 -->
|
||||
<view class="list_infos">
|
||||
<view class="list_lefts">
|
||||
<image :src="item.avatar || '/static/mine/avatar_1.png'" mode="医生头像"></image>
|
||||
<image :src="item.avatar || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="医生头像"></image>
|
||||
<!-- 医生 -->
|
||||
<view class="list_title">
|
||||
<view class="titles_name">
|
||||
@@ -153,9 +153,9 @@
|
||||
show: true,
|
||||
doctorList: [],
|
||||
list: [
|
||||
// '/static/home/banner.png',
|
||||
// '/static/home/banner.png',
|
||||
// '/static/home/banner.png'
|
||||
// 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png',
|
||||
// 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png',
|
||||
// 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/banner.png'
|
||||
],
|
||||
info:[],
|
||||
isShow_envVersion: true
|
||||
@@ -467,7 +467,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
background: url('/static/home/bg01.png') no-repeat;
|
||||
background: url('https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg01.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.nav_service_img {
|
||||
@@ -490,7 +490,7 @@
|
||||
}
|
||||
|
||||
.nav_service:last-child {
|
||||
background: url('/static/home/bg02.png') no-repeat;
|
||||
background: url('https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/home/bg02.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="card" v-for="item in list" :key="item.id" @click="toInfo(item.id)">
|
||||
<image :src="item.content['avatar']||'/static/mine/avatar_1.png'" mode=""></image>
|
||||
<image :src="item.content['avatar']||'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode=""></image>
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">{{item.content['doctor']}}医生</text>
|
||||
|
||||
@@ -12,85 +12,118 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<view class="list" @click="goSystem">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="/static/xx/xt.png" mode=""></image>
|
||||
<!-- 首次加载:四条固定入口用骨架,避免 num 未定义时闪「暂无新消息」 -->
|
||||
<ListSkeleton v-if="noticeInitialLoading" type="message" :rows="4" />
|
||||
<template v-else>
|
||||
<!-- 消息列表 -->
|
||||
<view class="list" @click="goSystem">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/xt.png" mode=""></image>
|
||||
</view>
|
||||
<u-badge size="default" v-if="system.num>=0" :count="system.num" :is-dot="system.num==0?true:false"
|
||||
type="error" :offset="system.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<u-badge size="default" v-if="system.num>=0" :count="system.num" :is-dot="system.num==0?true:false"
|
||||
type="error" :offset="system.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">系统消息</text>
|
||||
<text class="time">{{ system.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="system.num >0">
|
||||
您有{{ system.num }}条未读信息
|
||||
</view>
|
||||
<view class="preview" v-if="system.num==undefined">
|
||||
暂无新消息
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">系统消息</text>
|
||||
<text class="time">{{ system.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="system.num >0">
|
||||
您有{{ system.num }}条未读信息
|
||||
</view>
|
||||
<view class="preview" v-if="system.num==undefined">
|
||||
暂无新消息
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list" @click="goOrder">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="/static/xx/cp.png" mode=""></image>
|
||||
<view class="list" @click="goOrder">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cp.png" mode=""></image>
|
||||
</view>
|
||||
<u-badge size="default" v-if="order.num>=0" :count="order.num" :is-dot="order.num==0?true:false"
|
||||
type="error" :offset="order.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<u-badge size="default" v-if="order.num>=0" :count="order.num" :is-dot="order.num==0?true:false"
|
||||
type="error" :offset="order.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">产品订单</text>
|
||||
<text class="time">{{ order.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="order.num>0">
|
||||
您有{{ order.num }}条未读信息
|
||||
</view>
|
||||
<view class="preview" v-if="order.num==undefined">
|
||||
暂无新消息
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">产品订单</text>
|
||||
<text class="time">{{ order.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="order.num>0">
|
||||
您有{{ order.num }}条未读信息
|
||||
</view>
|
||||
<view class="preview" v-if="order.num==undefined">
|
||||
暂无新消息
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list" @click="goDoctor">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="/static/xx/ysxx.png" mode=""></image>
|
||||
<view class="list" @click="goDoctor">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png" mode=""></image>
|
||||
</view>
|
||||
<u-badge size="default" v-if="doctor.num>=0" :count="doctor.num" :is-dot="doctor.num==0?true:false"
|
||||
type="error" :offset="doctor.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<u-badge size="default" v-if="doctor.num>=0" :count="doctor.num" :is-dot="doctor.num==0?true:false"
|
||||
type="error" :offset="doctor.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">医生消息</text>
|
||||
<text class="time">{{ doctor.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="doctor.num>0">
|
||||
您有{{ doctor.num }}条新的消息
|
||||
</view>
|
||||
<view class="preview" v-if="doctor.num==undefined">
|
||||
暂无新消息
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">医生消息</text>
|
||||
<text class="time">{{ doctor.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="doctor.num>0">
|
||||
您有{{ doctor.num }}条新的消息
|
||||
</view>
|
||||
<view class="preview" v-if="doctor.num==undefined">
|
||||
暂无新消息
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模拟<u-divider>在线问诊</u-divider>效果-->
|
||||
<!-- 转诊消息 -->
|
||||
<view class="list" @click="goTransfer">
|
||||
<!-- 头像 -->
|
||||
<view class="avator">
|
||||
<view class="img">
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png" mode=""></image>
|
||||
</view>
|
||||
<u-badge size="default" v-if="transfer.num>=0" :count="transfer.num" :is-dot="transfer.num==0?true:false"
|
||||
type="error" :offset="transfer.num>0?[-4,-8]:[0,0]"></u-badge>
|
||||
</view>
|
||||
<!-- 描述 -->
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">转诊消息</text>
|
||||
<text class="time">{{ transfer.time }}</text>
|
||||
</view>
|
||||
<view class="preview" v-if="transfer.num>0">
|
||||
您有{{ transfer.num }}条转诊消息待处理
|
||||
</view>
|
||||
<view class="preview" v-if="transfer.num==undefined || transfer.num==0">
|
||||
暂无转诊消息
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 标题 - 在线咨询 -->
|
||||
<view v-if="checkDev('open-im') && onlineConsultationList.length > 0" class="title">
|
||||
<text>在线咨询</text>
|
||||
</view>
|
||||
<!-- IM 会话首次骨架:有旧数据时直接展示旧列表直至替换 -->
|
||||
<ListSkeleton
|
||||
v-if="checkDev('open-im') && chatInitialLoading && onlineConsultationList.length === 0 && onlineReconsultationList.length === 0"
|
||||
type="message"
|
||||
:rows="3"
|
||||
/>
|
||||
<!-- 在线咨询 - 用户 (type=1) -->
|
||||
<view v-if="checkDev('open-im')" v-for="(item, index) in onlineConsultationList" :key="item.id" class="list" @click="goChat(item, index)">
|
||||
<!-- 头像 -->
|
||||
@@ -110,7 +143,6 @@
|
||||
</view>
|
||||
<view class="preview">
|
||||
<p>{{ item.last_message }}</p>
|
||||
<!-- <p v-if="item.un_read_count>0">您有{{item.un_read_count}}条新的消息</p>-->
|
||||
</view>
|
||||
<view class="preview" v-if="item.last_message===''">
|
||||
暂无新消息
|
||||
@@ -141,7 +173,6 @@
|
||||
</view>
|
||||
<view class="preview">
|
||||
<p>{{ item.last_message }}</p>
|
||||
<!-- <p v-if="item.un_read_count>0">您有{{item.un_read_count}}条新的消息</p>-->
|
||||
</view>
|
||||
<view class="preview" v-if="item.last_message===''">
|
||||
暂无新消息
|
||||
@@ -157,6 +188,7 @@ import {
|
||||
} from '../../request/api/api'
|
||||
import {getSystemNoticeIndexApi} from '../../request/api/systemNotice'
|
||||
import {getChatFriendsListApi} from "@/request/api/im";
|
||||
import {getTransferPrescriptionListApi} from "@/request/api/transferPrescription";
|
||||
import {checkDev} from "@/utils/utils";
|
||||
import MessageNotification from "@/components/MessageNotification/MessageNotification.vue";
|
||||
|
||||
@@ -169,10 +201,17 @@ export default {
|
||||
order: [],
|
||||
system: [],
|
||||
doctor: [],
|
||||
transfer: {
|
||||
num: 0,
|
||||
time: ''
|
||||
},
|
||||
imChat: [],
|
||||
onlineConsultationList: [], // 在线咨询列表 (type=1)
|
||||
onlineReconsultationList: [], // 在线复诊列表 (type=3)
|
||||
type: ""
|
||||
type: "",
|
||||
// 首次加载骨架:刷新时不再清空数据,故仅首次为 true
|
||||
noticeInitialLoading: true,
|
||||
chatInitialLoading: true
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
@@ -204,7 +243,6 @@ export default {
|
||||
type: "all"
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'res');
|
||||
uni.showToast({
|
||||
title: '清除成功',
|
||||
duration: 2000,
|
||||
@@ -226,7 +264,6 @@ export default {
|
||||
type: "system"
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'res');
|
||||
})
|
||||
},
|
||||
// 详情
|
||||
@@ -241,7 +278,6 @@ export default {
|
||||
type: "order"
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'res');
|
||||
})
|
||||
},
|
||||
goDoctor() {
|
||||
@@ -255,24 +291,20 @@ export default {
|
||||
type: "doctor_news"
|
||||
}
|
||||
}).then((res) => {
|
||||
// console.log(res, 'res');
|
||||
})
|
||||
},
|
||||
// 转诊消息
|
||||
goTransfer() {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/transfer/transfer-message'
|
||||
})
|
||||
},
|
||||
goChat(item, index) {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/chat/chat?room_id=${item.room_id}&nick_name=${item.nick_name}&user_id=${item.id}&avatar=${encodeURIComponent(item.avatar || '')}&room_status=${item.room_status || 0}`
|
||||
})
|
||||
// getseesionRead({
|
||||
// method: "post",
|
||||
// data: {
|
||||
// store_id: uni.getStorageSync('store_id') || 11001,
|
||||
// type: "doctor_news"
|
||||
// }
|
||||
// }).then((res) => {
|
||||
// // console.log(res, 'res');
|
||||
// })
|
||||
},
|
||||
//
|
||||
// 获取系统/订单/医生消息概览;成功才覆盖,失败保留旧数据
|
||||
async getInfo() {
|
||||
try {
|
||||
const res = await getSystemNoticeIndexApi({
|
||||
@@ -285,16 +317,73 @@ export default {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取消息概览失败:', error)
|
||||
} finally {
|
||||
this.noticeInitialLoading = false
|
||||
}
|
||||
},
|
||||
// 拉取 IM 好友会话列表,成功后整份替换,失败不写空
|
||||
getMyChatFriendsList() {
|
||||
getChatFriendsListApi().then((res) => {
|
||||
return getChatFriendsListApi().then((res) => {
|
||||
const allChatList = res.data.result || []
|
||||
// 根据type字段区分在线咨询和在线复诊
|
||||
this.onlineConsultationList = allChatList.filter(item => item.type === 1 || !item.type) // type=1或未设置type的为在线咨询
|
||||
this.onlineReconsultationList = allChatList.filter(item => item.type === 3) // type=3的为在线复诊
|
||||
this.onlineReconsultationList = allChatList.filter(item => (item.type === 3 || item.type === 2)) // type=3的为在线复诊
|
||||
// 保持imChat兼容性(包含所有)
|
||||
this.imChat = allChatList
|
||||
}).catch((error) => {
|
||||
console.error('获取聊天列表失败:', error)
|
||||
}).finally(() => {
|
||||
this.chatInitialLoading = false
|
||||
})
|
||||
},
|
||||
// 获取转诊消息列表;失败不重置 transfer,避免闪空
|
||||
getTransferList() {
|
||||
return getTransferPrescriptionListApi().then((res) => {
|
||||
if (res.data && res.data.code == 0) {
|
||||
const transferList = res.data.result || []
|
||||
// 统计待确认的转诊消息数量
|
||||
const pendingCount = transferList.filter(item => item.status === 0).length
|
||||
this.transfer.num = pendingCount
|
||||
|
||||
// 获取最新的转诊时间
|
||||
if (transferList.length > 0) {
|
||||
const latestTransfer = transferList[0]
|
||||
if (latestTransfer.transfer_time) {
|
||||
this.transfer.time = latestTransfer.transfer_time
|
||||
} else if (latestTransfer.created_at) {
|
||||
this.transfer.time = latestTransfer.created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('获取转诊消息列表失败:', error)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 下拉刷新:重置骨架并并行重拉消息概览 / IM / 转诊
|
||||
*/
|
||||
refreshMessagePage() {
|
||||
this.noticeInitialLoading = true
|
||||
if (checkDev('open-im')) {
|
||||
this.chatInitialLoading = true
|
||||
this.onlineConsultationList = []
|
||||
this.onlineReconsultationList = []
|
||||
this.imChat = []
|
||||
}
|
||||
const tasks = [
|
||||
this.getInfo(),
|
||||
this.getTransferList()
|
||||
]
|
||||
if (checkDev('open-im')) {
|
||||
tasks.push(this.getMyChatFriendsList())
|
||||
} else {
|
||||
this.chatInitialLoading = false
|
||||
}
|
||||
return Promise.all(tasks)
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.refreshMessagePage().finally(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -302,13 +391,10 @@ export default {
|
||||
this.getInfo()
|
||||
},
|
||||
onShow() {
|
||||
this.order = []
|
||||
this.system = []
|
||||
this.doctor = []
|
||||
this.onlineConsultationList = []
|
||||
this.onlineReconsultationList = []
|
||||
// 不再清空列表:保留旧 UI,接口成功后再覆盖
|
||||
this.getInfo()
|
||||
this.getMyChatFriendsList()
|
||||
this.getTransferList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -428,4 +514,4 @@ export default {
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<MessageNotification />
|
||||
<!-- 描述 -->
|
||||
<view class="card" v-for="item in list" :key="item.id">
|
||||
<image src="/static/xx/cpdd.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/cpdd.png" mode=""></image>
|
||||
<view class="info">
|
||||
<view class="infos_title">
|
||||
<text class="infos">订单</text>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<view class="qualification-item" v-for="(item, index) in qualifications" :key="index" @click="previewImage(index)">
|
||||
<view class="qualification-content">
|
||||
<text class="qualification-name">{{ item.name }}</text>
|
||||
<image :src="item.image || '/static/mine/avatar_1.png'" mode="aspectFit" class="qualification-img"></image>
|
||||
<image :src="item.image || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="aspectFit" class="qualification-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<view class="title">
|
||||
欢迎登录
|
||||
</view>
|
||||
<image src="@/static/empty/login.png" mode="aspectFit"></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/empty/login.png" mode="aspectFit"></image>
|
||||
|
||||
<!-- 修改点1:移除 disabled 属性和 bg 样式判断,使其始终可点击且显示激活颜色 -->
|
||||
<button class="btn" @click="handleLoginClick">一键登录</button>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<view class="info">
|
||||
<view class="pwd">
|
||||
<d-input :maxlength="11" height="96" v-model="form['mobile']"
|
||||
:prefixIcon="require('../../static/choose/act.png')" borderRadius="96rpx" prefixIconSize="40rpx"
|
||||
prefixIcon="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/choose/act.png" borderRadius="96rpx" prefixIconSize="40rpx"
|
||||
placeholder="请输入手机号" @blur="check" @input="is_d=false" :placeholder-style="{fontSize:'32rpx'}"
|
||||
:custom-style="{fontSize:'32rpx',paddingLeft:'60rpx'}" />
|
||||
</view>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- 头部 -->
|
||||
<view class="head">
|
||||
<view class="head_img">
|
||||
<image :src="infoList.user.avatarurl || '/static/mine/mr_tx.png' " mode=""></image>
|
||||
<image :src="infoList.user.avatarurl || 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png' " mode=""></image>
|
||||
</view>
|
||||
<view class="head_info">
|
||||
<view class="head_name">
|
||||
@@ -31,25 +31,25 @@
|
||||
</view>
|
||||
<view class="info">
|
||||
<view class="item" @click="toAll(1)" style="position: relative;">
|
||||
<image src="/static/mine/dfk.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dfk.png" mode=""></image>
|
||||
<text>待付款</text>
|
||||
<u-badge type="error" :count="infoList.ProductOrder.unpay" :offset="[-2,20]"></u-badge>
|
||||
</view>
|
||||
|
||||
<view class="item" @click="toAll(2)" style="position: relative;">
|
||||
<image src="/static/mine/dfh.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dfh.png" mode=""></image>
|
||||
<text>待发货</text>
|
||||
<u-badge type="error" :count="infoList.ProductOrder.wait_send" :offset="[-2,20]"></u-badge>
|
||||
</view>
|
||||
|
||||
<view class="item" @click="toAll(3)" style="position: relative;">
|
||||
<image src="/static/mine/dsh.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/dsh.png" mode=""></image>
|
||||
<text>待收货</text>
|
||||
<u-badge type="error" :count="infoList.ProductOrder.wait_accept" :offset="[-2,20]"></u-badge>
|
||||
</view>
|
||||
|
||||
<view class="item" @click="toAll(4)">
|
||||
<image src="/static/mine/tk.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/tk.png" mode=""></image>
|
||||
<text>已完成</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -64,15 +64,15 @@
|
||||
<!-- 列表 -->
|
||||
<view class="list">
|
||||
<!-- <navigator url="../../subPackages/my/myorder" class="infos">
|
||||
<image src="@/static/mine/01.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/01.png" mode=""></image>
|
||||
<text>问诊记录</text>
|
||||
</navigator> -->
|
||||
<navigator url="../../subPackages/my/myrecord" class="infos">
|
||||
<image src="@/static/mine/02.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/02.png" mode=""></image>
|
||||
<text>处方记录</text>
|
||||
</navigator>
|
||||
<navigator url="../../subPackages/my/myappiont" class="infos">
|
||||
<image src="@/static/mine/03.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/03.png" mode=""></image>
|
||||
<text>挂号记录</text>
|
||||
</navigator>
|
||||
</view>
|
||||
@@ -87,15 +87,15 @@
|
||||
<!-- 列表 -->
|
||||
<view class="list">
|
||||
<navigator class="infos" url="../../subPackages/my/myinfo" v-if="isShow_envVersion==false">
|
||||
<image src="@/static/mine/jzr.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/jzr.png" mode=""></image>
|
||||
<text>就诊人管理</text>
|
||||
</navigator>
|
||||
<navigator url="../../subPackages/setup/area-list" class="infos">
|
||||
<image src="@/static/mine/gy.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/gy.png" mode=""></image>
|
||||
<text>收货地址</text>
|
||||
</navigator>
|
||||
<navigator url="../../subPackages/setup/customer-service" class="infos">
|
||||
<image src="@/static/mine/kfzx.png" mode=""></image>
|
||||
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/kfzx.png" mode=""></image>
|
||||
<text>客服中心</text>
|
||||
</navigator>
|
||||
</view>
|
||||
@@ -106,8 +106,8 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
getuserInfo
|
||||
} from '../../request/api/api'
|
||||
getUserInfoApi
|
||||
} from '../../request/api/user'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -137,15 +137,12 @@
|
||||
|
||||
// 用户信息
|
||||
getInfo() {
|
||||
getuserInfo({
|
||||
method: "post",
|
||||
data: {
|
||||
store_id: uni.getStorageSync('store_id') || 11001
|
||||
}
|
||||
getUserInfoApi({
|
||||
store_id: uni.getStorageSync('store_id') || 11001
|
||||
}).then((res) => {
|
||||
// console.log(res, 'ingo');
|
||||
if (res.data.errcode == 0) {
|
||||
this.infoList = res.data.data
|
||||
if (res.data.code == 0) {
|
||||
this.infoList = res.data.result
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import http from './request'
|
||||
import { get } from './http'
|
||||
|
||||
/** 将 xk-api 的 code/result 格式转为旧版 errcode/data,兼容现有页面 */
|
||||
function normalizeXkApiResponse(res) {
|
||||
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
|
||||
res.data.errcode = 0;
|
||||
res.data.data = res.data.result;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 登录 授权 其他手机号授权
|
||||
export async function getsLogin(params) {
|
||||
@@ -33,20 +43,20 @@ export async function getAgreem(params) {
|
||||
|
||||
// 首页 门店 门店切换
|
||||
export async function storeChange(params) {
|
||||
let data = await http('/oldApi/v1/store/store-change', params)
|
||||
return data
|
||||
let data = await http('/xkApi/store/store-change', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
// 首页 门店 用户所有门店
|
||||
export async function storeList(params) {
|
||||
let data = await http('/oldApi/v1/store/list', params)
|
||||
return data
|
||||
let data = await http('/xkApi/store/list', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
// 首页 门店 门店信息
|
||||
export async function storeInfo(params) {
|
||||
let data = await http('/oldApi/v1/store/info', params)
|
||||
return data
|
||||
let data = await http('/xkApi/store/info', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
// 医生列表
|
||||
@@ -106,7 +116,19 @@ export async function userInfo(params) {
|
||||
|
||||
// 就诊人保存
|
||||
export async function userAdd(params) {
|
||||
let data = await http('/oldApi/v1/patient/save', params)
|
||||
let data = await http('/xkApi/patient/save', params)
|
||||
return data
|
||||
}
|
||||
|
||||
// 就诊人监护人信息(编辑回显)
|
||||
export async function userGuardianInfo(params) {
|
||||
const query = params.data || params
|
||||
return await get('/patient/guardian-info', query, 3)
|
||||
}
|
||||
|
||||
// 删除就诊人
|
||||
export async function userPatientDel(params) {
|
||||
let data = await http('/xkApi/patient/delete', params)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -317,9 +339,9 @@ export async function getUseWayPay(params) {
|
||||
return data
|
||||
}
|
||||
|
||||
// 更新配送方式 http://xiaokang.com/member/v1/product-order/update-delivery
|
||||
// 更新配送方式(Laravel:单品包邮重算)
|
||||
export async function getDelivery(params) {
|
||||
let data = await http('/oldApi/v1/product-order/update-delivery', params)
|
||||
let data = await http('/xkApi/product-order/update-delivery', params)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -329,28 +351,51 @@ export async function getUpdateUseWay(params) {
|
||||
return data
|
||||
}
|
||||
|
||||
//资讯列表 /v1/info/article-list
|
||||
//资讯列表(Laravel guest-info,列表不含正文)
|
||||
export async function articleList(params) {
|
||||
let data = await http('/oldApi/v1/info/article-list', params)
|
||||
let data = await http('/xkApi/guest/guest-info/article-list', params)
|
||||
return data
|
||||
}
|
||||
|
||||
//分类列表 /v1/info/categories
|
||||
//分类列表
|
||||
export async function categoriesList(params) {
|
||||
let data = await http('/oldApi/v1/info/categories', params)
|
||||
let data = await http('/xkApi/guest/guest-info/categories', params)
|
||||
return data
|
||||
}
|
||||
|
||||
//资讯详情 /v1/info/article-info
|
||||
//资讯详情(含正文)
|
||||
export async function articleInfo(params) {
|
||||
let data = await http('/oldApi/v1/info/article-info', params)
|
||||
let data = await http('/xkApi/guest/guest-info/article-info', params)
|
||||
return data
|
||||
}
|
||||
|
||||
// v1/product-order/list
|
||||
export async function getOrdlist(params) {
|
||||
let data = await http('/oldApi/v1/product-order/list', params)
|
||||
return data
|
||||
let data = await http('/xkApi/product-order/list', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待支付商品订单列表(首页提醒用)
|
||||
*/
|
||||
export async function fetchUnpaidProductOrderListApi(params = {}) {
|
||||
const storeId = params.store_id || uni.getStorageSync('store_id') || '11001';
|
||||
const res = await getOrdlist({
|
||||
method: 'post',
|
||||
data: {
|
||||
store_id: storeId,
|
||||
type: 'unpaid',
|
||||
page: 1,
|
||||
...params,
|
||||
},
|
||||
});
|
||||
if (res.data?.errcode != 0) {
|
||||
return [];
|
||||
}
|
||||
const list = res.data?.data?.list || [];
|
||||
return list.filter(
|
||||
(item) => item.status === 0 && item.is_pay === 0 && item.cancel_status === 0
|
||||
);
|
||||
}
|
||||
|
||||
// v1/product-order/list
|
||||
@@ -420,6 +465,7 @@ export async function PayList(params) {
|
||||
|
||||
export async function getPayConfig(params) {
|
||||
let data = await http('/oldApi/v1/product-order/pay', params)
|
||||
// let data = await http('/newApi/mobile/product-order/pay', params)
|
||||
return data
|
||||
}
|
||||
// getOrdlist 获取订单列表 https://zjxk.app.ctkj88.com/member/order/lists
|
||||
@@ -469,18 +515,17 @@ export async function postRefundBased(params) {
|
||||
// return data
|
||||
|
||||
// }
|
||||
/** 物流详情 → xk-api mobile(含多包裹 packages) */
|
||||
export async function getLogistics(params) {
|
||||
let data = await http('/oldApi/v1/product-order/express', params)
|
||||
return data
|
||||
|
||||
let data = await http('/xkApi/product-order/express', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
|
||||
//确认收货 https://zjxk.app.ctkj88.com/member/order/confirm
|
||||
// https://zjxk.app.ctkj88.com/member/order/confirm
|
||||
// 确认收货(签收)→ xk-api product-order/receive(改状态 + 解冻供应商/仓余额)
|
||||
export async function confirmAccept(params) {
|
||||
let data = await http('/newApi/order/confirm', params)
|
||||
return data
|
||||
let data = await http('/xkApi/product-order/receive', params)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
//清空购物车
|
||||
export async function clearAll(params) {
|
||||
@@ -495,7 +540,8 @@ export async function getVersion(params) {
|
||||
|
||||
// 获取系统配置信息
|
||||
export async function getSystemConfig(params) {
|
||||
let data = await http('/xkApi/platform/config', params)
|
||||
// let data = await http('/xkApi/platform/config', params)
|
||||
let data = await http('/xkApi/platform/config', params, 3)
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
33
request/api/invoice.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { get, post } from './http'
|
||||
|
||||
/**
|
||||
* 将 xk-api 的 code/result 规范为 errcode/data,便于页面统一判断
|
||||
*/
|
||||
function normalizeXkApiResponse(res) {
|
||||
if (res && res.data && (res.data.code === 0 || res.data.code === '0')) {
|
||||
res.data.errcode = 0
|
||||
res.data.data = res.data.result
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请开票
|
||||
* @param {object} params order_id, title_type, title_name, tax_no, receive_type, email
|
||||
*/
|
||||
export async function applyInvoiceApi(params) {
|
||||
const data = await post('/invoice/apply', params, 3)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 开票详情
|
||||
* @param {object} params id 或 order_id
|
||||
*/
|
||||
export async function getInvoiceDetailApi(params) {
|
||||
const data = await get('/invoice/detail', params, 3)
|
||||
return normalizeXkApiResponse(data)
|
||||
}
|
||||
|
||||
/** 与 config/xk.php invoice_notice 保持一致的订阅消息模板 ID */
|
||||
export const INVOICE_NOTICE_TMPL_ID = '2FuQhhO74mABSqGx2cF7vRR2wGekrIMM9kZMvVI6h-Q'
|
||||
@@ -1,6 +1,20 @@
|
||||
import {get, post, uploadFile} from './http'
|
||||
|
||||
const prefix = '/order';
|
||||
|
||||
/**
|
||||
* 在线复诊:创建就诊信息(挂号前)
|
||||
*/
|
||||
export async function createRegisterInfoApi(params) {
|
||||
return await post(`${prefix}/create-register-info`, params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线复诊:可选西药列表(双端上架)
|
||||
*/
|
||||
export async function getWesternDrugsForFollowUpApi(params) {
|
||||
return await get(`${prefix}/western-drugs-for-follow-up`, params, 3)
|
||||
}
|
||||
/**
|
||||
* 获取扫码诊所的商品详情
|
||||
* @param params
|
||||
@@ -26,6 +40,11 @@ export async function saveRegisterInfoApi(params) {
|
||||
return await post(`${prefix}/save-register-info`, params, 3)
|
||||
}
|
||||
|
||||
/** 诊所在线复诊:确认是否曾用过所选西药 */
|
||||
export async function confirmFollowUpDrugUseApi(params) {
|
||||
return await post(`${prefix}/confirm-follow-up-drug-use`, params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单的处方状态
|
||||
* @param params
|
||||
|
||||
10
request/api/prescription.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {post} from './http'
|
||||
|
||||
/**
|
||||
* 获取处方记录列表
|
||||
* @param params { up_id: number, store_id: number, page: number }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getPrescriptionListApi(params) {
|
||||
return await post('/prescription/list', params, 3)
|
||||
}
|
||||
@@ -51,11 +51,30 @@ export async function updateIntroductionImagesApi(params) {
|
||||
|
||||
/**
|
||||
* 检查药店在线复诊配置(委托诊所和医生)
|
||||
* @param params { store_id: number }
|
||||
* @param params { store_id: number, guest?: number } - guest: 0=普通接口, 1=Guest接口
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function checkOnlineConsultationConfigApi(params) {
|
||||
return await get('/store/check-online-consultation-config', params, 3)
|
||||
const { guest = 0, ...restParams } = params;
|
||||
// guest=1 时调用 Guest 接口,guest=0 时调用普通接口
|
||||
const url = guest === 1
|
||||
? '/guest/guest-platform/check-online-consultation-config'
|
||||
: '/store/check-online-consultation-config';
|
||||
return await get(url, restParams, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取诊所在线问诊信息(委托诊所和委托医生)
|
||||
* @param params { store_id: number, guest?: number } - guest: 0=普通接口, 1=Guest接口
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getClinicOnlineConsultationInfoApi(params) {
|
||||
const { guest = 0, ...restParams } = params;
|
||||
// guest=1 时调用 Guest 接口,guest=0 时调用普通接口
|
||||
const url = guest === 1
|
||||
? '/guest/guest-platform/get-clinic-online-consultation-info'
|
||||
: '/store/get-clinic-online-consultation-info';
|
||||
return await get(url, restParams, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
29
request/api/register.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import {post} from './http'
|
||||
|
||||
/**
|
||||
* 获取挂号记录列表
|
||||
* @param params { patient_id: number, store_id: number, page: number }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getRegisterListApi(params) {
|
||||
return await post('/register/list', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待支付挂号列表(首页提醒用)
|
||||
* @param params
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
export async function fetchUnpaidRegisterListApi(params = {}) {
|
||||
const storeId = params.store_id || uni.getStorageSync('store_id') || '11001';
|
||||
const res = await post('/register/list?pageSize=100', {
|
||||
store_id: storeId,
|
||||
page: 1,
|
||||
...params,
|
||||
}, 3);
|
||||
if (res.data?.code !== 0 && res.data?.code !== '0') {
|
||||
return [];
|
||||
}
|
||||
const list = res.data?.result?.list || [];
|
||||
return list.filter((item) => item.status === 0 && item.is_cancel !== 1);
|
||||
}
|
||||
@@ -14,12 +14,66 @@ const arr = [
|
||||
'password'
|
||||
]
|
||||
|
||||
// 敏感数据
|
||||
// 敏感数据:响应解密后保留明文原字段,脱敏值写入 {field}_tm
|
||||
const sensitiveData = [
|
||||
'id_card',
|
||||
'idcard'
|
||||
'idcard',
|
||||
'guardian_id_card'
|
||||
]
|
||||
|
||||
// 仅展示脱敏、编辑需明文的字段
|
||||
const displayMaskFields = [
|
||||
'mobile',
|
||||
'express_mobile',
|
||||
'patient_mobile',
|
||||
]
|
||||
|
||||
/**
|
||||
* 脱敏展示值(写入 field_tm)
|
||||
*/
|
||||
function maskFieldValue(key, plainText) {
|
||||
if (plainText == null || plainText === '') {
|
||||
return plainText
|
||||
}
|
||||
const str = String(plainText)
|
||||
switch (key) {
|
||||
case 'express_name':
|
||||
case 'express_region':
|
||||
case 'accept_name':
|
||||
case 'patient':
|
||||
return str.slice(0, 1).padEnd(str.length, '*')
|
||||
case 'mobile':
|
||||
case 'express_mobile':
|
||||
case 'patient_mobile':
|
||||
case 'guardian_mobile':
|
||||
if (str.length <= 7) {
|
||||
return str
|
||||
}
|
||||
return str.slice(0, 3).padEnd(str.length - 4, '*') + str.slice(-4)
|
||||
case 'id_card':
|
||||
case 'idcard':
|
||||
case 'guardian_id_card':
|
||||
if (str.length <= 10) {
|
||||
return str
|
||||
}
|
||||
return str.slice(0, 6).padEnd(str.length - 4, '*') + str.slice(-4)
|
||||
default:
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/** 只读展示:优先 field_tm */
|
||||
export function displayTm(obj, field) {
|
||||
if (obj == null) {
|
||||
return ''
|
||||
}
|
||||
const tm = obj[field + '_tm']
|
||||
if (tm != null && tm !== '') {
|
||||
return tm
|
||||
}
|
||||
return obj[field] ?? ''
|
||||
}
|
||||
|
||||
const isDev = checkDev('dev');
|
||||
|
||||
// 环境URL配置
|
||||
@@ -29,14 +83,14 @@ const ENV_URLS = {
|
||||
main: "https://app.xiaokang88.com/member",
|
||||
xkApi: "https://api.xiaokang88.com/api/mobile",
|
||||
shop: "https://shop.xiaokang88.com/member",
|
||||
im: "https://api.ws.g.xiaokang88.com/api"
|
||||
im: "https://api.ws.g.xiaokang88.com/api",
|
||||
},
|
||||
// 测试环境
|
||||
test: {
|
||||
main: "https://test.xk.app.nailaoyun.cn/member",
|
||||
xkApi: "https://test.xk.api.nailaoyun.cn/api/mobile",
|
||||
shop: "https://test.xk.shop.nailaoyun.cn/member",
|
||||
im: "https://api.ws.g.nailaoyun.cn/api"
|
||||
main: "https://xk.test.saas.api-yii.nailaoyun.cn/member",
|
||||
xkApi: "https://xk.test.saas.api.nailaoyun.cn/api/mobile",
|
||||
shop: "https://test.xk.shop.nailaoyun.cn/member1",
|
||||
im: "https://xk.ws.nailaoyun.cn/api"
|
||||
},
|
||||
// 本地开发环境
|
||||
local: {
|
||||
@@ -54,7 +108,7 @@ function getEnvUrls() {
|
||||
return ENV_URLS.local;
|
||||
}
|
||||
// 检查是否开启了开发者测试模式
|
||||
const devMode = uni.getStorageSync('dev_mode');
|
||||
const devMode = checkDev('test');
|
||||
return devMode ? ENV_URLS.test : ENV_URLS.prod;
|
||||
}
|
||||
|
||||
@@ -87,6 +141,15 @@ function request(url, params, method = 0) {
|
||||
} else if (url.indexOf('/xkApi/platform/qualifications') !== -1) {
|
||||
url = url.replace('/xkApi/platform/qualifications', '/guest/guest-platform/qualifications');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/xkApi/store/info') !== -1) {
|
||||
url = url.replace('/xkApi/store/info', '/guest/guest-home/store-info');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/xkApi/store/list') !== -1) {
|
||||
url = url.replace('/xkApi/store/list', '/guest/guest-home/store-list');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/xkApi/special-prescription/') !== -1) {
|
||||
url = url.replace('/xkApi/special-prescription/', '/guest/guest-special-prescription/');
|
||||
baseURL = envUrls.xkApi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,12 +175,24 @@ function request(url, params, method = 0) {
|
||||
} else if (url.indexOf('/oldApi/v1/store/detail') !== -1) {
|
||||
url = url.replace('/oldApi/v1/store/detail', '/guest/guest-home/store-detail');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/store/list') !== -1) {
|
||||
url = url.replace('/oldApi/v1/store/list', '/guest/guest-home/store-list');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/register/doc-register-info') !== -1) {
|
||||
url = url.replace('/oldApi/v1/register/doc-register-info', '/guest/guest-doctor/doc-register-info');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/doctor/office-list') !== -1) {
|
||||
url = url.replace('/oldApi/v1/doctor/office-list', '/guest/guest-doctor/office-list');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/info/categories') !== -1) {
|
||||
url = url.replace('/oldApi/v1/info/categories', '/guest/guest-info/categories');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/info/article-list') !== -1) {
|
||||
url = url.replace('/oldApi/v1/info/article-list', '/guest/guest-info/article-list');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/oldApi/v1/info/article-info') !== -1) {
|
||||
url = url.replace('/oldApi/v1/info/article-info', '/guest/guest-info/article-info');
|
||||
baseURL = envUrls.xkApi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +207,12 @@ function request(url, params, method = 0) {
|
||||
} else if (url.indexOf('/v1/register/doc-register-info') !== -1 || url.indexOf('/member/v1/register/doc-register-info') !== -1) {
|
||||
url = url.replace(/\/member\/v1\/register\/doc-register-info|\/v1\/register\/doc-register-info/, '/guest/guest-doctor/doc-register-info');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/v1/store/info') !== -1 || url.indexOf('/member/v1/store/info') !== -1) {
|
||||
url = url.replace(/\/member\/v1\/store\/info|\/v1\/store\/info/, '/guest/guest-home/store-info');
|
||||
baseURL = envUrls.xkApi;
|
||||
} else if (url.indexOf('/v1/store/list') !== -1 || url.indexOf('/member/v1/store/list') !== -1) {
|
||||
url = url.replace(/\/member\/v1\/store\/list|\/v1\/store\/list/, '/guest/guest-home/store-list');
|
||||
baseURL = envUrls.xkApi;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +224,9 @@ function request(url, params, method = 0) {
|
||||
}
|
||||
if (url.split('/').indexOf('xkApi') !== -1) {
|
||||
baseURL = envUrls.xkApi;
|
||||
// 如果是guest模式且是xkApi请求,添加/guest/前缀
|
||||
if (isGuestRequest) {
|
||||
// 如果是guest模式且是xkApi请求,添加/guest/前缀(但避免重复添加)
|
||||
// 转诊相关接口需要登录,不转换为guest路由
|
||||
if (isGuestRequest && url.indexOf('/guest/') === -1 && url.indexOf('/transfer-prescription') === -1) {
|
||||
url = url.replace('/xkApi', '/guest/xkApi');
|
||||
}
|
||||
}
|
||||
@@ -155,10 +237,29 @@ function request(url, params, method = 0) {
|
||||
|
||||
// 加密
|
||||
if (params != null) {
|
||||
if (!params.data) {
|
||||
params.data = {};
|
||||
}
|
||||
params.data = getRes(params.data, false);
|
||||
// 如果是guest模式,强制设置store_id为11001
|
||||
const isProductApi = url.indexOf('/product/') !== -1;
|
||||
if (isProductApi && !params.data.store_id) {
|
||||
const localStoreId = uni.getStorageSync('store_id');
|
||||
params.data.store_id = localStoreId || getGuestStoreId();
|
||||
}
|
||||
// 如果是guest模式,设置store_id(优先使用用户传入或本地存储的值)
|
||||
if (isGuestRequest && params.data) {
|
||||
params.data.store_id = getGuestStoreId();
|
||||
// 优先使用用户传入的 store_id
|
||||
if (!params.data.store_id) {
|
||||
// 如果用户没传,尝试从本地存储获取
|
||||
const localStoreId = uni.getStorageSync('store_id');
|
||||
if (localStoreId) {
|
||||
params.data.store_id = localStoreId;
|
||||
} else {
|
||||
// 如果本地存储也没有,使用默认值(保持向后兼容)
|
||||
params.data.store_id = getGuestStoreId();
|
||||
}
|
||||
}
|
||||
// 如果用户已经传了 store_id,就使用用户传入的值,不做任何修改
|
||||
}
|
||||
}
|
||||
let num = 0;
|
||||
@@ -224,56 +325,39 @@ function request(url, params, method = 0) {
|
||||
|
||||
function getRes(obj, isDecode = true) {
|
||||
try {
|
||||
if (obj == null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (key.endsWith('_tm')) {
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(obj[key])) {
|
||||
obj[key] = getRes(obj[key])
|
||||
} else if (typeof obj[key] === 'object') {
|
||||
obj[key] = getRes(obj[key])
|
||||
} else {
|
||||
// 判断obj[key]是否在arr中
|
||||
if (arr.indexOf(key) !== -1) {
|
||||
let aseFile = ''
|
||||
if (isDecode === true) {
|
||||
aseFile = customBase64Decode(obj[key])
|
||||
} else {
|
||||
aseFile = customBase64Encode(obj[key])
|
||||
obj[key] = getRes(obj[key], isDecode)
|
||||
} else if (obj[key] !== null && typeof obj[key] === 'object') {
|
||||
obj[key] = getRes(obj[key], isDecode)
|
||||
} else if (arr.indexOf(key) !== -1) {
|
||||
let plainValue = ''
|
||||
if (isDecode === true) {
|
||||
plainValue = customBase64Decode(obj[key])
|
||||
} else {
|
||||
plainValue = customBase64Encode(obj[key])
|
||||
}
|
||||
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
||||
typeof plainValue === 'string' ? plainValue : ''
|
||||
)
|
||||
if (isGarbled) {
|
||||
plainValue = obj[key]
|
||||
}
|
||||
if (isDecode === true) {
|
||||
const needTm = sensitiveData.indexOf(key) !== -1
|
||||
|| displayMaskFields.indexOf(key) !== -1
|
||||
obj[key] = plainValue
|
||||
if (needTm) {
|
||||
obj[key + '_tm'] = maskFieldValue(key, plainValue)
|
||||
}
|
||||
const isGarbled = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]|[\u{E000}-\u{F8FF}]/u.test(
|
||||
typeof aseFile === 'string' ? aseFile : ''
|
||||
)
|
||||
if (isGarbled) {
|
||||
aseFile = obj[key]
|
||||
}
|
||||
// 为了修改,暂时不脱敏
|
||||
if (isDecode === true) {
|
||||
|
||||
if (sensitiveData.indexOf(key) !== -1) {
|
||||
// 数据脱敏,自动匹配姓名、手机号、身份证
|
||||
switch (key) {
|
||||
case 'express_name':
|
||||
case 'express_region':
|
||||
case 'accept_name':
|
||||
case 'patient':
|
||||
// 保留前两个字符,其余用星号代替
|
||||
aseFile = aseFile.slice(0, 1).padEnd(aseFile.length, '*');
|
||||
break;
|
||||
case 'mobile':
|
||||
case 'express_mobile':
|
||||
// 保留前三位和后四位,中间用星号代替
|
||||
aseFile = aseFile.slice(0, 3).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
|
||||
break;
|
||||
case 'id_card':
|
||||
case 'idcard':
|
||||
// 保留前六位和后四位,中间用星号代替
|
||||
aseFile = aseFile.slice(0, 6).padEnd(aseFile.length - 4, '*') + aseFile.slice(-4);
|
||||
break;
|
||||
default:
|
||||
// 默认情况下,全部用星号代替
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
obj[key] = aseFile
|
||||
} else {
|
||||
obj[key] = plainValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
request/api/specialPrescription.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { get, post } from './http'
|
||||
|
||||
export const SPECIAL_PRESCRIPTION_STORAGE_KEY = 'special_prescription_register_context'
|
||||
|
||||
/**
|
||||
* 首页特色方配置
|
||||
* @param params {{ store_id?: number|string }}
|
||||
*/
|
||||
export async function getSpecialPrescriptionHomeApi(params) {
|
||||
return await get('/special-prescription/home', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 特色方列表页配置
|
||||
* @param params {{ store_id?: number|string }}
|
||||
*/
|
||||
export async function getSpecialPrescriptionListPageApi(params) {
|
||||
return await get('/special-prescription/list-page', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 特色方列表
|
||||
* @param params {{ store_id?: number|string, category_id?: number, name?: string, page?: number, pageSize?: number }}
|
||||
*/
|
||||
export async function getSpecialPrescriptionListApi(params) {
|
||||
return await get('/special-prescription/list', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 特色方详情
|
||||
* @param params {{ id: number|string, store_id?: number|string }}
|
||||
*/
|
||||
export async function getSpecialPrescriptionDetailApi(params) {
|
||||
return await get('/special-prescription/detail', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 特色方分类
|
||||
*/
|
||||
export async function getSpecialPrescriptionCategoriesApi(params = {}) {
|
||||
return await get('/special-prescription/categories', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂号成功后创建选方记录
|
||||
* @param params {{ register_id, special_prescription_id, dose_count, store_id, doctor_id }}
|
||||
*/
|
||||
export async function createSpecialPrescriptionPatientRecordApi(params) {
|
||||
return await post('/special-prescription/create-patient-record', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存特色方挂号上下文(merge 已有值,避免后续页面未传 sku_id 时覆盖为 0)
|
||||
* @param ctx {{ special_prescription_id, dose_count?, sku_id?, doctor_id? }}
|
||||
*/
|
||||
export function saveSpecialPrescriptionRegisterContext(ctx) {
|
||||
if (!ctx || !ctx.special_prescription_id) return
|
||||
const prev = getSpecialPrescriptionRegisterContext() || {}
|
||||
const merged = {
|
||||
special_prescription_id: ctx.special_prescription_id,
|
||||
dose_count: ctx.dose_count != null ? ctx.dose_count : (prev.dose_count || 1),
|
||||
doctor_id: ctx.doctor_id != null ? ctx.doctor_id : (prev.doctor_id || ''),
|
||||
}
|
||||
// 未传 sku_id 字段时保留 storage 已有值,防止中间页覆盖为 0
|
||||
if (Object.prototype.hasOwnProperty.call(ctx, 'sku_id')) {
|
||||
merged.sku_id = Number(ctx.sku_id) || 0
|
||||
} else {
|
||||
merged.sku_id = Number(prev.sku_id) || 0
|
||||
}
|
||||
uni.setStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY, merged)
|
||||
}
|
||||
|
||||
export function getSpecialPrescriptionRegisterContext() {
|
||||
return uni.getStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY) || null
|
||||
}
|
||||
|
||||
export function clearSpecialPrescriptionRegisterContext() {
|
||||
uni.removeStorageSync(SPECIAL_PRESCRIPTION_STORAGE_KEY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂号成功后绑定特色方选方记录(幂等,失败不阻断流程)
|
||||
* @param registerId
|
||||
*/
|
||||
export async function tryCreateSpecialPrescriptionPatientRecord(registerId) {
|
||||
if (!registerId) return
|
||||
const ctx = getSpecialPrescriptionRegisterContext()
|
||||
if (!ctx || !ctx.special_prescription_id) return
|
||||
|
||||
try {
|
||||
const res = await createSpecialPrescriptionPatientRecordApi({
|
||||
register_id: registerId,
|
||||
special_prescription_id: ctx.special_prescription_id,
|
||||
dose_count: ctx.dose_count || 1,
|
||||
sku_id: ctx.sku_id || 0,
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
doctor_id: ctx.doctor_id,
|
||||
})
|
||||
if (res.data?.code === 0 || res.data?.code === '0') {
|
||||
clearSpecialPrescriptionRegisterContext()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('tryCreateSpecialPrescriptionPatientRecord', e)
|
||||
}
|
||||
}
|
||||
78
request/api/transferPrescription.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import {post, get} from './http'
|
||||
|
||||
/**
|
||||
* 获取转诊消息列表
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTransferPrescriptionListApi() {
|
||||
return await post('/transfer-prescription/list', {}, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转诊详情
|
||||
* @param params { id: number }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTransferPrescriptionDetailApi(params) {
|
||||
return await get('/transfer-prescription/detail', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同意转诊
|
||||
* @param params { id: number }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function agreeTransferPrescriptionApi(params) {
|
||||
return await post('/transfer-prescription/agree', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝转诊
|
||||
* @param params { id: number, reason?: string }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function rejectTransferPrescriptionApi(params) {
|
||||
return await post('/transfer-prescription/reject', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转诊问题列表(用于咨询页面)
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTransferQuestionsApi() {
|
||||
return await get('/transfer-prescription/get-questions', {}, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交转诊问题答案
|
||||
* @param params { transfer_id: number, answers: Array<{question_id: number, answer: string}> }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function submitTransferQuestionsApi(params) {
|
||||
return await post('/transfer-prescription/submit-questions', params, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转诊助理配置(移动端)
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTransferAssistantConfigApi() {
|
||||
return await get('/transfer-assistant-config/get-config', {}, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转诊咨询协议信息(固定文案和协议列表)
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getTransferConsultationAgreementInfoApi() {
|
||||
return await get('/transfer-consultation/get-agreement-info', {}, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议详情(根据协议ID)
|
||||
* @param {number} agreementId 协议ID
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getAgreementDetailApi(agreementId) {
|
||||
return await get(`/agreement/get-detail?id=${agreementId}`, {}, 3)
|
||||
}
|
||||
10
request/api/user.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import {post} from './http'
|
||||
|
||||
/**
|
||||
* 获取用户信息(包含订单统计)
|
||||
* @param params { store_id: number }
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
export async function getUserInfoApi(params) {
|
||||
return await post('/user/info', params, 3)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 594 B |
|
Before Width: | Height: | Size: 1019 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 859 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 971 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 67 KiB |
BIN
static/share.png
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 1.7 KiB |
BIN
static/xx/cp.png
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
BIN
static/xx/xt.png
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -326,6 +326,9 @@ const ChatManager = {
|
||||
if (parsed.type === 'price_update') {
|
||||
return parsed.message || '[价格更新]';
|
||||
}
|
||||
if (parsed.type === 'price_adjust') {
|
||||
return parsed.message || '[价格调整]';
|
||||
}
|
||||
return '[系统消息]';
|
||||
} catch (e) {
|
||||
return '[系统消息]';
|
||||
|
||||
816
subPackages/chat/components/MessageBubble.vue
Normal file
@@ -0,0 +1,816 @@
|
||||
<template>
|
||||
<view class="bubble-container" :class="{ 'mine': isMine, 'other': !isMine }">
|
||||
<!-- 头像:非己方可点进医生详情 -->
|
||||
<image class="avatar" :src="avatar" mode="aspectFill" @click.stop="onAvatarClick"></image>
|
||||
|
||||
<!-- 消息内容包裹 -->
|
||||
<view class="content-wrapper">
|
||||
|
||||
<!-- 文本/语音/视频/图片 气泡 -->
|
||||
<view class="message-bubble"
|
||||
:class="['type-' + msg.message_type, { 'no-padding': isMediaOrCard || isSystemPriceUpdate }]"
|
||||
>
|
||||
|
||||
<!-- 0. 文本 -->
|
||||
<text v-if="msg.message_type === 0" class="text-content" user-select="true">{{ parsedContent }}</text>
|
||||
|
||||
<!-- 1. 图片 -->
|
||||
<image v-else-if="msg.message_type === 1"
|
||||
:src="parsedContent"
|
||||
mode="widthFix"
|
||||
class="media-content image"
|
||||
@click="$emit('previewImage', parsedContent)">
|
||||
</image>
|
||||
|
||||
<!-- 2. 语音 (优化:动态宽度 + 播放动画) -->
|
||||
<view v-else-if="msg.message_type === 2"
|
||||
class="audio-content"
|
||||
:style="{ width: getAudioWidth(msg.duration) }"
|
||||
@click="$emit('playAudio', msg)">
|
||||
|
||||
<!-- 我发的:时长在左,图标在右 -->
|
||||
<block v-if="isMine">
|
||||
<text class="duration">{{ msg.duration }}''</text>
|
||||
<view class="voice-icon-wrap mine" :class="{ 'playing': isPlaying }">
|
||||
<u-icon name="volume-fill" size="20" color="#fff"></u-icon>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 别人发的:图标在左,时长在右 -->
|
||||
<block v-else>
|
||||
<view class="voice-icon-wrap other" :class="{ 'playing': isPlaying }">
|
||||
<u-icon name="volume-fill" size="20" color="#333"></u-icon>
|
||||
</view>
|
||||
<text class="duration">{{ msg.duration }}''</text>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 3. 视频 -->
|
||||
<video v-else-if="msg.message_type === 3"
|
||||
:src="parsedContent"
|
||||
controls
|
||||
class="media-content video">
|
||||
</video>
|
||||
|
||||
<!-- 9. 系统消息 (优化:回归原生服务通知风格) -->
|
||||
<block v-else-if="msg.message_type === 9">
|
||||
<!-- 改价通知卡片 -->
|
||||
<view
|
||||
v-if="parsedContent.type === 'price_update' || parsedContent.type === 'price_adjust'"
|
||||
class="service-notify-card"
|
||||
@click="parsedContent.order_id && $emit('viewOrder', parsedContent.order_id, parsedContent.order_no)"
|
||||
>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="sn-header">
|
||||
<text class="sn-title">{{ parsedContent.type === 'price_update' ? '订单改价通知' : '费用调整提醒' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容区:标准键值对列表 -->
|
||||
<view class="sn-body">
|
||||
|
||||
<!-- 场景A: 运费/总价更新 -->
|
||||
<block v-if="parsedContent.type === 'price_update'">
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">变动说明</text>
|
||||
<text class="sn-value">{{ parsedContent.message }}</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">最新应付</text>
|
||||
<text class="sn-value price">¥{{ parsedContent.total_pay_price }}</text>
|
||||
</view>
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">订单编号</text>
|
||||
<text class="sn-value sub">{{ parsedContent.order_no }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 场景B: 单品/明细调整 -->
|
||||
<block v-else-if="parsedContent.type === 'price_adjust'">
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">调整项目</text>
|
||||
<text class="sn-value truncate">{{ parsedContent.drug_name || '医疗服务' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">价格变动</text>
|
||||
<view class="sn-value-group">
|
||||
<text class="old">¥{{ parsedContent.old_price }}</text>
|
||||
<u-icon name="arrow-right" size="10" color="#999" style="margin:0 4rpx;"></u-icon>
|
||||
<text class="new">¥{{ parsedContent.new_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">变动金额</text>
|
||||
<text class="sn-value" :class="parsedContent.adjust_amount >= 0 ? 'color-orange' : 'color-green'">
|
||||
{{ parsedContent.adjust_amount >= 0 ? '增加' : '减少' }} ¥{{ Math.abs(parsedContent.adjust_amount) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="sn-row">
|
||||
<text class="sn-label">当前总额</text>
|
||||
<text class="sn-value strong">¥{{ parsedContent.total_pay_price }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="sn-footer">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="12" color="#ccc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 普通系统文本 (保持原样) -->
|
||||
<view v-else class="system-inner">
|
||||
<text>{{ msg.message_content }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 卡片类消息 (4, 10, 11, 12, 13) -->
|
||||
<view v-else-if="isCardType" class="card-content">
|
||||
|
||||
<!-- 类型10: 挂号 -->
|
||||
<block v-if="msg.message_type === 10">
|
||||
<view class="card-header bg-green">
|
||||
<view class="title-group"><u-icon name="calendar-fill" size="16" color="#059669"></u-icon><text class="card-title">预约挂号</text></view>
|
||||
<text class="status-tag" :class="'status-' + parsedContent.status">{{ getRegisterStatusText(parsedContent.status) }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row"><text class="label">订单号</text><text class="value order-font">{{ parsedContent.order_no }}</text></view>
|
||||
<view class="info-row"><text class="label">就诊人</text><text class="value">{{ (parsedContent.user_patient && parsedContent.user_patient.name) || '未知' }}</text></view>
|
||||
<view class="info-row highlight"><text class="label">费用</text><text class="value price">¥{{ parsedContent.price }}</text></view>
|
||||
<view class="text-block" v-if="parsedContent.chief_complaint">
|
||||
<text class="block-label">主诉</text>
|
||||
<text class="block-content">{{ parsedContent.chief_complaint }}</text>
|
||||
</view>
|
||||
<view class="text-block" v-if="parsedContent.illnessInfo">
|
||||
<text class="block-label">适用症/症状</text>
|
||||
<text class="block-content">{{ parsedContent.illnessInfo }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="parsedContent.illnessInfo">
|
||||
<text class="label">是否就诊过</text>
|
||||
<text class="value">{{ parsedContent.has_visited === 1 || parsedContent.has_visited === '1' ? '是' : '否' }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="parsedContent.illnessInfo">
|
||||
<text class="label">是否使用过药品</text>
|
||||
<text class="value">{{ parsedContent.has_used_drug === 1 || parsedContent.has_used_drug === '1' ? '是' : '否' }}</text>
|
||||
</view>
|
||||
<view v-if="registerCardDrugRows.length" class="drug-list-block">
|
||||
<text class="block-label">所选药品</text>
|
||||
<view v-for="row in registerCardDrugRows" :key="row._wxKey" class="info-row drug-spec-row">
|
||||
<image
|
||||
v-if="row.image"
|
||||
class="drug-thumb"
|
||||
:src="row.image"
|
||||
mode="aspectFill"
|
||||
@click.stop="$emit('previewImage', row.image)"
|
||||
/>
|
||||
<view class="drug-name-wrap">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text v-if="row.specification" class="spec-text">{{ row.specification }}</text>
|
||||
</view>
|
||||
<text class="value">×{{ row.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer"><button class="action-btn" @click="$emit('viewRegister', parsedContent.id)">查看详情</button></view>
|
||||
</block>
|
||||
|
||||
<!-- 类型11: 复诊用药确认(扩展 flow,与药店购药 type11 互斥展示) -->
|
||||
<block v-else-if="msg.message_type === 11 && parsedContent && parsedContent.flow === 'follow_up_drug'">
|
||||
<view class="card-header bg-gray">
|
||||
<view class="title-group"><u-icon name="question-circle-fill" size="16" color="#64748B"></u-icon><text class="card-title">医生助理</text></view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="text-block" v-if="parsedContent.question">
|
||||
<text class="block-content">{{ parsedContent.question }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-for="(row, idx) in (parsedContent.drugs || [])" :key="idx">
|
||||
<text class="label">{{ row.name || '药品' }}</text>
|
||||
<text class="value">×{{ row.quantity != null ? row.quantity : 1 }}</text>
|
||||
</view>
|
||||
<view v-if="followUpAnsweredLabel" class="info-row">
|
||||
<text class="label">您的选择</text>
|
||||
<text class="value">{{ followUpAnsweredLabel }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="parsedContent.has_visited === 1 || parsedContent.has_visited === '1' || parsedContent.has_visited === 0 || parsedContent.has_visited === '0'"
|
||||
class="info-row"
|
||||
>
|
||||
<text class="label">是否就诊过</text>
|
||||
<text class="value">{{ parsedContent.has_visited === 1 || parsedContent.has_visited === '1' ? '是' : '否' }}</text>
|
||||
</view>
|
||||
<view class="text-block" v-if="parsedContent.illness_info">
|
||||
<text class="block-label">适用症/症状</text>
|
||||
<text class="block-content">{{ parsedContent.illness_info }}</text>
|
||||
</view>
|
||||
<view class="text-block" v-if="parsedContent.supplement">
|
||||
<text class="block-label">补充信息</text>
|
||||
<text class="block-content">{{ parsedContent.supplement }}</text>
|
||||
</view>
|
||||
<view v-if="parsedContent.answer_status === 'pending'" class="follow-up-pending-readonly">
|
||||
<text class="follow-up-pending-tip">请在挂号后的用药确认页完成选择,此处不可更改</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型11: 患者就诊经历 -->
|
||||
<block v-else-if="msg.message_type === 11">
|
||||
<view class="card-header bg-gray">
|
||||
<view class="title-group"><u-icon name="file-text-fill" size="16" color="#64748B"></u-icon><text class="card-title">患者就诊经历</text></view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row" v-if="parsedContent.drug_name">
|
||||
<text class="label">药品名称</text>
|
||||
<text class="value">{{ parsedContent.drug_name }}</text>
|
||||
</view>
|
||||
<view class="text-block" v-if="parsedContent.illness_info">
|
||||
<text class="block-label">症状描述</text>
|
||||
<text class="block-content">{{ parsedContent.illness_info }}</text>
|
||||
</view>
|
||||
<view class="info-row"><text class="label">是否就诊过</text><text class="value">{{ parsedContent.has_visited === '1' ? '是' : '否' }}</text></view>
|
||||
<view class="info-row"><text class="label">是否使用过药品</text><text class="value">{{ parsedContent.has_used_drug === '1' ? '是' : '否' }}</text></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型4: 电子处方 -->
|
||||
<block v-else-if="msg.message_type === 4">
|
||||
<view class="card-header bg-blue">
|
||||
<view class="title-group"><u-icon name="order" size="16" color="#0A84FF"></u-icon><text class="card-title">电子处方</text></view>
|
||||
<text class="status-tag blue">{{ getStatusText(parsedContent.status) }}</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row"><text class="label">诊断单号</text><text class="value small">{{ parsedContent.order_no }}</text></view>
|
||||
<view class="info-row"><text class="label">金额</text><text class="value price">¥{{ parsedContent.total_pay_price }}</text></view>
|
||||
</view>
|
||||
<view class="card-footer split">
|
||||
<text class="link-btn" @click="$emit('viewPrescription', parsedContent.id, parsedContent.order_no)">详情</text>
|
||||
<button class="action-btn primary" @click="$emit('goToOrderMedicine', parsedContent.order_id, parsedContent.order_no)">立即支付</button>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型12: 商品 -->
|
||||
<block v-else-if="msg.message_type === 12">
|
||||
<view class="card-header bg-blue">
|
||||
<view class="title-group"><u-icon name="shopping-cart-fill" size="16" color="#0A84FF"></u-icon><text class="card-title">推荐商品</text></view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="info-row"><text class="label">名称</text><text class="value">{{ parsedContent.product_name }}</text></view>
|
||||
<view class="info-row"><text class="label">价格</text><text class="value price">¥{{ parsedContent.price }}</text></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型13: 结束问诊 -->
|
||||
<block v-else-if="msg.message_type === 13">
|
||||
<view class="card-header bg-green">
|
||||
<view class="title-group"><u-icon name="checkmark-circle-fill" size="16" color="#059669"></u-icon><text class="card-title">问诊结束</text></view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="desc-text">{{ parsedContent.end_reason || parsedContent.reason || parsedContent.message || '本次服务已完成' }}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 类型14: 转诊消息 -->
|
||||
<block v-else-if="msg.message_type === 14">
|
||||
<TransferMessageBubble
|
||||
:message-data="parsedContent"
|
||||
@agree="handleTransferAgree"
|
||||
@reject="handleTransferReject"
|
||||
/>
|
||||
</block>
|
||||
|
||||
<block v-else>
|
||||
<view class="card-body"><text>不支持的消息类型</text></view>
|
||||
</block>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 其他未知类型默认显示文本 -->
|
||||
<text v-else class="text-content">{{ parsedContent }}</text>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TransferMessageBubble from './TransferMessageBubble.vue'
|
||||
import {
|
||||
agreeTransferPrescriptionApi,
|
||||
rejectTransferPrescriptionApi
|
||||
} from '@/request/api/transferPrescription'
|
||||
export default {
|
||||
name: "MessageBubble",
|
||||
components: {
|
||||
TransferMessageBubble
|
||||
},
|
||||
props: {
|
||||
msg: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isMine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isPlaying: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 纯文本、图片、语音、视频以外的特殊类型,不需要默认的气泡背景/Padding
|
||||
isMediaOrCard() {
|
||||
// 1:图片, 3:视频, 4,10,11,12,13,14: 卡片 (9单独处理)
|
||||
return [1, 3, 4, 10, 11, 12, 13, 14].includes(this.msg.message_type);
|
||||
},
|
||||
// 是否是系统消息且为价格变动(需要独立卡片样式)
|
||||
isSystemPriceUpdate() {
|
||||
return this.msg.message_type === 9 && (this.parsedContent.type === 'price_update' || this.parsedContent.type === 'price_adjust');
|
||||
},
|
||||
isCardType() {
|
||||
return [4, 10, 11, 12, 13, 14].includes(this.msg.message_type);
|
||||
},
|
||||
parsedContent() {
|
||||
const { message_type, message_content } = this.msg;
|
||||
// 包含9(System)在内的复杂类型尝试解析 JSON
|
||||
if ([4, 9, 10, 11, 12, 13, 14].includes(message_type)) {
|
||||
try {
|
||||
return typeof message_content === 'object' ? message_content : JSON.parse(message_content);
|
||||
} catch (e) {
|
||||
return message_content;
|
||||
}
|
||||
}
|
||||
return message_content;
|
||||
},
|
||||
/**
|
||||
* 预约挂号卡片药品行:药名 + 规格 + 数量(与医生端一致,去掉底部「各 X 盒」)
|
||||
*/
|
||||
registerCardDrugRows() {
|
||||
if (this.msg.message_type !== 10) return [];
|
||||
const pc = this.parsedContent;
|
||||
if (!pc || typeof pc !== 'object') return [];
|
||||
const fallbackQty = pc.number != null && pc.number !== '' ? Number(pc.number) : 1;
|
||||
const arr = pc.selected_western_drugs;
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
return arr.map((d, idx) => {
|
||||
const q = d && d.quantity != null ? Number(d.quantity) : fallbackQty;
|
||||
const id = d && (d.drug_id || d.id);
|
||||
return {
|
||||
_wxKey: 'rd-' + (id != null ? id : idx),
|
||||
name: (d && d.name) || '',
|
||||
specification: (d && (d.specification || d.spec || d.drug_spec)) || '',
|
||||
image: (d && (d.image || d.drug_image)) || '',
|
||||
quantity: q >= 1 ? q : 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (pc.drug && pc.drug.name) {
|
||||
return [{
|
||||
_wxKey: 'rd-' + (pc.drug.drug_id || pc.drug.id || 0),
|
||||
name: pc.drug.name,
|
||||
specification: pc.drug.specification || pc.drug.spec || '',
|
||||
image: pc.drug.image || pc.drug.drug_image || '',
|
||||
quantity: fallbackQty >= 1 ? fallbackQty : 1,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
followUpAnsweredLabel() {
|
||||
const pc = this.parsedContent;
|
||||
if (!pc || pc.flow !== 'follow_up_drug') return '';
|
||||
const st = pc.answer_status;
|
||||
if (st === 'yes' || st === '1') return '是,曾使用过';
|
||||
if (st === 'no' || st === '0') return '否,未使用过';
|
||||
return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 非己方头像点击:通知父页跳转医生详情;己方头像不响应
|
||||
*/
|
||||
onAvatarClick() {
|
||||
if (this.isMine) return;
|
||||
this.$emit('avatarClick');
|
||||
},
|
||||
// 计算语音条宽度 (Min: 100rpx, Max: 350rpx, Unit: 10rpx/sec)
|
||||
getAudioWidth(duration) {
|
||||
const min = 120;
|
||||
const max = 350;
|
||||
const unit = 12;
|
||||
let w = min + (parseInt(duration) || 0) * unit;
|
||||
return Math.min(w, max) + 'rpx';
|
||||
},
|
||||
getStatusText(status) { const map = { 0: '待审核', 1: '已审核', 2: '未通过' }; return map[status] || '状态未知'; },
|
||||
getRegisterStatusText(status) { const map = { 0: '待就诊', 1: '已缴费', 2: '已就诊', 3: '已取消', 4: '已退费' }; return map[status] || '状态未知'; },
|
||||
// 处理转诊同意
|
||||
handleTransferAgree(messageData) {
|
||||
if (!messageData || !messageData.transfer_id) {
|
||||
uni.showToast({
|
||||
title: '转诊信息不完整',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '确认转诊',
|
||||
content: '确定同意转诊吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
agreeTransferPrescriptionApi({ id: messageData.transfer_id }).then((result) => {
|
||||
if (result.data.code == 0) {
|
||||
uni.showToast({
|
||||
title: '同意转诊成功',
|
||||
icon: 'success'
|
||||
})
|
||||
// 跳转到咨询页面
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
url: `/subPackages/transfer/transfer-consultation?transfer_id=${messageData.transfer_id}`,
|
||||
fail: (err) => {
|
||||
console.error('跳转失败:', err)
|
||||
uni.showToast({ title: '跳转失败,请重试', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.message || '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('同意转诊失败:', error)
|
||||
uni.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理转诊拒绝
|
||||
handleTransferReject(messageData) {
|
||||
if (!messageData || !messageData.transfer_id) {
|
||||
uni.showToast({
|
||||
title: '转诊信息不完整',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '拒绝转诊',
|
||||
content: '确定拒绝转诊吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
rejectTransferPrescriptionApi({ id: messageData.transfer_id }).then((result) => {
|
||||
if (result.data.code == 0) {
|
||||
uni.showToast({
|
||||
title: '已拒绝转诊',
|
||||
icon: 'success'
|
||||
})
|
||||
// 触发消息更新事件
|
||||
uni.$emit('chat-message-update')
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result.data.message || '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('拒绝转诊失败:', error)
|
||||
uni.showToast({
|
||||
title: '操作失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 变量
|
||||
$primary: #0A84FF;
|
||||
$bg-mine: #0A84FF;
|
||||
$bg-other: #FFFFFF;
|
||||
$text-main: #333333;
|
||||
$text-sub: #666666;
|
||||
$radius-bubble: 12rpx;
|
||||
$price-color: #FF4D4F; // 价格红
|
||||
$success-color: #059669;
|
||||
|
||||
.bubble-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30rpx;
|
||||
width: 100%;
|
||||
|
||||
.avatar {
|
||||
width: 72rpx; height: 72rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 72%; // 稍微放宽一点
|
||||
}
|
||||
|
||||
&.other {
|
||||
flex-direction: row;
|
||||
.avatar { margin-right: 16rpx; }
|
||||
.message-bubble {
|
||||
background-color: $bg-other;
|
||||
color: $text-main;
|
||||
border-radius: 4rpx $radius-bubble $radius-bubble $radius-bubble;
|
||||
|
||||
// 添加小箭头
|
||||
&::before {
|
||||
content: ''; position: absolute; top: 20rpx; left: -10rpx;
|
||||
width: 0; height: 0;
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-right: 12rpx solid $bg-other;
|
||||
}
|
||||
// 卡片或特殊系统消息不需要箭头
|
||||
&.no-padding::before { display: none; }
|
||||
}
|
||||
}
|
||||
|
||||
&.mine {
|
||||
flex-direction: row-reverse;
|
||||
.avatar { margin-left: 16rpx; }
|
||||
.message-bubble {
|
||||
background-color: $bg-mine;
|
||||
color: #fff;
|
||||
border-radius: $radius-bubble 4rpx $radius-bubble $radius-bubble;
|
||||
|
||||
&::after {
|
||||
content: ''; position: absolute; top: 20rpx; right: -10rpx;
|
||||
width: 0; height: 0;
|
||||
border-top: 10rpx solid transparent;
|
||||
border-bottom: 10rpx solid transparent;
|
||||
border-left: 12rpx solid $bg-mine;
|
||||
}
|
||||
&.no-padding::after { display: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
word-break: break-all;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.05);
|
||||
min-height: 72rpx;
|
||||
display: flex; align-items: center;
|
||||
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
&::before, &::after { display: none; }
|
||||
}
|
||||
}
|
||||
|
||||
// 图片/视频
|
||||
.media-content {
|
||||
border-radius: 8rpx;
|
||||
&.image { max-width: 300rpx; display: block; }
|
||||
&.video { width: 300rpx; height: 170rpx; }
|
||||
}
|
||||
|
||||
// ========== 语音条样式 (Audio) ==========
|
||||
.audio-content {
|
||||
display: flex; align-items: center;
|
||||
// 宽度由 style 动态控制,这里不要锁死 width
|
||||
justify-content: space-between;
|
||||
|
||||
.duration {
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.voice-icon-wrap {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 44rpx; height: 44rpx;
|
||||
|
||||
// 对方发的消息,图标在左,波纹向右(默认)
|
||||
&.other {
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
// 我发的消息,图标在右,波纹需要转向左侧 (rotate 180)
|
||||
&.mine {
|
||||
margin-left: 6rpx;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
// 播放时的动画:呼吸/波动效果
|
||||
&.playing {
|
||||
animation: voice-pulse 1.2s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 语音播放动画关键帧
|
||||
@keyframes voice-pulse {
|
||||
0% { opacity: 1; transform: scale(1) rotate(var(--rotate-angle, 0deg)); }
|
||||
50% { opacity: 0.4; transform: scale(0.92) rotate(var(--rotate-angle, 0deg)); }
|
||||
100% { opacity: 1; transform: scale(1) rotate(var(--rotate-angle, 0deg)); }
|
||||
}
|
||||
// 为了解决 rotate 和 scale 同时作用的问题,分别定义 mine 和 other
|
||||
.voice-icon-wrap.mine.playing { --rotate-angle: 180deg; animation: voice-pulse 1.2s infinite ease-in-out; }
|
||||
.voice-icon-wrap.other.playing { --rotate-angle: 0deg; animation: voice-pulse 1.2s infinite ease-in-out; }
|
||||
|
||||
|
||||
// ========== 卡片样式 (通用) ==========
|
||||
.card-content {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #eee;
|
||||
width: 480rpx; // 卡片定宽
|
||||
|
||||
.card-header {
|
||||
padding: 16rpx 20rpx;
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
border-bottom: 1rpx solid #f9f9f9;
|
||||
|
||||
&.bg-green { background: #F0FDF4; }
|
||||
&.bg-blue { background: #EFF6FF; }
|
||||
&.bg-gray { background: #F8FAFC; }
|
||||
|
||||
.title-group { display: flex; align-items: center; gap: 8rpx; .card-title { font-size: 26rpx; font-weight: 600; color: #333; } }
|
||||
.status-tag { font-size: 20rpx; padding: 2rpx 10rpx; border-radius: 6rpx; background: #fff; color: $text-sub; border: 1rpx solid rgba(0,0,0,0.05); }
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20rpx; display: flex; flex-direction: column; gap: 12rpx;
|
||||
.info-row {
|
||||
display: flex; justify-content: space-between; font-size: 24rpx;
|
||||
.label { color: #888; }
|
||||
.value { color: #333; font-weight: 500; text-align: right; max-width: 70%; }
|
||||
.order-font { font-family: Courier, monospace; letter-spacing: 0.5rpx; }
|
||||
.price { color: #ef4444; font-size: 28rpx; font-weight: bold; }
|
||||
.small { font-size: 22rpx; color: #999; }
|
||||
}
|
||||
.drug-spec-row {
|
||||
align-items: flex-start;
|
||||
.drug-thumb {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #f3f4f6;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
.drug-name-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.spec-text {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #9ca3af;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.drug-list-block {
|
||||
.block-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
.desc-text { font-size: 26rpx; color: #333; }
|
||||
.text-block {
|
||||
background: #f5f5f5; padding: 16rpx; border-radius: 8rpx;
|
||||
.block-label { display: block; font-size: 22rpx; color: #999; margin-bottom: 6rpx; }
|
||||
.block-content { font-size: 26rpx; color: #333; line-height: 1.4; }
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 16rpx 20rpx; border-top: 1rpx solid #f0f0f0;
|
||||
.action-btn {
|
||||
width: 100%; height: 60rpx; line-height: 60rpx; font-size: 26rpx; border-radius: 30rpx; background: #fff; border: 1rpx solid #ddd; color: #555;
|
||||
&.primary { background: $primary; color: #fff; border: none; }
|
||||
&::after { border: none; }
|
||||
}
|
||||
&.split { display: flex; justify-content: space-between; align-items: center; .link-btn { font-size: 24rpx; color: $primary; padding: 10rpx; } .action-btn { width: 160rpx; } }
|
||||
}
|
||||
|
||||
.follow-up-pending-readonly {
|
||||
margin-top: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12rpx;
|
||||
.follow-up-pending-tip {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 原生风格服务通知 (Service Notify Card) ==========
|
||||
// 这是一个非常经典、耐看的布局,模仿微信/支付宝的服务通知
|
||||
.service-notify-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12rpx; // 稍微调小圆角,更正式
|
||||
width: 480rpx;
|
||||
border: 1rpx solid #eaeaea; // 纯粹的边框
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
// 头部:极简
|
||||
.sn-header {
|
||||
padding: 24rpx 24rpx 16rpx 24rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
.sn-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
// 内容区:标准的左key右value列表
|
||||
.sn-body {
|
||||
padding: 20rpx 24rpx;
|
||||
display: flex; flex-direction: column; gap: 16rpx;
|
||||
|
||||
.sn-row {
|
||||
display: flex; justify-content: space-between; align-items: flex-start;
|
||||
line-height: 1.4;
|
||||
|
||||
.sn-label {
|
||||
font-size: 26rpx; color: #999; // 经典的标签灰
|
||||
min-width: 110rpx; // 保证对齐
|
||||
}
|
||||
|
||||
.sn-value {
|
||||
font-size: 26rpx; color: #333; text-align: right; flex: 1;
|
||||
&.truncate {
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 320rpx;
|
||||
}
|
||||
&.price { color: #333; font-weight: 600; font-size: 28rpx; } // 普通金额强调
|
||||
&.sub { color: #999; font-family: monospace; }
|
||||
&.strong { color: #333; font-weight: bold; font-size: 30rpx; }
|
||||
|
||||
// 涨跌颜色
|
||||
&.color-orange { color: #fa8c16; }
|
||||
&.color-green { color: #52c41a; }
|
||||
}
|
||||
|
||||
// 价格变化组合
|
||||
.sn-value-group {
|
||||
display: flex; align-items: center; justify-content: flex-end;
|
||||
.old { color: #999; text-decoration: line-through; font-size: 24rpx; }
|
||||
.new { color: #333; font-weight: 600; font-size: 26rpx; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部:通用的查看详情
|
||||
.sn-footer {
|
||||
border-top: 1rpx solid #f5f5f5;
|
||||
padding: 18rpx 24rpx;
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
font-size: 24rpx; color: #666;
|
||||
background: #fff;
|
||||
|
||||
&:active {
|
||||
background: #f9f9f9; // 仅底栏有点击态
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-inner {
|
||||
background: rgba(0,0,0,0.05); padding: 8rpx 20rpx; border-radius: 10rpx; font-size: 22rpx; color: #999; align-self: center; margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
309
subPackages/chat/components/TransferMessageBubble.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<view class="transfer-message-bubble">
|
||||
<view class="bubble-header">
|
||||
<view class="header-icon">
|
||||
<text class="iconfont icon-mendian"></text>
|
||||
</view>
|
||||
<view class="header-title">转诊消息</view>
|
||||
</view>
|
||||
|
||||
<view class="bubble-body">
|
||||
<view class="info-item" v-if="messageData.transfer_store">
|
||||
<text class="label">转诊诊所:</text>
|
||||
<text class="value">{{ messageData.transfer_store.name || '未知诊所' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="messageData.delegate_store">
|
||||
<text class="label">委托诊所:</text>
|
||||
<text class="value">{{ messageData.delegate_store.name || '未知诊所' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="messageData.prescription_no">
|
||||
<text class="label">处方编号:</text>
|
||||
<text class="value">{{ messageData.prescription_no }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="messageData.transfer_reason">
|
||||
<text class="label">转诊原因:</text>
|
||||
<text class="value">{{ messageData.transfer_reason }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 咨询问题和答案 -->
|
||||
<view class="questions-section" v-if="messageData.questions && messageData.questions.length > 0">
|
||||
<view class="section-title">
|
||||
<text class="section-icon">❓</text>
|
||||
<text class="section-text">咨询问题</text>
|
||||
</view>
|
||||
<view class="question-item" v-for="(qa, index) in messageData.questions" :key="index">
|
||||
<view class="question-text">
|
||||
<text class="question-number">{{ index + 1 }}.</text>
|
||||
<text class="question-content">{{ qa.question }}</text>
|
||||
</view>
|
||||
<view class="answer-text" :class="qa.answer === '是' ? 'answer-yes' : (qa.answer === '否' ? 'answer-no' : 'answer-other')">
|
||||
<text class="answer-icon">{{ qa.answer === '是' ? '✓' : (qa.answer === '否' ? '✗' : '?') }}</text>
|
||||
<text>{{ qa.answer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bubble-footer" v-if="messageData.status === 0">
|
||||
<button class="action-btn agree" @click="handleAgree">同意转诊</button>
|
||||
<button class="action-btn reject" @click="handleReject">拒绝转诊</button>
|
||||
</view>
|
||||
|
||||
<view class="bubble-footer" v-else-if="messageData.status === 1">
|
||||
<text class="status-text confirmed">已同意转诊</text>
|
||||
</view>
|
||||
|
||||
<view class="bubble-footer" v-else-if="messageData.status === 3">
|
||||
<text class="status-text cancelled">已拒绝转诊</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TransferMessageBubble',
|
||||
props: {
|
||||
messageData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAgree() {
|
||||
this.$emit('agree', this.messageData);
|
||||
},
|
||||
handleReject() {
|
||||
this.$emit('reject', this.messageData);
|
||||
},
|
||||
// 获取答案的样式类:是=绿色,否=红色,其他=黄色
|
||||
getAnswerClass(answer) {
|
||||
if (answer === '是') {
|
||||
return 'answer-yes';
|
||||
} else if (answer === '否') {
|
||||
return 'answer-no';
|
||||
} else {
|
||||
return 'answer-other';
|
||||
}
|
||||
},
|
||||
// 获取答案的图标
|
||||
getAnswerIcon(answer) {
|
||||
if (answer === '是') {
|
||||
return '✓';
|
||||
} else if (answer === '否') {
|
||||
return '✗';
|
||||
} else {
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.transfer-message-bubble {
|
||||
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid #fed7aa;
|
||||
padding: 24rpx;
|
||||
margin: 20rpx 0;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.bubble-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 2rpx solid #fed7aa;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.bubble-body {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.bubble-footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: flex-end;
|
||||
padding-top: 16rpx;
|
||||
border-top: 2rpx solid #fed7aa;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.agree {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.reject {
|
||||
background: white;
|
||||
color: #64748b;
|
||||
border: 2rpx solid #cbd5e1;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.status-text.confirmed {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.status-text.cancelled {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* 咨询问题区域 */
|
||||
.questions-section {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid #fed7aa;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding-bottom: 12rpx;
|
||||
border-bottom: 2rpx solid #fed7aa;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.section-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.question-item {
|
||||
margin-bottom: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: white;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
}
|
||||
|
||||
.question-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 26rpx;
|
||||
color: #334155;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
font-weight: 600;
|
||||
color: #0A84FF;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.question-content {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.answer-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 6rpx;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.answer-yes {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.answer-yes .answer-icon {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.answer-no {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.answer-no .answer-icon {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.answer-other {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.answer-other .answer-icon {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.answer-icon {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
781
subPackages/common/components/AssistantConsultationShell.vue
Normal file
@@ -0,0 +1,781 @@
|
||||
<template>
|
||||
<!--
|
||||
问诊助手通用壳:协议抽屉、消息流、选项按钮等。
|
||||
仅 follow-up / transfer 分包页使用,放在 subPackages/common 以减小主包体积。
|
||||
-->
|
||||
<view class="consultation-container safe-area-inset-bottom">
|
||||
<view class="consultation-header">
|
||||
<view class="header-back" @click="handleHeaderBack">
|
||||
<text class="iconfont icon-arrow-left"></text>
|
||||
</view>
|
||||
<view class="header-title">{{ headerTitle }}</view>
|
||||
<view class="header-placeholder"></view>
|
||||
</view>
|
||||
|
||||
<view class="notice-box" v-if="showNotice">
|
||||
<view class="notice-text">
|
||||
<text v-for="(part, index) in parsedNoticeParts" :key="index">
|
||||
<text v-if="part.type === 'text'">{{ part.text }}</text>
|
||||
<text v-else-if="part.type === 'agreement'" class="notice-link" @click="$emit('viewAgreement', part.agreementId)">
|
||||
《{{ part.text }}》
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 协议抽屉:v-if 控制挂载,:show 控制显隐(与 chat 页一致) -->
|
||||
<page-container
|
||||
v-if="agreementPopupVisible"
|
||||
:show="agreementPopupVisible"
|
||||
position="bottom"
|
||||
round
|
||||
overlay
|
||||
custom-style="height:88%;"
|
||||
@clickoverlay="closeAgreement"
|
||||
@afterleave="closeAgreement"
|
||||
>
|
||||
<view class="agreement-popup">
|
||||
<view class="agreement-header">
|
||||
<text class="agreement-title">{{ currentAgreementName || '协议详情' }}</text>
|
||||
<view class="agreement-close" @click="closeAgreement">×</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="agreement-content" :show-scrollbar="true">
|
||||
<u-parse :html="agreementContent"></u-parse>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</page-container>
|
||||
|
||||
<scroll-view class="messages-scroll" scroll-y="true" :scroll-into-view="scrollIntoView" scroll-with-animation>
|
||||
<view class="messages-container">
|
||||
<view v-for="message in displayedMessages" :key="message.id">
|
||||
<view v-if="message.type === 'welcome'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>您好! 我是医生助理, 已收到您的用药申请:「{{ prescriptionInfo }}」, 为了帮助医生判断准确, 请按照您的真实情况回答以下问题。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'question'" class="message-item other" :id="'question-' + message.questionId">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'answer'" class="message-item mine">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'waiting'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>请稍等, 正在为您转接医生进行复诊开方...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="message.type === 'hint'" class="message-item other">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble">
|
||||
<text>{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isTyping" class="message-item other typing-indicator">
|
||||
<image class="avatar" :src="assistantAvatar" mode="aspectFill"></image>
|
||||
<view class="message-content">
|
||||
<view class="assistant-label">{{ assistantName }}</view>
|
||||
<view class="message-bubble typing-bubble">
|
||||
<view class="typing-dots">
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view :id="bottomAnchorId" style="height: 1px;"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<slot v-if="customFooterActive && $slots.footer" name="footer"></slot>
|
||||
<view
|
||||
v-else-if="showQuestionButtons && currentQuestion && !isSubmitting && !showWaiting && answerMode === 'multi'"
|
||||
class="action-footer multi-footer safe-area-inset-bottom"
|
||||
>
|
||||
<!-- 多选适用症:点选 toggling,确认后再提交;选「其他」可填自定义症状 -->
|
||||
<view class="multi-options">
|
||||
<button
|
||||
v-for="(answer, index) in currentQuestionAnswers"
|
||||
:key="index"
|
||||
hover-class="none"
|
||||
:class="['multi-option-btn', { selected: !!multiSelectedFlag[index] }]"
|
||||
@click="toggleMultiAnswer(index)"
|
||||
>
|
||||
{{ answer }}
|
||||
</button>
|
||||
</view>
|
||||
<input
|
||||
v-if="multiHasOtherSelected"
|
||||
class="multi-other-input"
|
||||
v-model="multiOtherText"
|
||||
placeholder="请填写具体症状"
|
||||
:maxlength="100"
|
||||
/>
|
||||
<!-- 不用原生 disabled,避免微信小程序卡死无法点击;未达标时 toast -->
|
||||
<button
|
||||
class="action-btn yes-btn confirm-btn"
|
||||
hover-class="none"
|
||||
:class="{ disabled: !multiConfirmEnabled }"
|
||||
@click="confirmMultiAnswer"
|
||||
>
|
||||
确认
|
||||
</button>
|
||||
</view>
|
||||
<view
|
||||
v-else-if="showQuestionButtons && currentQuestion && !isSubmitting && !showWaiting"
|
||||
class="action-footer safe-area-inset-bottom"
|
||||
>
|
||||
<button
|
||||
v-for="(answer, index) in currentQuestionAnswers"
|
||||
:key="index"
|
||||
hover-class="none"
|
||||
:class="['action-btn', answerButtonClass(answer, index)]"
|
||||
@click="emitSingleAnswer(answer)"
|
||||
>
|
||||
{{ answer }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="submitting-overlay" v-if="isSubmitting">
|
||||
<view class="submitting-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="submitting-text">{{ submittingText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AssistantConsultationShell',
|
||||
props: {
|
||||
headerTitle: {
|
||||
type: String,
|
||||
default: '转诊咨询',
|
||||
},
|
||||
showNotice: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
parsedNoticeParts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showAgreement: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
agreementContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentAgreementName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
displayedMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
assistantAvatar: {
|
||||
type: String,
|
||||
default: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/images/assistant-avatar.png',
|
||||
},
|
||||
assistantName: {
|
||||
type: String,
|
||||
default: '医生助理',
|
||||
},
|
||||
userAvatar: {
|
||||
type: String,
|
||||
default: 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/images/user-avatar.png',
|
||||
},
|
||||
prescriptionInfo: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isTyping: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scrollIntoView: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bottomAnchorId: {
|
||||
type: String,
|
||||
default: 'bottom-anchor',
|
||||
},
|
||||
/** 为 true 时展示底部「是/否」等选项(与转诊一致) */
|
||||
showQuestionButtons: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
currentQuestion: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
currentQuestionAnswers: {
|
||||
type: Array,
|
||||
default: () => ['是', '否'],
|
||||
},
|
||||
isSubmitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showWaiting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
submittingText: {
|
||||
type: String,
|
||||
default: '正在提交...',
|
||||
},
|
||||
/** 为 true 时使用 #footer 插槽,避免父组件始终占位导致无法显示默认答题按钮 */
|
||||
customFooterActive: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 答题模式:single 点一下即提交;multi 可多选后点确认
|
||||
* 诊所复诊按药选适用症用 multi
|
||||
*/
|
||||
answerMode: {
|
||||
type: String,
|
||||
default: 'single',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
/** 多选已选下标列表(整表替换,微信小程序下比 object+$set 更稳) */
|
||||
multiSelectedIndexes: [],
|
||||
/** 选「其他」时的自定义症状文案 */
|
||||
multiOtherText: '',
|
||||
/** 防双击:emit 后立刻锁,题目切换解锁 */
|
||||
answeringLock: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 题目切换时清空多选,避免上一题选中态残留
|
||||
currentQuestion() {
|
||||
this.multiSelectedIndexes = [];
|
||||
this.multiOtherText = '';
|
||||
this.answeringLock = false;
|
||||
},
|
||||
answerMode() {
|
||||
this.multiSelectedIndexes = [];
|
||||
this.multiOtherText = '';
|
||||
this.answeringLock = false;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
agreementPopupVisible: {
|
||||
get() {
|
||||
return this.showAgreement
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:showAgreement', val)
|
||||
},
|
||||
},
|
||||
/** 供模板判断选中,避免 :class 里写 indexOf */
|
||||
multiSelectedFlag() {
|
||||
const map = {};
|
||||
const list = this.multiSelectedIndexes || [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
map[list[i]] = true;
|
||||
}
|
||||
return map;
|
||||
},
|
||||
multiSelectedCount() {
|
||||
return (this.multiSelectedIndexes || []).length;
|
||||
},
|
||||
/** 是否选中了「其他」选项 */
|
||||
multiHasOtherSelected() {
|
||||
const answers = this.currentQuestionAnswers || [];
|
||||
const list = this.multiSelectedIndexes || [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const idx = list[i];
|
||||
if (answers[idx] === '其他') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/** 多选确认是否可提交:至少选一项;选了其他则自定义文案非空 */
|
||||
multiConfirmEnabled() {
|
||||
if (this.multiSelectedCount === 0) return false;
|
||||
if (this.multiHasOtherSelected) {
|
||||
return !!(this.multiOtherText && String(this.multiOtherText).trim());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleHeaderBack() {
|
||||
if (this.showAgreement) {
|
||||
this.closeAgreement();
|
||||
return;
|
||||
}
|
||||
this.$emit('back');
|
||||
},
|
||||
closeAgreement() {
|
||||
this.$emit('update:showAgreement', false);
|
||||
},
|
||||
answerButtonClass(answer, index) {
|
||||
const answers = this.currentQuestionAnswers
|
||||
const totalAnswers = answers.length
|
||||
if (totalAnswers === 2) {
|
||||
if (answer === '是') return 'yes-btn'
|
||||
if (answer === '否') return 'no-btn'
|
||||
}
|
||||
if (index === 0) return 'yes-btn'
|
||||
return 'no-btn'
|
||||
},
|
||||
/** 单选:点一下即提交,防双击重复 emit */
|
||||
emitSingleAnswer(answer) {
|
||||
if (this.answeringLock || this.isSubmitting) return;
|
||||
this.answeringLock = true;
|
||||
this.$emit('answer', answer);
|
||||
},
|
||||
/** 多选:切换某个适用症的选中状态 */
|
||||
toggleMultiAnswer(index) {
|
||||
if (this.answeringLock || this.isSubmitting) return;
|
||||
const list = this.multiSelectedIndexes || [];
|
||||
const pos = list.indexOf(index);
|
||||
if (pos === -1) {
|
||||
this.multiSelectedIndexes = list.concat([index]);
|
||||
} else {
|
||||
this.multiSelectedIndexes = list.filter((i) => i !== index);
|
||||
if (this.currentQuestionAnswers[index] === '其他') {
|
||||
this.multiOtherText = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 多选确认:选中标签用顿号拼接;「其他」替换为自定义填写内容
|
||||
*/
|
||||
confirmMultiAnswer() {
|
||||
if (this.answeringLock || this.isSubmitting) return;
|
||||
if (!this.multiConfirmEnabled) {
|
||||
if (this.multiSelectedCount === 0) {
|
||||
uni.showToast({ title: '请至少选择一项', icon: 'none' });
|
||||
} else if (this.multiHasOtherSelected) {
|
||||
uni.showToast({ title: '请填写具体症状', icon: 'none' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const indexes = (this.multiSelectedIndexes || []).slice().sort((a, b) => a - b);
|
||||
if (!indexes.length) return;
|
||||
const otherText = (this.multiOtherText || '').trim();
|
||||
const labels = indexes
|
||||
.map((i) => {
|
||||
const label = this.currentQuestionAnswers[i];
|
||||
if (label === '其他') return otherText;
|
||||
return label;
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (!labels.length) return;
|
||||
this.answeringLock = true;
|
||||
this.$emit('answer', labels.join('、'));
|
||||
this.multiSelectedIndexes = [];
|
||||
this.multiOtherText = '';
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.consultation-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.consultation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
.header-back {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.iconfont {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-box {
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 24rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
|
||||
.notice-text {
|
||||
display: block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.notice-link {
|
||||
color: #0a84ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.messages-container {
|
||||
padding: 30rpx;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.avatar {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 10rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 70%;
|
||||
margin-left: 16rpx;
|
||||
|
||||
.assistant-label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.5;
|
||||
border-radius: 4rpx 12rpx 12rpx 12rpx;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
&.mine {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
margin-left: 0;
|
||||
margin-right: 16rpx;
|
||||
align-items: flex-end;
|
||||
|
||||
.message-bubble {
|
||||
background: #0a84ff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx 4rpx 12rpx 12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.typing-indicator {
|
||||
.typing-bubble {
|
||||
padding: 18rpx 24rpx;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #999;
|
||||
animation: typing 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 0.7;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-10rpx);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.action-footer {
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
min-height: 88rpx;
|
||||
height: auto;
|
||||
line-height: 1.35;
|
||||
padding: 20rpx 16rpx;
|
||||
border-radius: 44rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
white-space: normal;
|
||||
/* 覆盖微信 button 默认白底/按下灰底,避免按钮组背景错乱 */
|
||||
background-color: #0a84ff !important;
|
||||
color: #fff !important;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.yes-btn {
|
||||
background-color: #0a84ff !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
&.no-btn {
|
||||
background-color: #fff !important;
|
||||
color: #333 !important;
|
||||
border: 2rpx solid #ddd;
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&[disabled] {
|
||||
opacity: 0.45;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-footer {
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
|
||||
.multi-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.multi-other-input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx 24rpx;
|
||||
min-height: 72rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f8fafc;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
}
|
||||
|
||||
.multi-option-btn {
|
||||
min-width: 140rpx;
|
||||
padding: 0 28rpx;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
border-radius: 32rpx;
|
||||
font-size: 26rpx;
|
||||
background-color: rgba(10, 132, 255, 0.08) !important;
|
||||
color: #475569 !important;
|
||||
border: 2rpx solid transparent;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #0a84ff !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
background-color: #0a84ff !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submitting-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.submitting-content {
|
||||
background: #fff;
|
||||
padding: 60rpx 80rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid #0a84ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.submitting-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.agreement-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx 50rpx 20rpx;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agreement-close {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
color: #64748b;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.agreement-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agreement-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
min-height: 200rpx;
|
||||
padding: 30rpx 50rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||