feat(home/chat): 列表骨架屏、聊天室己方头像与协议/医生详情优化

- 新增分包组件 ListSkeleton(doctor/message/product/article/category/zone)
- 首页/消息/资讯首次加载改用骨架屏,刷新不再清空旧数据避免闪空
  · 去掉 home getList、消息 onShow 中的数组清空
  · 三页 pages.json 增加 easycom 与 componentPlaceholder 主包异步引用
- 聊天室己方头像改为读取本地 userinfo.avatarurl(登录后微信头像),
  mycollection 上传头像成功后同步写回 storage
- 修复协议抽屉点不开:page-container 改为 v-if + :show;
  协议名单独可点节点,匹配放宽 name/desc 双向 includes
- 医生气泡头像可点击进入 doctor-detail?readonly=1 只读医生详情,
  隐藏预约挂号区
This commit is contained in:
李琦
2026-07-19 16:08:04 +08:00
parent 2c739324d6
commit 2d2a8abfd4
21 changed files with 1657 additions and 466 deletions

View File

@@ -8,3 +8,5 @@ alwaysApply: true
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 再绑定

View File

@@ -8,3 +8,5 @@ alwaysApply: true
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 再绑定

View File

@@ -7,17 +7,23 @@
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
// 消息通知组件
"^MessageNotification": "@/components/MessageNotification/MessageNotification.vue",
"^FollowUpDrugDrawer$": "@/subPackages/doctor/components/FollowUpDrugDrawer.vue"
"^FollowUpDrugDrawer$": "@/subPackages/doctor/components/FollowUpDrugDrawer.vue",
// 列表骨架屏(分包组件,主包页需配合 componentPlaceholder
"^ListSkeleton$": "@/subPackages/common/components/ListSkeleton.vue"
},
"pages": [
{
"path": "pages/home/home",
"style": {
// "navigationBarTitleText": "custom",
"navigationStyle": "custom"
"navigationStyle": "custom",
// "navigationStyle": "default",
// "navigationBarBackgroundColor": "#298DFF",
// "navigationBarTextStyle": "white"
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
}
},
{
@@ -53,7 +59,11 @@
{
"path": "pages/cate/index",
"style": {
"navigationBarTitleText": "健康资讯"
"navigationBarTitleText": "健康资讯",
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
}
// "needLogin": true
},
@@ -67,7 +77,11 @@
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "消息"
"navigationBarTitleText": "消息",
// 主包异步引用分包 ListSkeleton
"componentPlaceholder": {
"list-skeleton": "view"
}
}
// "needLogin": true
},

View File

@@ -13,7 +13,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,7 +28,9 @@
<view class="goods_right">
<!-- 药品 -->
<view class="second">
<!-- 商品列表 -->
<!-- 首次或切分类且当前无文章时展示骨架有旧文章则保留至新结果 -->
<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>
@@ -35,6 +39,7 @@
</view>
</view>
</view>
</template>
</view>
</view>
</view>
@@ -65,7 +70,18 @@
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) {
@@ -86,14 +102,14 @@
url: '/pages/cate/info-detail?id=' + e
})
},
//点击药品一级分类e
//点击药品一级分类e:不清空 infoList有旧数据则保留到新结果返回
handleCategory1(e, index) {
this.num = e
this.cid = e
this.getArticleList()
},
//获取药品分类
//获取药品分类:成功后再覆盖,失败保留旧分类
getList() {
categoriesList({
method: "post",
@@ -101,12 +117,13 @@
store_id: uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
// console.log(res, 'list');
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
@@ -114,10 +131,15 @@
this.cid = firstId
this.getArticleList()
}
}).finally(() => {
this.categoryInitialLoading = false
})
},
// 文章列表
// 文章列表:请求前不清空;仅当当前无文章可展示时打骨架标记
getArticleList() {
if (!this.infoList || this.infoList.length === 0) {
this.articleFetchingEmpty = true
}
articleList({
method: "get",
data: {
@@ -125,12 +147,12 @@
cid: this.cid
}
}).then((res) => {
// console.log(res, 'list');
if (res.data.code == 0) {
this.infoList = (res.data.result && res.data.result.list) || []
// console.log(res, 'list')
}
}).finally(() => {
this.articleInitialLoading = false
this.articleFetchingEmpty = false
})
}
},
@@ -138,6 +160,7 @@
// this.getList();
},
onShow() {
// 不清空 categoryLists / infoList成功后再替换
this.getList();
}
}

View File

@@ -49,7 +49,10 @@
</view>
<!-- 2.5 金刚区 (Zone) - 支持滑动/换行切换 -->
<view v-if="homeZones.length > 0" class="zone-wrapper" :class="zoneLayoutMode === 'scroll' ? 'zone-scroll-mode' : 'zone-wrap-mode'">
<view v-if="homeZonesInitialLoading" class="zone-wrapper">
<ListSkeleton type="zone" :rows="5" />
</view>
<view v-else-if="homeZones.length > 0" class="zone-wrapper" :class="zoneLayoutMode === 'scroll' ? 'zone-scroll-mode' : 'zone-wrap-mode'">
<view v-for="(item, index) in homeZones" :key="index" class="zone-item" @click="goZoneList(item)">
<view class="zone-icon-box">
<image
@@ -197,6 +200,9 @@
<view class="section-more" @click="goYard(0)">更多 <text class="iconfont icon-jiantou1" style="font-size: 20rpx;"></text></view>
</view>
<!-- 首次加载展示骨架刷新时保留旧列表避免闪空 -->
<ListSkeleton v-if="doctorListInitialLoading" type="doctor" :rows="3" />
<template v-else>
<view class="doctor-card" @click="goLookDoctor(item.id)" v-for="(item,index) in doctorList" :key="item.id">
<view class="doc-main">
<view class="doc-avatar-box">
@@ -232,6 +238,7 @@
</view>
</view>
</view>
</template>
</view>
<!-- 5. 药店模式内容热销好药 -->
@@ -243,6 +250,8 @@
<!-- 优化后的药品列表使用计算属性 displayProductList 解决循环报错 -->
<view class="product-list-container">
<ListSkeleton v-if="productListInitialLoading" type="product" :rows="3" />
<template v-else>
<view class="product-card" @click="goProductDetail(item)" v-for="(item, index) in displayProductList" :key="index">
<view class="prod-img-box">
<image :src="(item.drug && item.drug.image) ? item.drug.image : 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png'" mode="aspectFill" class="prod-img" :class="{ 'prescription-blur': item.drug && item.drug.type == 2 && (item.drug.is_otc === 0 || item.drug.is_otc === '0') }"></image>
@@ -269,6 +278,7 @@
</view>
</view>
</view>
</template>
</view>
<!-- 6. 资质证明 -->
@@ -332,6 +342,10 @@ export default {
doctorList: [],
productList: [],
homeZones: [],
// 首次加载骨架标记:仅第一次请求结束前为 true刷新时保留旧数据不闪空
doctorListInitialLoading: true,
productListInitialLoading: true,
homeZonesInitialLoading: true,
topProductsTitle: '热销商品',
topProductsList: [],
qualifications: [],
@@ -683,7 +697,7 @@ export default {
})
},
getList() {
this.doctorList = [];
// 不再请求前清空 doctorList避免 onShow 刷新时出现「先空后满」
doctorList({
method: "post",
data: { store_id: uni.getStorageSync('store_id') || '11001' }
@@ -699,6 +713,8 @@ export default {
const responseData = isGuest && res.data.result ? res.data.result : res.data.data;
this.doctorList = (responseData.list || []).slice(0, 5)
}
}).finally(() => {
this.doctorListInitialLoading = false
})
},
toChang(e) {
@@ -750,6 +766,9 @@ export default {
if (res.data.code == 0) {
this.productList = res.data.result.records;
}
}).finally(() => {
// top 与 product 任一返回即可结束首次骨架(见 getHomeTopProducts
this._markProductInitialLoaded()
})
},
getHomeTopProducts() {
@@ -761,8 +780,17 @@ export default {
this.topProductsTitle = result.title || '热销商品';
this.topProductsList = result.list || [];
}
}).finally(() => {
this._markProductInitialLoaded()
})
},
// 商品区依赖 topProducts 或 productList两者都结束首次请求后再关骨架
_markProductInitialLoaded() {
this._productFetchDone = (this._productFetchDone || 0) + 1
if (this._productFetchDone >= 2) {
this.productListInitialLoading = false
}
},
getHomeZones() {
getHomeZonesApi({
store_id: uni.getStorageSync('store_id') || '11001',
@@ -770,7 +798,14 @@ export default {
if (res.data.code == 0) {
this.homeZones = res.data.result || res.data.data || [];
}
}).catch(() => { this.homeZones = []; })
}).catch(() => {
// 仅尚无数据时清空,避免刷新失败抹掉已有专区
if (!this.homeZones || this.homeZones.length === 0) {
this.homeZones = [];
}
}).finally(() => {
this.homeZonesInitialLoading = false
})
},
getPlatformQualifications() {
getPlatformQualificationsApi({
@@ -965,12 +1000,12 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
&:active { transform: scale(0.95); }
.zone-icon-box {
width: 100rpx; height: 100rpx;
background: linear-gradient(180deg, #FFFFFF 0%, #F5F9FF 100%);
//background: linear-gradient(180deg, #FFFFFF 0%, #F5F9FF 100%);
border-radius: 36rpx;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(65, 117, 238, 0.08);
//box-shadow: 0 8rpx 24rpx rgba(65, 117, 238, 0.08);
margin-bottom: 12rpx;
.zone-img { width: 56rpx; height: 56rpx; }
.zone-img { width: 100rpx; height: 100rpx; }
}
.zone-text { font-size: 24rpx; color: #555; font-weight: 500; }
}

View File

@@ -12,6 +12,9 @@
</view>
</view>
<!-- 首次加载四条固定入口用骨架避免 num 未定义时闪暂无新消息 -->
<ListSkeleton v-if="noticeInitialLoading" type="message" :rows="4" />
<template v-else>
<!-- 消息列表 -->
<view class="list" @click="goSystem">
<!-- 头像 -->
@@ -91,7 +94,6 @@
<view class="avator">
<view class="img">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/ysxx.png" mode=""></image>
<!-- <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/xx/zhuanzhen.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>
@@ -110,13 +112,18 @@
</view>
</view>
</view>
<!-- 模拟<u-divider>在线问诊</u-divider>效果-->
</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)">
<!-- 头像 -->
@@ -136,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===''">
暂无新消息
@@ -167,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===''">
暂无新消息
@@ -203,7 +208,10 @@ export default {
imChat: [],
onlineConsultationList: [], // 在线咨询列表 (type=1)
onlineReconsultationList: [], // 在线复诊列表 (type=3)
type: ""
type: "",
// 首次加载骨架:刷新时不再清空数据,故仅首次为 true
noticeInitialLoading: true,
chatInitialLoading: true
}
},
onLoad() {
@@ -235,7 +243,6 @@ export default {
type: "all"
}
}).then((res) => {
// console.log(res, 'res');
uni.showToast({
title: '清除成功',
duration: 2000,
@@ -257,7 +264,6 @@ export default {
type: "system"
}
}).then((res) => {
// console.log(res, 'res');
})
},
// 详情
@@ -272,7 +278,6 @@ export default {
type: "order"
}
}).then((res) => {
// console.log(res, 'res');
})
},
goDoctor() {
@@ -286,7 +291,6 @@ export default {
type: "doctor_news"
}
}).then((res) => {
// console.log(res, 'res');
})
},
// 转诊消息
@@ -299,17 +303,8 @@ export default {
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({
@@ -322,8 +317,11 @@ export default {
}
} catch (error) {
console.error('获取消息概览失败:', error)
} finally {
this.noticeInitialLoading = false
}
},
// 拉取 IM 好友会话列表,成功后整份替换,失败不写空
getMyChatFriendsList() {
getChatFriendsListApi().then((res) => {
const allChatList = res.data.result || []
@@ -332,9 +330,13 @@ export default {
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() {
getTransferPrescriptionListApi().then((res) => {
if (res.data && res.data.code == 0) {
@@ -362,15 +364,7 @@ export default {
this.getInfo()
},
onShow() {
this.order = []
this.system = []
this.doctor = []
this.transfer = {
num: 0,
time: ''
}
this.onlineConsultationList = []
this.onlineReconsultationList = []
// 不再清空列表:保留旧 UI接口成功后再覆盖
this.getInfo()
this.getMyChatFriendsList()
this.getTransferList()

View File

@@ -515,10 +515,10 @@ 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)
}

View File

@@ -80,17 +80,21 @@ const isDev = checkDev('dev');
const ENV_URLS = {
// 生产环境
prod: {
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"
// 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",
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"
},
// 测试环境
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: "http://xk.test.saas.api.yii.nailaoyun.cn/member",
xkApi: "http://xk.test.saas.api.nailaoyun.cn/api/mobile",
shop: "https://test.xk.shop.nailaoyun.cn/member1",
im: ""
},
// 本地开发环境
local: {

View File

@@ -59,15 +59,17 @@
>
<view class="message-list-container" id="msglist" :style="{ paddingBottom: (keyboardHeight > 0 ? keyboardHeight + 10 : 0) + 'px' }">
<!-- 协议提示信息 -->
<!-- 协议提示信息协议名单独节点绑定点击避免嵌套 text 吞事件 -->
<view class="notice-box" v-if="showNotice">
<view class="notice-text">
<text v-for="(part, index) in parsedNoticeParts" :key="index">
<block 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="viewAgreement(part.agreementId)">
{{ part.text }}
</text>
</text>
<text
v-else-if="part.type === 'agreement'"
class="notice-link"
@click.stop="viewAgreement(part.agreementId)"
>{{ part.text }}</text>
</block>
</view>
</view>
@@ -103,6 +105,7 @@
@goToOrderMedicine="goToOrderMedicine"
@viewOrder="viewOrder"
@followUpDrugAnswered="onFollowUpDrugAnswered"
@avatarClick="goDoctorDetail(msg)"
/>
</view>
@@ -115,9 +118,10 @@
<view style="height: 180rpx;"></view>
</scroll-view>
<!-- 协议抽屉 -->
<!-- 协议抽屉v-if 控制挂载:show 控制显隐微信 page-container 要求 -->
<page-container
:if="showAgreement"
v-if="showAgreement"
:show="showAgreement"
position="bottom"
round
overlay
@@ -295,6 +299,8 @@ export default {
this.doctorUserInfo = option;
if (option.avatar) this.doctorAvatar = option.avatar;
this.currentUserId = 'user-' + uni.getStorageSync('user_id') || 'user-2512';
// 从登录缓存读取己方微信/用户头像,避免一直用硬编码占位图
this.syncUserAvatar();
let doctorId = option.user_id || '';
if (doctorId.includes('-type-')) doctorId = doctorId.split('-type-')[0];
@@ -352,6 +358,8 @@ export default {
},
onShow() {
// 从资料页返回时再同步一次,避免改过头像后聊天仍显示旧图
this.syncUserAvatar();
// 确保房间ID已设置从ChatManager获取作为备用
if (!this.doctorUserInfo.room_id && typeof ChatManager !== 'undefined') {
const currentRoomId = ChatManager.getCurrentRoomId();
@@ -391,6 +399,16 @@ export default {
methods: {
// === 新增辅助方法 ===
/**
* 从本地 userinfo 同步己方头像(字段为 avatarurl与登录/我的页一致)
* 无头像时回退到与我的页相同的默认图
*/
syncUserAvatar() {
const userinfo = uni.getStorageSync('userinfo') || {};
this.userAvatar = userinfo.avatarurl
|| 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png';
},
isMsgMine(msg) {
return this.getSenderIdWithoutPrefix(msg.sender_user_id) === this.getCurrentUserIdWithoutPrefix();
},
@@ -495,22 +513,20 @@ export default {
});
}
// 查找匹配的协议
// 查找匹配的协议:名称/描述双向 includes仍无匹配则用列表第一条保证可点
const agreementName = match[1];
const agreement = this.agreements.find(ag => ag.name === agreementName || ag.name.includes(agreementName));
const agreement = this.findAgreementByName(agreementName);
if (agreement) {
// 找到匹配的协议,添加为可点击的链接
parts.push({
type: 'agreement',
text: agreementName,
agreementId: agreement.id
});
} else {
// 没找到匹配的协议,作为普通文本显示
parts.push({
type: 'text',
text: match[0] // 《协议名称》
text: match[0]
});
}
@@ -527,6 +543,24 @@ export default {
this.parsedNoticeParts = parts;
},
/**
* 按文案中的协议名匹配协议列表(放宽 name/desc 双向包含)
* 匹配不到但列表非空时回退第一条,避免《》无法点击
*/
findAgreementByName(agreementName) {
const list = this.agreements || [];
if (!list.length) return null;
const name = String(agreementName || '');
const hit = list.find((ag) => {
const n = String(ag.name || '');
const d = String(ag.desc || '');
return n === name
|| n.includes(name)
|| name.includes(n)
|| (d && (d === name || d.includes(name) || name.includes(d)));
});
return hit || list[0];
},
// 查看协议
async viewAgreement(agreementId) {
if (!agreementId) {
@@ -541,6 +575,7 @@ export default {
const agreement = this.agreements.find(ag => ag.id === agreementId);
this.currentAgreementId = agreementId;
this.currentAgreementName = agreement ? agreement.name : '协议详情';
this.agreementContent = '';
this.showAgreement = true;
try {
@@ -569,6 +604,26 @@ export default {
this.showAgreement = false;
},
/**
* 点击气泡医生头像进入医生详情(只读,不展示挂号)
* 己方/助理头像不跳转
*/
goDoctorDetail(msg) {
if (!msg || this.isMsgMine(msg)) return;
const senderId = msg.sender_user_id || msg.senderId || '';
if (senderId === 'doctor_assistant') return;
const cleanId = String(this.doctorId || '')
.replace(/^doctor-/, '')
.replace(/-type-.*$/, '');
if (!cleanId) {
uni.showToast({ title: '医生信息缺失', icon: 'none' });
return;
}
uni.navigateTo({
url: `/subPackages/doctor/doctor-detail?id=${cleanId}&readonly=1`
});
},
// === 音频播放逻辑 ===
playAudio(msg) {
const audioUrl = getParsedContent(msg.message_type, msg.message_content);

View File

@@ -1,7 +1,7 @@
<template>
<view class="bubble-container" :class="{ 'mine': isMine, 'other': !isMine }">
<!-- 头像 -->
<image class="avatar" :src="avatar" mode="aspectFill"></image>
<!-- 头像非己方可点进医生详情 -->
<image class="avatar" :src="avatar" mode="aspectFill" @click.stop="onAvatarClick"></image>
<!-- 消息内容包裹 -->
<view class="content-wrapper">
@@ -146,13 +146,22 @@
<text class="block-label">主诉</text>
<text class="block-content">{{ parsedContent.chief_complaint }}</text>
</view>
<view class="info-row" v-if="registerCardDrugNamesLine">
<text class="label">所选药品</text>
<text class="value">{{ registerCardDrugNamesLine }}</text>
<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 class="info-row" v-if="parsedContent.number != null && parsedContent.number !== ''">
<text class="label">数量</text>
<text class="value"> {{ parsedContent.number }} </text>
</view>
</view>
<view class="card-footer"><button class="action-btn" @click="$emit('viewRegister', parsedContent.id)">查看详情</button></view>
@@ -314,17 +323,38 @@ export default {
}
return message_content;
},
/** 挂号卡片:多选西药名称串联,无则单药 drug.name */
registerCardDrugNamesLine() {
if (this.msg.message_type !== 10) return '';
/**
* 预约挂号卡片药品行:药名 + 规格 + 数量(与医生端一致,去掉底部「各 X 盒」)
*/
registerCardDrugRows() {
if (this.msg.message_type !== 10) return [];
const pc = this.parsedContent;
if (!pc || typeof pc !== 'object') return '';
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) => d && d.name).filter(Boolean).join('、');
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 pc.drug.name;
return '';
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;
@@ -336,6 +366,13 @@ export default {
}
},
methods: {
/**
* 非己方头像点击:通知父页跳转医生详情;己方头像不响应
*/
onAvatarClick() {
if (this.isMine) return;
this.$emit('avatarClick');
},
// 计算语音条宽度 (Min: 100rpx, Max: 350rpx, Unit: 10rpx/sec)
getAudioWidth(duration) {
const min = 120;
@@ -609,6 +646,40 @@ $success-color: #059669;
.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;

View File

@@ -0,0 +1,275 @@
<template>
<!--
列表骨架屏 type 模拟医生卡/消息行/商品卡/资讯卡/分类侧栏
放在分包以控制主包体积主包页通过 componentPlaceholder 异步引用
-->
<view class="list-skeleton" :class="'list-skeleton--' + type">
<view
v-for="i in rowCount"
:key="i"
class="sk-row"
:class="'sk-row--' + type"
>
<!-- 医生推荐卡片 -->
<template v-if="type === 'doctor'">
<view class="sk-avatar sk-shimmer"></view>
<view class="sk-body">
<view class="sk-line sk-line--lg sk-shimmer"></view>
<view class="sk-line sk-line--md sk-shimmer"></view>
<view class="sk-line sk-line--sm sk-shimmer"></view>
</view>
</template>
<!-- 消息列表行 -->
<template v-else-if="type === 'message'">
<view class="sk-avatar sk-avatar--round sk-shimmer"></view>
<view class="sk-body">
<view class="sk-line-row">
<view class="sk-line sk-line--md sk-shimmer"></view>
<view class="sk-line sk-line--xs sk-shimmer"></view>
</view>
<view class="sk-line sk-line--sm sk-shimmer"></view>
</view>
</template>
<!-- 商品卡片 -->
<template v-else-if="type === 'product'">
<view class="sk-prod-img sk-shimmer"></view>
<view class="sk-body">
<view class="sk-line sk-line--lg sk-shimmer"></view>
<view class="sk-line sk-line--sm sk-shimmer"></view>
<view class="sk-line sk-line--md sk-shimmer"></view>
</view>
</template>
<!-- 资讯文章卡片 -->
<template v-else-if="type === 'article'">
<view class="sk-cover sk-shimmer"></view>
<view class="sk-cover-title sk-shimmer"></view>
</template>
<!-- 资讯左侧分类 -->
<template v-else-if="type === 'category'">
<view class="sk-cate-item sk-shimmer"></view>
</template>
<!-- 金刚区横滑块 -->
<template v-else-if="type === 'zone'">
<view class="sk-zone-icon sk-shimmer"></view>
<view class="sk-line sk-line--xs sk-shimmer"></view>
</template>
</view>
</view>
</template>
<script>
export default {
name: 'ListSkeleton',
props: {
// 骨架样式类型
type: {
type: String,
default: 'message',
validator(v) {
return ['doctor', 'message', 'product', 'article', 'category', 'zone'].indexOf(v) !== -1
}
},
// 行数
rows: {
type: [Number, String],
default: 3
}
},
computed: {
rowCount() {
const n = Number(this.rows)
return n > 0 ? n : 3
}
}
}
</script>
<style lang="scss" scoped>
.list-skeleton {
width: 100%;
}
.sk-shimmer {
background: linear-gradient(90deg, #ebebeb 25%, #f5f5f5 50%, #ebebeb 75%);
background-size: 400% 100%;
animation: sk-shimmer 1.4s ease infinite;
}
@keyframes sk-shimmer {
0% {
background-position: 100% 0;
}
100% {
background-position: 0 0;
}
}
.sk-row--doctor {
display: flex;
align-items: flex-start;
margin: 0 24rpx 20rpx;
padding: 28rpx;
background: #fff;
border-radius: 24rpx;
.sk-avatar {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
flex-shrink: 0;
margin-right: 20rpx;
}
.sk-body {
flex: 1;
}
}
.sk-row--message {
display: flex;
align-items: center;
padding: 32rpx 38rpx;
background: #fff;
.sk-avatar {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
flex-shrink: 0;
margin-right: 24rpx;
}
.sk-body {
flex: 1;
}
.sk-line-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
}
}
.sk-row--product {
display: flex;
margin: 0 24rpx 20rpx;
padding: 20rpx;
background: #fff;
border-radius: 16rpx;
.sk-prod-img {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.sk-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
.sk-row--article {
position: relative;
width: 500rpx;
height: 246rpx;
margin: 10rpx auto;
border-radius: 12rpx;
overflow: hidden;
.sk-cover {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.sk-cover-title {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 92rpx;
opacity: 0.5;
}
}
.sk-row--category {
width: 184rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
.sk-cate-item {
width: 100rpx;
height: 28rpx;
border-radius: 6rpx;
}
}
.list-skeleton--zone {
display: flex;
flex-wrap: nowrap;
padding: 16rpx 24rpx;
overflow: hidden;
}
.sk-row--zone {
display: flex;
flex-direction: column;
align-items: center;
width: 140rpx;
flex-shrink: 0;
margin-right: 16rpx;
.sk-zone-icon {
width: 88rpx;
height: 88rpx;
border-radius: 20rpx;
margin-bottom: 12rpx;
}
.sk-line--xs {
width: 72rpx;
}
}
.sk-line {
height: 24rpx;
border-radius: 6rpx;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.sk-line--lg {
width: 70%;
}
.sk-line--md {
width: 50%;
}
.sk-line--sm {
width: 90%;
}
.sk-line--xs {
width: 80rpx;
height: 20rpx;
margin-bottom: 0;
}
.sk-line-row .sk-line--md {
width: 40%;
margin-bottom: 0;
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -36,8 +36,8 @@
</view>
</view>
<!-- 到店医生 预约挂号 -->
<view class="appoin">
<!-- 到店医生 预约挂号聊天室只读进入时不展示挂号入口 -->
<view class="appoin" v-if="!readonly">
<view class="title">预约挂号</view>
<view class="list">
<view class="yard">
@@ -121,10 +121,13 @@
registList: [],
peopleList: [],
rType: '0', // 挂号类型
// 聊天室等场景只读查看:隐藏挂号按钮区
readonly: false,
}
},
onLoad(e) {
console.log(e, 'e');
this.readonly = e.readonly === '1' || e.readonly === 1;
// 允许未登录访问,移除登录跳转
// 如果是guest模式强制设置store_id为11001
if (isGuestMode()) {
@@ -148,7 +151,10 @@
this.doctorID = uni.getStorageSync('doctors_id')
this.getDocdetail();
this.getUser();
// 只读模式不拉号源,避免无用请求
if (!this.readonly) {
this.getRegist();
}
console.log(this.doctorID, '6666');
} else {
@@ -159,7 +165,9 @@
if (isGuestMode()) {
uni.setStorageSync('store_id', '11001');
this.getDocdetail();
if (!this.readonly) {
this.getRegist();
}
} else {
const id = e.sts_id || uni.getStorageSync('store_id')
if (id!='undefined' && id) {
@@ -179,6 +187,8 @@
},
// 挂号
goAdd() {
// 只读模式不允许挂号
if (this.readonly) return;
// 如果未登录,保存跳转信息并跳转到登录页
if (!isLoggedIn()) {
const storeId = uni.getStorageSync('store_id') || '11001';
@@ -345,8 +355,11 @@
mounted() {
this.getDocdetail();
this.getUser();
// 只读模式不拉号源/就诊人相关挂号数据
if (!this.readonly) {
this.getRegist();
this.getPeople();
}
if (!this.doctorList && doctorID) {
this.getDocdetail();

View File

@@ -132,6 +132,10 @@
<!-- 按钮区完全保留你原有的判断逻辑 -->
<view class="choose_btn">
<view class="order_info active" v-if="item.is_pay==0 && item.status==0 && item.cancel_status==0"
@tap.stop="goCancel(item.id)">
取消
</view>
<view class="order_info active" v-if="item.status==1 && item.is_pay==1 && item.forbiddenRefund==0"
@tap.stop="toCancel(item.id,item.status,refundType)">
退款
@@ -164,10 +168,6 @@
查看物流
</view>
<view class="order_info active" v-if="item.is_pay==0 && item.status==0 && item.cancel_status==0"
@tap.stop="goCancel(item.id)">
取消
</view>
</view>
</view>
<u-loadmore :status="loadMoreStatus" v-if="orderList.length!=0" margin-top="30" margin-bottom="30" />

View File

@@ -91,10 +91,26 @@
this.mobile = res.data.data.user.mobile
this.idCard = res.data.data.user.idcard
this._securityBaseline = { name: this.name }
// 写回本地 userinfo保证聊天室等页面能读到最新头像
this.syncUserinfoStorage(res.data.data.user)
}
})
},
/**
* 将最新用户资料合并进本地 userinfo尤其是 avatarurl
* 登录时存的是整份 user此处只覆盖接口返回的字段避免丢掉其它缓存键
*/
syncUserinfoStorage(user) {
if (!user) return
const cached = uni.getStorageSync('userinfo') || {}
uni.setStorageSync('userinfo', {
...cached,
...user,
avatarurl: user.avatarurl || cached.avatarurl || ''
})
},
// 点击头像触发click事件 changeUserPhoto
async changeUserPhoto() {
console.log('进入更新头像的功能')
@@ -198,6 +214,13 @@
icon: "success",
title: '保存成功'
})
// 先写回 storage再拉详情避免聊天页 onShow 读到旧头像
const cached = uni.getStorageSync('userinfo') || {}
uni.setStorageSync('userinfo', {
...cached,
avatarurl: req.data.url
})
this.avatar = req.data.url
this.getInfo()
}
})

View File

@@ -17,7 +17,7 @@
<image :src="data.store['offical_seal'] ||''" mode=""></image>
<view class="record_yard">
<view class="yard">
{{data.store.name}}
{{ storeDisplayName }}
</view>
<view class="state">
处方笺
@@ -359,8 +359,16 @@
getUseWay
} from '../../request/api/api'
import { checkImageUrl } from '../../utils/utils'
import { formatStoreNameWithHu } from '../../utils/formatStoreNameWithHu.js'
export default {
computed: {
/** 在线处方诊所名加(互) */
storeDisplayName() {
const store = this.data && this.data.store ? this.data.store : {}
return formatStoreNameWithHu(store.name, this.data && this.data.is_online)
},
},
data() {
return {
statusName: {

View File

@@ -2,7 +2,7 @@
<view class="container safe-area-inset-bottom">
<MessageNotification />
<!-- 收货地址 -->
<view class="head" v-if="logisticsList.express_name!=''">
<view class="head" v-if="logisticsList.express_name">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/mr_tx.png" mode=""></image>
<view class="head_info">
<view class="name">{{logisticsList.express_name}} {{logisticsList.express_mobile_tm || logisticsList.express_mobile}}</view>
@@ -10,73 +10,140 @@
</view>
</view>
<!-- 订单信息 -->
<view class="infos" v-if="express!=''">
<!-- 多包裹 Tab warehouse_name 时只显示包裹序号 -->
<scroll-view v-if="packages.length > 1" class="pkg-tabs" scroll-x>
<view
v-for="(pkg, idx) in packages"
:key="idx"
class="pkg-tab"
:class="{ active: activePkg === idx }"
@click="switchPkg(idx)"
>
包裹{{ pkg.package_no || idx + 1 }}
<text v-if="pkg.warehouse_name" class="pkg-sub">{{ pkg.warehouse_name }}</text>
</view>
</scroll-view>
<!-- 多包裹左右滑动切换单包直接展示 -->
<swiper
v-if="packages.length > 1"
class="pkg-swiper"
:current="activePkg"
:style="{ height: swiperHeight + 'px' }"
@change="onSwiperChange"
>
<swiper-item v-for="(pkg, idx) in packages" :key="idx">
<view class="pkg-panel" :id="'pkg-panel-' + idx">
<view class="infos" v-if="pkgExpress(pkg).express_no">
<view class="_info">
<text>快递单号</text>
<text class="info">{{express.express_no}}</text>
<text class="copy" @click="copy(express.express_no)">复制</text>
<text class="info">{{ pkgExpress(pkg).express_no }}</text>
<text class="copy" @click="copy(pkgExpress(pkg).express_no)">复制</text>
</view>
<view class="_info">
<text>配送方式</text>
<text class="info">{{express.express_company_name}}</text>
<text class="info">{{ pkgExpress(pkg).express_company_name }}</text>
</view>
</view>
<view class="infos" v-else>
<view class="_info">
<text>发货状态</text>
<text class="info">{{ Number(pkg.is_send) === 1 ? '已发货' : '待发货' }}</text>
</view>
</view>
<!-- 物流追踪 -->
<view class="card">
<view class="title">
物流追踪
</view>
<!-- <view class="empty" v-if="newArr.length==0">
<image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/empty/orders.png" mode="aspectFit"></image>
<text>药房正在备药中暂时还没有物流信息哦~</text>
</view> -->
<view class="line" v-if="newArr.length>0">
<u-time-line v-for="(item,index) in newArr" :key="index">
<u-time-line-item nodeTop="2">
<view class="title">物流追踪</view>
<view class="line" v-if="pkgTracks(pkg).length > 0">
<u-time-line>
<u-time-line-item
v-for="(item, index) in pkgTracks(pkg)"
:key="index"
nodeTop="2"
>
<template v-slot:node>
<view class="track-node" :class="{ 'is-active': index === 0 }" />
</template>
<template v-slot:content>
<view>
<view class="u-order-title">{{item.status}}</view>
<view class="u-order-desc">
{{item.detail}}
<view class="track-content" :class="{ 'is-active': index === 0 }">
<view class="u-order-title">
{{ item.status }}
<text v-if="showOngoing(item, index)" class="ongoing">进行中</text>
</view>
<view class="u-order-time">{{item.detail_at}}</view>
<view class="u-order-desc">{{ item.detail }}</view>
<view class="u-order-time">{{ item.detail_at }}</view>
</view>
</template>
</u-time-line-item>
</u-time-line>
</view>
<view v-else class="empty-tip">暂无物流轨迹</view>
</view>
</view>
</swiper-item>
</swiper>
<template v-else>
<view class="infos" v-if="currentExpress && currentExpress.express_no">
<view class="_info">
<text>快递单号</text>
<text class="info">{{ currentExpress.express_no }}</text>
<text class="copy" @click="copy(currentExpress.express_no)">复制</text>
</view>
<view class="_info">
<text>配送方式</text>
<text class="info">{{ currentExpress.express_company_name }}</text>
</view>
</view>
<view class="infos" v-else-if="packages.length">
<view class="_info">
<text>发货状态</text>
<text class="info">{{ currentPkg && Number(currentPkg.is_send) === 1 ? '已发货' : '待发货' }}</text>
</view>
</view>
<view class="card">
<view class="title">物流追踪</view>
<view class="line" v-if="newArr.length > 0">
<u-time-line>
<u-time-line-item
v-for="(item, index) in newArr"
:key="index"
nodeTop="2"
>
<template v-slot:node>
<view class="track-node" :class="{ 'is-active': index === 0 }" />
</template>
<template v-slot:content>
<view class="track-content" :class="{ 'is-active': index === 0 }">
<view class="u-order-title">
{{ item.status }}
<text v-if="showOngoing(item, index)" class="ongoing">进行中</text>
</view>
<view class="u-order-desc">{{ item.detail }}</view>
<view class="u-order-time">{{ item.detail_at }}</view>
</view>
</template>
</u-time-line-item>
</u-time-line>
</view>
<view v-else class="empty-tip">暂无物流轨迹</view>
</view>
</template>
<!-- 底部 确认收货 -->
<view class="footer" v-if="newArr.length>0">
<!-- 底部确认收货 -->
<view class="footer" v-if="canConfirm">
<view class="to_pay" @click="toaffirm">确认收货</view>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
// getLogistics
import {
getLogistics,
confirmAccept
} from '../../request/api/api';
export default {
data() {
return {
isshow: true,
show: false,
content: '是否确认退款',
logisticsList: {},
order_no: '',
express: [],
order_id: '',
newArr: [{
const FALLBACK_TRACKS = [{
detail: "药品已打包,等待快递揽件",
detail_at: "",
status: "已发货",
@@ -86,34 +153,107 @@
detail_at: "",
status: "备药",
}
]
];
export default {
data() {
return {
logisticsList: {},
express: {},
packages: [],
activePkg: 0,
order_id: '',
newArr: [],
canConfirm: false,
swiperHeight: 480,
}
},
computed: {
currentPkg() {
return this.packages[this.activePkg] || null
},
currentExpress() {
const pkg = this.currentPkg
if (pkg && pkg.express) return pkg.express
return this.express
}
},
onLoad(op) {
this.order_id = op.id || '45'
this.order_id = op.id || ''
this.getLogisticsList()
},
methods: {
// 支付
/** 最新一条非签收时才显示「进行中」 */
showOngoing(item, index) {
if (index !== 0) return false
const status = String((item && item.status) || '')
return status !== '签收'
},
pkgExpress(pkg) {
return (pkg && pkg.express) || {}
},
/** 按包裹组装轨迹(读 packages[i].express.detail */
pkgTracks(pkg) {
const express = this.pkgExpress(pkg)
const detail = Array.isArray(express.detail) ? express.detail : []
if (detail.length) return detail
if (pkg && Number(pkg.is_send) === 1) return FALLBACK_TRACKS
return [{
detail: '药房正在备药中',
detail_at: '',
status: '备药',
}]
},
/** Tab 切换包裹,驱动 swiper */
switchPkg(idx) {
this.activePkg = idx
this.applyTracksFromPkg()
this.$nextTick(() => this.updateSwiperHeight())
},
/** swiper 左右滑同步 activePkg */
onSwiperChange(e) {
const idx = Number(e.detail.current || 0)
if (idx === this.activePkg) return
this.activePkg = idx
this.applyTracksFromPkg()
this.$nextTick(() => this.updateSwiperHeight())
},
/** 测量当前面板高度,避免 swiper 裁切时间轴 */
updateSwiperHeight() {
if (this.packages.length <= 1) return
const q = uni.createSelectorQuery().in(this)
q.select('#pkg-panel-' + this.activePkg).boundingClientRect((rect) => {
if (rect && rect.height) {
this.swiperHeight = Math.ceil(rect.height)
}
}).exec()
},
/**
* 根据当前包裹组装时间轴(单包模式用)
* 接口 detail 按时间倒序,第 0 条即最新
*/
applyTracksFromPkg() {
const pkg = this.currentPkg
this.newArr = this.pkgTracks(pkg)
const express = this.currentExpress
const hasTrack = Array.isArray(express.detail) && express.detail.length > 0
this.canConfirm = hasTrack || (pkg && Number(pkg.is_send) === 1)
},
toaffirm() {
this.postConfirm()
uni.switchTab({
url: '/pages/mine/mine'
})
},
// 点击复制
copy(value) {
//提示模板
uni.showModal({
content: value, //模板中提示的内容
content: value,
confirmText: '复制内容',
success: () => {
uni.setClipboardData({
data: value, //要被复制的内容
success: () => { //复制成功的回调函数
uni.showToast({ //提示
data: value,
success: () => {
uni.showToast({
title: '复制成功',
icon: 'success'
})
@@ -122,28 +262,31 @@
}
});
},
//物流详情请求
getLogisticsList() {
getLogistics({
method: "get",
method: "post",
data: {
store_id: uni.getStorageSync('store_id') || '11001',
order_id: this.order_id,
}
}).then((res) => {
// console.log(res, 'list');
if (res.data.errcode == 0) {
console.log(res.data.data);
this.logisticsList = res.data.data.address
this.express = res.data.data.express
let arr = this.express.detail.filter((item, index) => {
return index != 0
})
this.newArr = [...arr,...this.newArr ]
const data = res.data.data || {}
this.logisticsList = data.address || {}
this.express = data.express || {}
this.packages = Array.isArray(data.packages) ? data.packages : []
if (!this.packages.length && this.express && this.express.express_no) {
this.packages = [{
package_no: 1,
warehouse_name: '',
is_send: 1,
express: this.express,
}]
}
this.activePkg = 0
this.applyTracksFromPkg()
this.$nextTick(() => this.updateSwiperHeight())
}
})
},
postConfirm() {
@@ -154,11 +297,9 @@
order_id: this.order_id,
}
}).then((res) => {
// console.log(res, 'list');
if (res.data.errcode == 0) {
console.log(res.data.data);
}
})
},
}
@@ -166,25 +307,63 @@
</script>
<style lang="scss" scoped>
$primary: #4175EE;
$primary-soft: rgba(65, 117, 238, 0.12);
$primary-glow: rgba(65, 117, 238, 0.25);
.container {
width: 750rpx;
max-height: 100%;
min-height: 100vh;
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
background: #F5F5F5;
.empty {
width: 440rpx;
height: 393rpx;
margin: 200rpx auto;
.empty-tip {
text-align: center;
image {
width: 100%;
height: 100%;
z-index: 10;
margin-bottom: 10rpx;
color: #999;
padding: 40rpx 0;
font-size: 26rpx;
}
.pkg-tabs {
white-space: nowrap;
width: 710rpx;
margin: 0 auto 16rpx;
background: #fff;
padding: 16rpx 20rpx;
box-sizing: border-box;
}
.pkg-tab {
display: inline-flex;
flex-direction: column;
align-items: center;
padding: 12rpx 28rpx;
margin-right: 16rpx;
border-radius: 12rpx;
background: #f5f5f5;
font-size: 26rpx;
color: #666;
&.active {
background: $primary-soft;
color: $primary;
font-weight: 600;
}
.pkg-sub {
font-size: 20rpx;
margin-top: 4rpx;
opacity: 0.8;
}
}
.pkg-swiper {
width: 750rpx;
}
.pkg-panel {
padding-bottom: 20rpx;
}
.head {
@@ -217,157 +396,133 @@
.choose_area {
font-size: 24rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #232323;
}
}
}
.card {
width: 710rpx;
border-radius: 16rpx;
margin: 10rpx auto 180rpx;
padding: 30rpx;
background: #FFFFFF;
.title {
width: 640rpx;
padding-bottom: 22rpx;
font-size: 28rpx;
font-family: PingFang SC-Bold, PingFang SC;
font-weight: bold;
color: #232323;
border-bottom: 2rpx solid #DDDDDD;
}
.line {
margin-top: 20rpx;
margin-bottom: 18rpx;
.u-time-line-item {
display: flex;
}
.u-node {
width: 44rpx;
height: 44rpx;
border-radius: 100rpx;
display: flex;
justify-content: center;
align-items: center;
background: #d0d0d0;
}
.u-order-title {
color: #333333;
font-weight: bold;
font-size: 32rpx;
}
.u-order-desc {
color: rgb(150, 150, 150);
font-size: 28rpx;
margin-bottom: 6rpx;
}
.u-order-time {
color: rgb(200, 200, 200);
font-size: 26rpx;
color: #666;
}
}
}
.infos {
width: 710rpx;
margin: 20rpx auto 20rpx;
padding: 20rpx 32rpx;
border-radius: 16rpx;
background: #FFFFFF;
margin: 0 auto 20rpx;
background: #fff;
padding: 10rpx 30rpx;
._info {
display: flex;
align-items: center;
margin: 20rpx 0;
text {
padding: 20rpx 0;
font-size: 28rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
}
color: #666;
.info {
font-size: 28rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
flex: 1;
margin-left: 30rpx;
color: #232323;
margin-left: 60rpx;
}
.copy {
display: inline-block;
border: 2rpx solid #1777FF;
width: 88rpx;
height: 48rpx;
color: $primary;
margin-left: 20rpx;
}
}
}
.card {
width: 710rpx;
margin: 0 auto 20rpx;
background: #fff;
padding: 30rpx;
.title {
font-size: 32rpx;
font-weight: bold;
color: #232323;
margin-bottom: 20rpx;
}
.line {
.track-node {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #d9d9d9;
margin-left: 6rpx;
&.is-active {
width: 20rpx;
height: 20rpx;
margin-left: 4rpx;
background: $primary;
box-shadow: 0 0 0 6rpx $primary-glow;
}
}
.track-content {
.u-order-title {
font-size: 28rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
color: #1777FF;
line-height: 44rpx;
text-align: center;
margin-left: 60rpx;
font-weight: 500;
color: #999;
display: flex;
align-items: center;
gap: 12rpx;
}
.u-order-desc {
font-size: 26rpx;
color: #bbb;
margin: 10rpx 0;
}
.u-order-time {
font-size: 24rpx;
color: #ccc;
}
&.is-active {
.u-order-title {
font-weight: 700;
color: $primary;
}
.u-order-desc {
color: #666;
}
.u-order-time {
color: #999;
}
.ongoing {
font-size: 22rpx;
font-weight: 500;
color: #fff;
background: $primary;
padding: 2rpx 12rpx;
border-radius: 8rpx;
}
}
}
}
}
.footer {
width: 750rpx;
height: 100rpx;
background: #fff;
position: fixed;
bottom: 0;
left: 0;
bottom: env(safe-area-inset-bottom);
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 24rpx;
font-size: 28rpx;
font-family: PingFang SC-Regular, PingFang SC;
font-weight: 400;
.to_cancle {
width: 196rpx;
height: 64rpx;
text-align: center;
line-height: 64rpx;
color: #1777FF;
background: #E7F1FF;
border-radius: 50rpx 50rpx 50rpx 50rpx;
margin-right: 20rpx;
}
background: #fff;
padding: 20rpx 40rpx calc(20rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
.to_pay {
width: 196rpx;
height: 64rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
line-height: 64rpx;
background: $primary;
color: #fff;
background: #1777FF;
border-radius: 50rpx 50rpx 50rpx 50rpx;
}
.to_agin {
width: 196rpx;
height: 64rpx;
text-align: center;
line-height: 64rpx;
border-radius: 50rpx 50rpx 50rpx 50rpx;
border: 2rpx solid #999999;
color: #999999;
margin-left: 20rpx;
border-radius: 40rpx;
font-size: 30rpx;
}
}
}
</style>

View File

@@ -84,13 +84,11 @@ export function handleSecurityError(err) {
function assertCheckOk(res) {
if (res && res.code === 0) {
if (res && res.data.code === 0) {
return true;
}
throw { msg: (res && res.message) || '内容含有违规信息,请修改后重试' };
throw { msg: (res && res.data.message) || '内容含有违规信息,请修改后重试' };
}

View File

@@ -0,0 +1,12 @@
/**
* 在线处方诊所名追加「is_online 为 1/2/3线下 0 不加
*/
export function formatStoreNameWithHu(storeName, isOnline) {
const name = String(storeName || '').trim()
if (!name) return ''
const online = Number(isOnline)
if (online === 1 || online === 2 || online === 3) {
return name.endsWith('(互)') ? name : `${name}(互)`
}
return name
}

View File

@@ -6,8 +6,8 @@ export function checkDev(key = '') {
// 判断某些模块是否开启
switch (key) {
case 'dev':
// return false;
return true;
return false;
// return true;
case 'open-im':
// return false;
return true;

View File

@@ -331,5 +331,6 @@ export class WebSocketManager {
// 全局WebSocket实例
// export const webSocketManager = new WebSocketManager('ws://127.0.0.1:12080/ws');
const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws':'wss://api.ws.g.xiaokang88.com/ws';
// const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws':'wss://api.ws.g.xiaokang88.com/ws';
const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws':'wss://xk.ws.nailaoyun.cn/ws';
export const webSocketManager = new WebSocketManager(wsUrl);