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:
@@ -17,6 +17,7 @@
|
||||
"style": {
|
||||
// "navigationBarTitleText": "custom",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true,
|
||||
// "navigationStyle": "default",
|
||||
// "navigationBarBackgroundColor": "#298DFF",
|
||||
// "navigationBarTextStyle": "white"
|
||||
@@ -60,6 +61,7 @@
|
||||
"path": "pages/cate/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康资讯",
|
||||
"enablePullDownRefresh": true,
|
||||
// 主包异步引用分包 ListSkeleton
|
||||
"componentPlaceholder": {
|
||||
"list-skeleton": "view"
|
||||
@@ -78,6 +80,7 @@
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"enablePullDownRefresh": true,
|
||||
// 主包异步引用分包 ListSkeleton
|
||||
"componentPlaceholder": {
|
||||
"list-skeleton": "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>
|
||||
|
||||
@@ -88,9 +89,18 @@
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 搜索
|
||||
// 搜索:按当前分类 + 标题关键词拉文章(不再误调分类接口)
|
||||
toSearch() {
|
||||
this.getList()
|
||||
this.infoList = []
|
||||
this.articleFetchingEmpty = true
|
||||
this.getArticleList()
|
||||
},
|
||||
// 清空搜索词后重新拉当前分类文章
|
||||
clearSearch() {
|
||||
this.keyword = ''
|
||||
this.infoList = []
|
||||
this.articleFetchingEmpty = true
|
||||
this.getArticleList()
|
||||
},
|
||||
/*切换需要*/
|
||||
changeStyle(id) {
|
||||
@@ -102,16 +112,17 @@
|
||||
url: '/pages/cate/info-detail?id=' + e
|
||||
})
|
||||
},
|
||||
//点击药品一级分类e:不清空 infoList,有旧数据则保留到新结果返回
|
||||
// 切分类:立即清空文章出骨架
|
||||
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',
|
||||
@@ -129,23 +140,28 @@
|
||||
const firstId = list[0].id
|
||||
this.num = firstId
|
||||
this.cid = firstId
|
||||
this.getArticleList()
|
||||
return this.getArticleList()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.categoryInitialLoading = false
|
||||
})
|
||||
},
|
||||
// 文章列表:请求前不清空;仅当当前无文章可展示时打骨架标记
|
||||
// 文章列表:可选 keyword 标题搜索
|
||||
getArticleList() {
|
||||
if (!this.infoList || this.infoList.length === 0) {
|
||||
this.articleFetchingEmpty = true
|
||||
}
|
||||
articleList({
|
||||
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) => {
|
||||
if (res.data.code == 0) {
|
||||
this.infoList = (res.data.result && res.data.result.list) || []
|
||||
@@ -154,6 +170,19 @@
|
||||
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()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -86,14 +86,20 @@
|
||||
</view>
|
||||
|
||||
<!-- 药店模式:单列铺满 -->
|
||||
<view v-if="storeType === '1' || storeType === 1" class="nav-pharmacy-grid">
|
||||
<view v-if="storeType === '1' || storeType === 1" class="nav-pharmacy-grid book-medication-appointment-card" @click="golist(3)">
|
||||
<view class="nav-card" @click="golist(3)">
|
||||
<view class="nav-content">
|
||||
<view class="nav-title" style="color:#298DFF">预约购药</view>
|
||||
<view class="nav-desc">正品保障 · 种类齐全 · 快速配送</view>
|
||||
</view>
|
||||
<image src="https://img.icons8.com/3d-fluency/94/delivery-scooter.png" class="nav-icon-img" style="width:140rpx; height:140rpx; right:20rpx; bottom:10rpx;" mode="aspectFit"></image>
|
||||
<!-- <image src="https://img.icons8.com/3d-fluency/94/delivery-scooter.png" class="nav-icon-img" style="width:140rpx; height:140rpx; right:20rpx; bottom:10rpx;" mode="aspectFit"></image>-->
|
||||
<image
|
||||
src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20260721/91af7f9d-3a99-45bb-aa3b-b0edb39d464c_1784599019.png"
|
||||
class="nav-icon-img-256"
|
||||
style="width:320rpx; height:220rpx; right:20rpx; bottom:0rpx;"
|
||||
></image>
|
||||
</view>
|
||||
<!-- <image src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20260721/0d1b63f3-907b-4045-981a-5c298c10cac7_1784599580.jpg" style="width: 100%;" mode="aspectFit">-->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -200,7 +206,7 @@
|
||||
<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">
|
||||
@@ -697,8 +703,8 @@ export default {
|
||||
})
|
||||
},
|
||||
getList() {
|
||||
// 不再请求前清空 doctorList,避免 onShow 刷新时出现「先空后满」
|
||||
doctorList({
|
||||
// 返回 Promise 供下拉刷新 Promise.all 等待
|
||||
return doctorList({
|
||||
method: "post",
|
||||
data: { store_id: uni.getStorageSync('store_id') || '11001' }
|
||||
}).then((res) => {
|
||||
@@ -726,7 +732,7 @@ export default {
|
||||
})
|
||||
},
|
||||
getBannered() {
|
||||
getBanner({
|
||||
return getBanner({
|
||||
method: "post",
|
||||
data: { store_id: uni.getStorageSync('store_id') || '11001' }
|
||||
}).then((res) => {
|
||||
@@ -758,7 +764,7 @@ export default {
|
||||
}
|
||||
},
|
||||
getProductList() {
|
||||
getStoreDrugListBySalespersonApi({
|
||||
return getStoreDrugListBySalespersonApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
page: 1,
|
||||
limit: 3
|
||||
@@ -772,7 +778,7 @@ export default {
|
||||
})
|
||||
},
|
||||
getHomeTopProducts() {
|
||||
getHomeTopProductsApi({
|
||||
return getHomeTopProductsApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0 && res.data.result) {
|
||||
@@ -792,7 +798,7 @@ export default {
|
||||
}
|
||||
},
|
||||
getHomeZones() {
|
||||
getHomeZonesApi({
|
||||
return getHomeZonesApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
@@ -807,6 +813,32 @@ export default {
|
||||
this.homeZonesInitialLoading = false
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 下拉刷新:清空金刚区与卡片,重新走骨架后再拉数
|
||||
* 为什么:用户期望刷新时看到加载态,而不是短暂保留旧数据
|
||||
*/
|
||||
refreshHomeContent() {
|
||||
this.homeZones = [];
|
||||
this.homeZonesInitialLoading = true;
|
||||
this.doctorList = [];
|
||||
this.doctorListInitialLoading = true;
|
||||
this.productList = [];
|
||||
this.topProductsList = [];
|
||||
this.productListInitialLoading = true;
|
||||
this._productFetchDone = 0;
|
||||
return Promise.all([
|
||||
this.getHomeZones(),
|
||||
this.getList(),
|
||||
this.getProductList(),
|
||||
this.getHomeTopProducts(),
|
||||
this.getBannered()
|
||||
]);
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.refreshHomeContent().finally(() => {
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
getPlatformQualifications() {
|
||||
getPlatformQualificationsApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
@@ -1022,6 +1054,7 @@ $card-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
&::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: linear-gradient(90deg, #FDFEFF 0%, rgba(255,255,255,0) 100%); z-index: 1; }
|
||||
.nav-content { position: relative; z-index: 2; .nav-title { font-size: 36rpx; font-weight: 800; margin-bottom: 8rpx; } .nav-desc { font-size: 22rpx; color: #888; } }
|
||||
.nav-icon-img { position: absolute; right: 0; bottom: 0; width: 128rpx; height: 128rpx; z-index: 1; }
|
||||
.nav-icon-img-256 { position: absolute; right: 0; bottom: 0; width: 256rpx; height: 138rpx; z-index: 1; }
|
||||
.nav-icon-img-64 { position: absolute; right: 30rpx; top: 30rpx; width: 84rpx; height: 84rpx; z-index: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ export default {
|
||||
},
|
||||
// 拉取 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的为在线咨询
|
||||
@@ -338,7 +338,7 @@ export default {
|
||||
},
|
||||
// 获取转诊消息列表;失败不重置 transfer,避免闪空
|
||||
getTransferList() {
|
||||
getTransferPrescriptionListApi().then((res) => {
|
||||
return getTransferPrescriptionListApi().then((res) => {
|
||||
if (res.data && res.data.code == 0) {
|
||||
const transferList = res.data.result || []
|
||||
// 统计待确认的转诊消息数量
|
||||
@@ -358,6 +358,33 @@ export default {
|
||||
}).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()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -80,22 +80,18 @@ 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",
|
||||
},
|
||||
// 测试环境
|
||||
test: {
|
||||
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: "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: {
|
||||
main: "http://127.0.0.1:18000/member",
|
||||
@@ -112,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,85 @@
|
||||
<template>
|
||||
<!--
|
||||
列表骨架屏:按 type 模拟医生卡/消息行/商品卡/资讯卡/分类侧栏,
|
||||
列表骨架屏:按 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>
|
||||
<!-- 商品详情:整页一块骨架,不按 rows 循环 -->
|
||||
<template v-if="type === 'detail'">
|
||||
<view class="sk-detail-hero sk-shimmer"></view>
|
||||
<view class="sk-detail-basic">
|
||||
<view class="sk-line sk-line--price sk-shimmer"></view>
|
||||
<view class="sk-line sk-line--lg sk-shimmer"></view>
|
||||
<view class="sk-line sk-line--md sk-shimmer"></view>
|
||||
<view class="sk-detail-spec">
|
||||
<view class="sk-line sk-line--sm 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>
|
||||
<view class="sk-detail-tip sk-shimmer"></view>
|
||||
<view
|
||||
v-for="i in detailCardCount"
|
||||
:key="'card-' + i"
|
||||
class="sk-detail-card"
|
||||
>
|
||||
<view class="sk-line sk-line--md sk-shimmer"></view>
|
||||
<view class="sk-line sk-line--sm sk-shimmer"></view>
|
||||
<view class="sk-line sk-line--lg sk-shimmer"></view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 列表类骨架按 rows 循环 -->
|
||||
<template v-else>
|
||||
<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--xs sk-shimmer"></view>
|
||||
<view class="sk-line sk-line--sm 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>
|
||||
</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>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -61,15 +87,15 @@
|
||||
export default {
|
||||
name: 'ListSkeleton',
|
||||
props: {
|
||||
// 骨架样式类型
|
||||
// 骨架样式类型(detail 为商品详情整页骨架)
|
||||
type: {
|
||||
type: String,
|
||||
default: 'message',
|
||||
validator(v) {
|
||||
return ['doctor', 'message', 'product', 'article', 'category', 'zone'].indexOf(v) !== -1
|
||||
return ['doctor', 'message', 'product', 'article', 'category', 'zone', 'detail'].indexOf(v) !== -1
|
||||
}
|
||||
},
|
||||
// 行数
|
||||
// 行数(detail 时表示信息卡片块数)
|
||||
rows: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
@@ -79,6 +105,10 @@ export default {
|
||||
rowCount() {
|
||||
const n = Number(this.rows)
|
||||
return n > 0 ? n : 3
|
||||
},
|
||||
// 详情骨架信息卡数量,默认 3 块
|
||||
detailCardCount() {
|
||||
return this.rowCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,4 +302,45 @@ export default {
|
||||
width: 40%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 商品详情骨架:大图 + 基础信息 + 物流条 + 信息卡 */
|
||||
.list-skeleton--detail {
|
||||
background: #F5F7FA;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.sk-detail-hero {
|
||||
width: 100%;
|
||||
height: 750rpx;
|
||||
}
|
||||
|
||||
.sk-detail-basic {
|
||||
background: #fff;
|
||||
padding: 32rpx 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.sk-line--price {
|
||||
width: 200rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.sk-detail-spec {
|
||||
margin-top: 24rpx;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.sk-detail-tip {
|
||||
height: 80rpx;
|
||||
margin: 0 0 20rpx;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.sk-detail-card {
|
||||
background: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 物流提示信息 -->
|
||||
<view class="order-text-tip" v-if="orderText">
|
||||
<!-- 物流提示信息:有外部配送仓时隐藏顶部文案,改在药品行展示 -->
|
||||
<view class="order-text-tip" v-if="orderText && !hasDeliveryWarehouse">
|
||||
<text>{{ orderText }}</text>
|
||||
</view>
|
||||
|
||||
@@ -90,6 +90,9 @@
|
||||
<view class="it_name">{{item.drug_name}}
|
||||
<text class="it_num" style="margin-left: 8rpx;">x{{item.number}}</text>
|
||||
</view>
|
||||
<view class="wh-tip" v-if="hasDeliveryWarehouse && item.delivery_warehouse_name">
|
||||
该药品由{{ item.delivery_warehouse_name }}配送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -109,6 +112,10 @@
|
||||
<view class="it_info" v-if="orderInfo.ProductOrder.prescription_type==2 && item.drug && canShowUsage(item.drug, prescriptionStatus)">
|
||||
{{ item.drug.usage }}
|
||||
</view>
|
||||
<!-- 有外部配送仓时:按药展示配送来源(含本仓药品) -->
|
||||
<view class="wh-tip" v-if="hasDeliveryWarehouse && item.delivery_warehouse_name">
|
||||
该药品由{{ item.delivery_warehouse_name }}配送
|
||||
</view>
|
||||
<!-- 添加查看说明书按钮 -->
|
||||
<view class="instruction-btn" v-if="canShowInstruction(item.drug, prescriptionStatus) && item.drug && item.drug.instruction" @click.stop="previewInstruction(item.drug.instruction)">
|
||||
<text>查看说明书</text>
|
||||
@@ -300,6 +307,20 @@ export default {
|
||||
const o = this.orderInfo.ProductOrder
|
||||
return o.delivery_method == 0 && o.status != 0 && o.address && o.cancel_status == 0
|
||||
},
|
||||
// 整单存在任一外部配送仓时:隐藏顶部文案,改在药品行展示
|
||||
hasDeliveryWarehouse() {
|
||||
const o = this.orderInfo && this.orderInfo.ProductOrder
|
||||
if (o && Number(o.has_delivery_warehouse) === 1) {
|
||||
return true
|
||||
}
|
||||
const items = (this.orderInfo && this.orderInfo.ProductOrderItems) || []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (Number(items[i].delivery_warehouse_id || 0) > 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
onLoad(e) {
|
||||
this.p_id = e.id
|
||||
@@ -329,18 +350,27 @@ export default {
|
||||
url: `/subPackages/product/symptoms?${q}`,
|
||||
});
|
||||
},
|
||||
// 获取订单提示文本
|
||||
// 获取订单提示文本(无外部配送仓时才用于顶部与支付弹窗)
|
||||
getOrderText() {
|
||||
getOrderTextApi().then((res) => {
|
||||
if (res.data && res.data.result) {
|
||||
this.orderText = res.data.result;
|
||||
// 设置模态框内容
|
||||
this.modalContent = `${res.data.result}\n\n请在确认以上信息后继续支付。`;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取订单提示失败:', err);
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 有外部配送仓时支付弹窗用中性文案,避免仍写「由萧康医药提供服务」
|
||||
*/
|
||||
refreshPayModalContent() {
|
||||
if (this.hasDeliveryWarehouse) {
|
||||
this.modalContent = '请确认配送信息后继续支付。';
|
||||
} else if (this.orderText) {
|
||||
this.modalContent = `${this.orderText}\n\n请在确认以上信息后继续支付。`;
|
||||
}
|
||||
},
|
||||
// 不需要代煎-代煎费
|
||||
cancel() {
|
||||
this.openshow = false;
|
||||
@@ -538,6 +568,7 @@ export default {
|
||||
|
||||
// 查询处方状态
|
||||
this.getPrescriptionStatus()
|
||||
this.refreshPayModalContent()
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -598,7 +629,8 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 先显示物流提示模态框
|
||||
// 先显示物流提示模态框(有配送仓时弹窗为中性确认文案)
|
||||
this.refreshPayModalContent();
|
||||
this.showOrderTextModal = true;
|
||||
},
|
||||
|
||||
@@ -751,6 +783,12 @@ export default {
|
||||
line-height: 1.4;
|
||||
border-left: 6rpx solid #1890ff;
|
||||
}
|
||||
.wh-tip {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.head_cancle {
|
||||
padding: 20rpx 42rpx;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<view class="container safe-area-inset-bottom">
|
||||
<MessageNotification />
|
||||
<!-- 物流提示信息 -->
|
||||
<view class="order-text-tip" v-if="orderText">
|
||||
<!-- 物流提示信息:有外部配送仓时隐藏顶部文案,改在药品行展示 -->
|
||||
<view class="order-text-tip" v-if="orderText && !hasDeliveryWarehouse">
|
||||
<text>{{ orderText }}</text>
|
||||
</view>
|
||||
|
||||
@@ -70,16 +70,12 @@
|
||||
|
||||
<view class="list" v-if="chinese">
|
||||
<view class="it" v-for="(item,index) in chinese.list" :key="item.id">
|
||||
<!-- <view class="it_name">{{item.name}}</view> -->
|
||||
<view class="it_name">{{item.name}}
|
||||
<text class="it_num" style="margin-left: 8rpx;">x{{item.number}}</text>
|
||||
</view>
|
||||
<!-- <view class="it_price">
|
||||
<text>¥{{item.price}}</text>
|
||||
<view class="it_num">
|
||||
x{{item.number}}
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="wh-tip" v-if="hasDeliveryWarehouse && item.delivery_warehouse_name">
|
||||
该药品由{{ item.delivery_warehouse_name }}配送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -89,13 +85,9 @@
|
||||
<view class="it_name">{{item.name}}
|
||||
<text class="it_num" style="margin-left: 8rpx;">x{{item.number}}</text>
|
||||
</view>
|
||||
<!-- <view class="it_name">{{item.name}}</view>
|
||||
<view class="it_price">
|
||||
<text>¥{{item.price}}</text>
|
||||
<view class="it_num">
|
||||
x{{item.number}}
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="wh-tip" v-if="hasDeliveryWarehouse && item.delivery_warehouse_name">
|
||||
该药品由{{ item.delivery_warehouse_name }}配送
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -110,6 +102,9 @@
|
||||
<view class="it_info" v-if="canShowUsage(item.content, prescriptionStatus)">
|
||||
{{item.content.usage}}
|
||||
</view>
|
||||
<view class="wh-tip" v-if="hasDeliveryWarehouse && item.delivery_warehouse_name">
|
||||
该药品由{{ item.delivery_warehouse_name }}配送
|
||||
</view>
|
||||
<view
|
||||
class="instruction-btn"
|
||||
v-if="canShowInstruction(item.content, prescriptionStatus) && item.content && item.content.instruction"
|
||||
@@ -290,6 +285,22 @@
|
||||
prescriptionStatus: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 整单存在任一外部配送仓时:隐藏顶部文案,改在药品行展示
|
||||
hasDeliveryWarehouse() {
|
||||
const o = this.orderInfo && this.orderInfo.ProductOrder
|
||||
if (o && Number(o.has_delivery_warehouse) === 1) {
|
||||
return true
|
||||
}
|
||||
const items = (this.orderInfo && this.orderInfo.ProductOrderItems) || []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (Number(items[i].delivery_warehouse_id || 0) > 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
onLoad(e) {
|
||||
this.p_id = e.id
|
||||
// 获取订单提示文本
|
||||
@@ -301,18 +312,27 @@
|
||||
methods: {
|
||||
canShowUsage,
|
||||
canShowInstruction,
|
||||
// 获取订单提示文本
|
||||
// 获取订单提示文本(无外部配送仓时才用于顶部与支付弹窗)
|
||||
getOrderText() {
|
||||
getOrderTextApi().then((res) => {
|
||||
if (res.data && res.data.result) {
|
||||
this.orderText = res.data.result;
|
||||
// 设置模态框内容
|
||||
this.modalContent = `${res.data.result}\n\n请在确认以上信息后继续支付。`;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取订单提示失败:', err);
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 有外部配送仓时支付弹窗用中性文案
|
||||
*/
|
||||
refreshPayModalContent() {
|
||||
if (this.hasDeliveryWarehouse) {
|
||||
this.modalContent = '请确认配送信息后继续支付。';
|
||||
} else if (this.orderText) {
|
||||
this.modalContent = `${this.orderText}\n\n请在确认以上信息后继续支付。`;
|
||||
}
|
||||
},
|
||||
// 物流提示确认 - 用户选择"我知道了"
|
||||
handleOrderTextConfirm() {
|
||||
this.showOrderTextModal = false;
|
||||
@@ -521,6 +541,7 @@
|
||||
}
|
||||
}
|
||||
this.getPrescriptionStatus()
|
||||
this.refreshPayModalContent()
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -609,7 +630,8 @@
|
||||
|
||||
// 支付
|
||||
toPay() {
|
||||
// 先显示物流提示模态框
|
||||
// 先显示物流提示模态框(有配送仓时弹窗为中性确认文案)
|
||||
this.refreshPayModalContent();
|
||||
this.showOrderTextModal = true;
|
||||
},
|
||||
|
||||
@@ -736,6 +758,12 @@
|
||||
line-height: 1.4;
|
||||
border-left: 6rpx solid #1890ff;
|
||||
}
|
||||
.wh-tip {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.head_cancle {
|
||||
padding: 20rpx 42rpx;
|
||||
|
||||
@@ -21,105 +21,125 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 顶部专区Tab -->
|
||||
<view class="zone-tabs-wrapper">
|
||||
<scroll-view scroll-x class="zone-tabs-scroll">
|
||||
<view class="zone-tabs">
|
||||
<view
|
||||
v-for="(zone, index) in homeZones"
|
||||
:key="index"
|
||||
class="zone-tab-item"
|
||||
:class="{ active: selectedZoneIndex === index }"
|
||||
@click="onZoneChange(index)"
|
||||
>
|
||||
<text>{{ zone.title }}</text>
|
||||
<!-- 2. 顶部专区 Tab:is-scroll 自动滚到当前选中项(与订单列表一致) -->
|
||||
<view class="zone-tabs-head">
|
||||
<u-tabs
|
||||
v-if="zoneTabList.length > 0"
|
||||
:list="zoneTabList"
|
||||
:is-scroll="true"
|
||||
:current="selectedZoneIndex"
|
||||
@change="onTabChange"
|
||||
></u-tabs>
|
||||
</view>
|
||||
|
||||
<!-- 专区未就绪时整页骨架,避免 swiper 无 item 时白屏 -->
|
||||
<view v-if="pageInitialLoading && homeZones.length === 0" class="zone-swiper zone-swiper--skeleton">
|
||||
<view class="main-content">
|
||||
<view class="category-sidebar">
|
||||
<ListSkeleton type="category" :rows="8" />
|
||||
</view>
|
||||
<view class="product-area">
|
||||
<ListSkeleton type="product" :rows="4" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. swiper 丝滑切换专区,内容为左分类 + 右商品 -->
|
||||
<swiper
|
||||
v-else
|
||||
class="zone-swiper"
|
||||
:current="selectedZoneIndex"
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="(zone, zoneIdx) in homeZones" :key="zone.id || zoneIdx">
|
||||
<view class="main-content" v-if="Number(selectedZoneIndex) === zoneIdx">
|
||||
<!-- 左侧分类栏 -->
|
||||
<view class="category-sidebar">
|
||||
<scroll-view scroll-y class="category-scroll">
|
||||
<ListSkeleton v-if="showCategorySkeleton" type="category" :rows="8" />
|
||||
<block v-else>
|
||||
<view
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === 0 }"
|
||||
@click="selectCategory(0)"
|
||||
>
|
||||
<text>全部</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="cat in categoryList"
|
||||
:key="cat.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === cat.id }"
|
||||
@click="selectCategory(cat.id)"
|
||||
>
|
||||
<text class="u-line-1">{{ cat.category_name }}</text>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧商品列表:下拉刷新 -->
|
||||
<view class="product-area">
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="product-scroll"
|
||||
@scrolltolower="loadMore"
|
||||
refresher-enabled="true"
|
||||
:refresher-triggered="isRefreshing"
|
||||
@refresherrefresh="onRefresherRefresh"
|
||||
>
|
||||
<ListSkeleton v-if="showProductSkeleton" type="product" :rows="4" />
|
||||
<block v-else>
|
||||
<!-- 商品列表 - 两列布局 -->
|
||||
<view class="product-grid" v-if="products.length > 0">
|
||||
<view
|
||||
v-for="(item, index) in products"
|
||||
:key="item.id"
|
||||
class="product-card"
|
||||
@click="handClickProduct(item)"
|
||||
>
|
||||
<view class="product-img-box">
|
||||
<image
|
||||
class="product-img"
|
||||
:class="{ 'prescription-blur': item.drug && item.drug.is_otc === 0 && item.drug.type == 2 }"
|
||||
: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"
|
||||
lazy-load
|
||||
/>
|
||||
</view>
|
||||
<view class="product-info">
|
||||
<view class="product-title u-line-2">{{ item.drug ? item.drug.drug_name : '' }}</view>
|
||||
<view class="product-tags" v-if="item.drug && item.drug.type == 2">
|
||||
<text
|
||||
class="tag-label"
|
||||
:class="getProductTag(item.drug).className"
|
||||
>
|
||||
{{ getProductTag(item.drug).label }}
|
||||
</text>
|
||||
<text class="tag-spec u-line-1">{{ (item.drug && item.drug.specification) ? item.drug.specification : '标准规格' }}</text>
|
||||
</view>
|
||||
<view class="product-bottom">
|
||||
<view class="price-box">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-val">{{ item.price }}</text>
|
||||
</view>
|
||||
<view class="cart-btn" @click.stop="showCartPopup(item)">
|
||||
<u-icon name="plus" color="#ffffff" size="26"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 空状态:仅非加载中且无数据时展示 -->
|
||||
<view v-if="products.length === 0 && !listFetching" class="empty-state">
|
||||
<u-empty text="暂无相关药品" mode="search" margin-top="100"></u-empty>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 主体内容区:左侧分类 + 右侧商品 -->
|
||||
<view
|
||||
class="main-content"
|
||||
@touchstart="onZoneTouchStart"
|
||||
@touchend="onZoneTouchEnd"
|
||||
>
|
||||
<!-- 左侧分类栏 -->
|
||||
<view class="category-sidebar">
|
||||
<scroll-view scroll-y class="category-scroll">
|
||||
<view
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === 0 }"
|
||||
@click="selectCategory(0)"
|
||||
>
|
||||
<text>全部</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="cat in categoryList"
|
||||
:key="cat.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategoryId === cat.id }"
|
||||
@click="selectCategory(cat.id)"
|
||||
>
|
||||
<text class="u-line-1">{{ cat.category_name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧商品列表区 -->
|
||||
<view class="product-area">
|
||||
<scroll-view scroll-y class="product-scroll" @scrolltolower="loadMore">
|
||||
<!-- 商品列表 - 两列布局 -->
|
||||
<view class="product-grid" v-if="products.length > 0">
|
||||
<view
|
||||
v-for="(item, index) in products"
|
||||
:key="item.id"
|
||||
class="product-card"
|
||||
@click="handClickProduct(item)"
|
||||
>
|
||||
<!-- 商品图片 -->
|
||||
<view class="product-img-box">
|
||||
<image
|
||||
class="product-img"
|
||||
:class="{ 'prescription-blur': item.drug && item.drug.is_otc === 0 && item.drug.type == 2 }"
|
||||
: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"
|
||||
lazy-load
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view class="product-info">
|
||||
<view class="product-title u-line-2">{{ item.drug ? item.drug.drug_name : '' }}</view>
|
||||
<view class="product-tags" v-if="item.drug.type == 2">
|
||||
<text
|
||||
class="tag-label"
|
||||
:class="getProductTag(item.drug).className"
|
||||
>
|
||||
{{ getProductTag(item.drug).label }}
|
||||
</text>
|
||||
<text class="tag-spec u-line-1">{{ (item.drug && item.drug.specification) ? item.drug.specification : '标准规格' }}</text>
|
||||
</view>
|
||||
<view class="product-bottom">
|
||||
<view class="price-box">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-val">{{ item.price }}</text>
|
||||
</view>
|
||||
<view class="cart-btn" @click.stop="showCartPopup(item)">
|
||||
<u-icon name="plus" color="#ffffff" size="26"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="products.length === 0" class="empty-state">
|
||||
<u-empty text="暂无相关药品" mode="search" margin-top="100"></u-empty>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 购物车悬浮球 -->
|
||||
<view
|
||||
@@ -184,17 +204,15 @@ export default {
|
||||
return {
|
||||
products: [],
|
||||
searchKeyword: "",
|
||||
|
||||
// 专区相关
|
||||
homeZones: [],
|
||||
selectedZoneIndex: 0,
|
||||
selectedZoneType: 'otc',
|
||||
zoneTouch: { startX: 0, startY: 0 },
|
||||
|
||||
// 与订单列表一致:防止 tabs / swiper 互相触发形成回路
|
||||
tabChanging: false,
|
||||
// 分类相关
|
||||
categoryList: [],
|
||||
selectedCategoryId: 0,
|
||||
|
||||
doctorId: 0,
|
||||
r_type: 0,
|
||||
delegateStoreId: '',
|
||||
@@ -203,10 +221,38 @@ export default {
|
||||
cartMove: { startX: 0, startY: 0, moving: false },
|
||||
showPopup: false,
|
||||
currentProduct: {},
|
||||
quantity: 1
|
||||
quantity: 1,
|
||||
// 首次进入骨架:专区+分类+商品首次请求结束前
|
||||
pageInitialLoading: true,
|
||||
// 商品列表请求中(切专区有旧数据时不闪空)
|
||||
listFetching: false,
|
||||
// 分类请求中
|
||||
categoryFetching: false,
|
||||
// scroll-view 下拉刷新状态
|
||||
isRefreshing: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// u-tabs 需要 { name } 结构
|
||||
zoneTabList() {
|
||||
return (this.homeZones || []).map((z) => ({
|
||||
name: z.title || ''
|
||||
}));
|
||||
},
|
||||
// 左侧分类骨架:首次加载,或分类请求中且尚无分类可展示
|
||||
showCategorySkeleton() {
|
||||
if (this.pageInitialLoading) {
|
||||
return true;
|
||||
}
|
||||
return this.categoryFetching && this.categoryList.length === 0;
|
||||
},
|
||||
// 右侧商品骨架:首次加载,或请求中且当前无商品可保留
|
||||
showProductSkeleton() {
|
||||
if (this.pageInitialLoading) {
|
||||
return true;
|
||||
}
|
||||
return this.listFetching && this.products.length === 0;
|
||||
},
|
||||
getProductImage() {
|
||||
return (this.currentProduct.drug && this.currentProduct.drug.image) ? this.currentProduct.drug.image : 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png';
|
||||
},
|
||||
@@ -221,15 +267,11 @@ export default {
|
||||
this.doctorId = e.doctor_id;
|
||||
this.r_type = e.register_type;
|
||||
this.delegateStoreId = e.delegate_store_id || '';
|
||||
|
||||
// 根据首页传入的专区类型初始化
|
||||
if (e.zone_type) {
|
||||
this.selectedZoneType = e.zone_type;
|
||||
} else {
|
||||
this.selectedZoneType = 'otc';
|
||||
}
|
||||
|
||||
// 获取专区列表
|
||||
this.getHomeZones();
|
||||
},
|
||||
methods: {
|
||||
@@ -240,19 +282,14 @@ export default {
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
this.homeZones = res.data.result || res.data.data || [];
|
||||
|
||||
// 根据传入的zone_type找到对应的索引
|
||||
if (this.homeZones.length > 0) {
|
||||
const index = this.homeZones.findIndex(z => z.type === this.selectedZoneType);
|
||||
this.selectedZoneIndex = index >= 0 ? index : 0;
|
||||
this.selectedZoneType = this.homeZones[this.selectedZoneIndex]?.type || 'otc';
|
||||
this.selectedZoneType = this.homeZones[this.selectedZoneIndex].type || 'otc';
|
||||
}
|
||||
|
||||
// 获取分类列表
|
||||
this.getCategoryList();
|
||||
}
|
||||
}).catch(() => {
|
||||
// 使用默认专区
|
||||
this.homeZones = [
|
||||
{ id: 1, title: 'OTC', type: 'otc' },
|
||||
{ id: 2, title: '保健食品', type: 'health_food' }
|
||||
@@ -260,27 +297,25 @@ export default {
|
||||
this.getCategoryList();
|
||||
});
|
||||
},
|
||||
|
||||
// 获取分类列表
|
||||
getCategoryList() {
|
||||
// 获取分类列表;不清空旧商品,避免切专区闪空
|
||||
getCategoryList(callback) {
|
||||
this.categoryFetching = true;
|
||||
getDrugCategoriesApi({
|
||||
zone_type: this.selectedZoneType
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
// 将树形结构扁平化为一级分类列表
|
||||
const categories = res.data.result || [];
|
||||
this.categoryList = this.flattenCategories(categories);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.categoryList = [];
|
||||
}).finally(() => {
|
||||
// 重置分类选择并获取商品列表
|
||||
this.categoryFetching = false;
|
||||
this.selectedCategoryId = 0;
|
||||
this.getList();
|
||||
this.getList(callback);
|
||||
});
|
||||
},
|
||||
|
||||
// 扁平化分类树(只取一级分类,或展开所有层级)
|
||||
// 扁平化分类树
|
||||
flattenCategories(categories, result = []) {
|
||||
for (const cat of categories) {
|
||||
result.push({
|
||||
@@ -295,37 +330,59 @@ export default {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
// 专区切换
|
||||
// Tab 点击切换(与订单列表 onTabChange 一致)
|
||||
onTabChange(index) {
|
||||
if (this.tabChanging) return;
|
||||
const tabIndex = Number(index) || 0;
|
||||
// 已是当前项时不重复请求;也可挡住 swiper 回写
|
||||
if (tabIndex === this.selectedZoneIndex) return;
|
||||
this.tabChanging = true;
|
||||
this.onZoneChange(tabIndex);
|
||||
this.$nextTick(() => {
|
||||
this.tabChanging = false;
|
||||
});
|
||||
},
|
||||
// swiper 滑动切换
|
||||
onSwiperChange(e) {
|
||||
if (this.tabChanging) return;
|
||||
const tabIndex = Number(e.detail.current) || 0;
|
||||
if (tabIndex === this.selectedZoneIndex) return;
|
||||
this.tabChanging = true;
|
||||
this.onZoneChange(tabIndex);
|
||||
this.$nextTick(() => {
|
||||
this.tabChanging = false;
|
||||
});
|
||||
},
|
||||
// 专区切换:立即清空旧数据出骨架,再拉分类+商品
|
||||
onZoneChange(index) {
|
||||
this.selectedZoneIndex = index;
|
||||
this.selectedZoneType = this.homeZones[index]?.type || 'otc';
|
||||
const tabIndex = Number(index) || 0;
|
||||
this.selectedZoneIndex = tabIndex;
|
||||
this.selectedZoneType = (this.homeZones[tabIndex] && this.homeZones[tabIndex].type) || 'otc';
|
||||
this.selectedCategoryId = 0;
|
||||
this.products = [];
|
||||
this.categoryList = [];
|
||||
this.listFetching = true;
|
||||
this.categoryFetching = true;
|
||||
this.getCategoryList();
|
||||
},
|
||||
|
||||
onZoneTouchStart(e) {
|
||||
const touch = e.touches[0] || e.changedTouches[0];
|
||||
if (!touch) return;
|
||||
this.zoneTouch.startX = touch.clientX;
|
||||
this.zoneTouch.startY = touch.clientY;
|
||||
},
|
||||
|
||||
onZoneTouchEnd(e) {
|
||||
const dx = e.changedTouches[0].clientX - this.zoneTouch.startX;
|
||||
const dy = e.changedTouches[0].clientY - this.zoneTouch.startY;
|
||||
if (Math.abs(dx) < 60 || Math.abs(dx) < Math.abs(dy)) return;
|
||||
const next = dx < 0 ? this.selectedZoneIndex + 1 : this.selectedZoneIndex - 1;
|
||||
if (next < 0 || next >= this.homeZones.length) return;
|
||||
this.onZoneChange(next);
|
||||
},
|
||||
|
||||
// 分类选择
|
||||
// 分类选择:清空商品出骨架后再请求
|
||||
selectCategory(categoryId) {
|
||||
this.selectedCategoryId = categoryId;
|
||||
this.products = [];
|
||||
this.listFetching = true;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 右侧下拉刷新:清空分类+商品出骨架,再重拉
|
||||
onRefresherRefresh() {
|
||||
this.isRefreshing = true;
|
||||
this.products = [];
|
||||
this.categoryList = [];
|
||||
this.listFetching = true;
|
||||
this.categoryFetching = true;
|
||||
this.getCategoryList(() => {
|
||||
this.isRefreshing = false;
|
||||
});
|
||||
},
|
||||
// 核心标签判断逻辑
|
||||
getProductTag(drug) {
|
||||
if (!drug) return { label: 'OTC', className: 'tag-green' };
|
||||
@@ -339,41 +396,43 @@ export default {
|
||||
return { label: 'Rx', className: 'tag-red' };
|
||||
}
|
||||
}
|
||||
return { label: 'OTC', className: 'tag-green' };
|
||||
},
|
||||
|
||||
getList() {
|
||||
// 拉取商品列表;callback 供下拉刷新收尾
|
||||
getList(callback) {
|
||||
this.listFetching = true;
|
||||
const params = {
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
page: 1,
|
||||
limit: 100,
|
||||
category: this.selectedZoneType
|
||||
};
|
||||
|
||||
if (this.selectedCategoryId > 0) {
|
||||
params.category_id = this.selectedCategoryId;
|
||||
}
|
||||
|
||||
if (this.searchKeyword) {
|
||||
params.keyword = this.searchKeyword;
|
||||
}
|
||||
|
||||
getStoreDrugListBySalespersonApi(params).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
this.products = res.data.result.records || [];
|
||||
this.cartCount = res.data.result.cart_count || 0;
|
||||
}
|
||||
}).finally(() => {
|
||||
this.listFetching = false;
|
||||
this.pageInitialLoading = false;
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleSearch() {
|
||||
this.getList();
|
||||
},
|
||||
|
||||
clearSearch() {
|
||||
this.searchKeyword = "";
|
||||
this.getList();
|
||||
},
|
||||
|
||||
handClickProduct(item) {
|
||||
let url = `/subPackages/product/symptoms?id=${item.drug.id}&drug_name=${item.drug.drug_name}&is_otc=${item.drug.is_otc}&image=${item.drug.image}&price=${item.price}`;
|
||||
if (this.delegateStoreId) {
|
||||
@@ -381,7 +440,6 @@ export default {
|
||||
}
|
||||
uni.navigateTo({ url });
|
||||
},
|
||||
|
||||
// 悬浮球逻辑
|
||||
initCartPosition() {
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
@@ -433,7 +491,6 @@ export default {
|
||||
},
|
||||
quantityChange(value) {},
|
||||
confirmAddToCart() {
|
||||
// 检查是否为处方药:is_otc === 0 && type == 2
|
||||
if (this.currentProduct.drug && this.currentProduct.drug.is_otc === 0 && this.currentProduct.drug.type == 2) {
|
||||
uni.showToast({
|
||||
title: '处方药不能加入购物车,请通过提交预定购买',
|
||||
@@ -442,7 +499,6 @@ export default {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
saveCartApi({
|
||||
store_id: uni.getStorageSync('store_id') || '11001',
|
||||
drug_id: this.currentProduct.drug.id,
|
||||
@@ -502,58 +558,33 @@ export default {
|
||||
background: #fff;
|
||||
padding: 16rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
|
||||
flex-shrink: 0;
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. 顶部专区Tab */
|
||||
.zone-tabs-wrapper {
|
||||
/* 2. 顶部专区 Tab(u-tabs) */
|
||||
.zone-tabs-head {
|
||||
width: 750rpx;
|
||||
height: 80rpx;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
|
||||
.zone-tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.zone-tabs {
|
||||
display: inline-flex;
|
||||
padding: 0 24rpx;
|
||||
|
||||
.zone-tab-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.active {
|
||||
color: #2B85E4;
|
||||
font-weight: bold;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: linear-gradient(90deg, #5CADFF, #2B85E4);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
/* 3. 主体内容区 */
|
||||
.main-content {
|
||||
/* 3. 专区 swiper:占满剩余高度,与订单列表 order-swiper 一致 */
|
||||
.zone-swiper {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 主体内容区 */
|
||||
.main-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -563,6 +594,7 @@ export default {
|
||||
width: 180rpx;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
|
||||
.category-scroll {
|
||||
height: 100%;
|
||||
@@ -607,6 +639,7 @@ export default {
|
||||
.product-area {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.product-scroll {
|
||||
height: 100%;
|
||||
@@ -643,10 +676,7 @@ export default {
|
||||
height: 100%;
|
||||
|
||||
&.prescription-blur {
|
||||
/* 暂时禁用模糊
|
||||
filter: blur(8px);
|
||||
-webkit-filter: blur(8px);
|
||||
*/
|
||||
/* 暂时禁用模糊 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<MessageNotification />
|
||||
<!-- 首次详情加载骨架,避免半空页可点购买 -->
|
||||
<ListSkeleton v-if="detailInitialLoading" type="detail" :rows="3" />
|
||||
<block v-else>
|
||||
<!-- 1. 顶部轮播图 -->
|
||||
<view class="swiper-section">
|
||||
<swiper
|
||||
@@ -74,10 +77,10 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 物流提示 -->
|
||||
<view class="logistics-tip" v-if="orderText">
|
||||
<!-- 3. 物流提示:有配送仓时展示仓文案,否则用 order-text -->
|
||||
<view class="logistics-tip" v-if="logisticsTipText">
|
||||
<u-icon name="car-fill" color="#2B85E4" size="34" style="margin-right:12rpx"></u-icon>
|
||||
<text>{{ orderText }}</text>
|
||||
<text>{{ logisticsTipText }}</text>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -169,6 +172,7 @@
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- === 以下为原有弹窗代码 (保留完整逻辑) === -->
|
||||
|
||||
@@ -268,7 +272,11 @@ export default {
|
||||
indications: '',
|
||||
indications_array: [],
|
||||
compliance_tip: '', // 合规提示文案,由后端返回
|
||||
supplier: null
|
||||
supplier: null,
|
||||
// 配送仓字段需预声明,否则 Vue2 Object.assign 新 key 不触发视图更新
|
||||
has_delivery_warehouse: 0,
|
||||
delivery_warehouse_id: 0,
|
||||
delivery_warehouse_name: ''
|
||||
},
|
||||
formData: {
|
||||
id: '',
|
||||
@@ -290,6 +298,12 @@ export default {
|
||||
showBuyPopup: false,
|
||||
buyQuantity: 1,
|
||||
orderText: '本次商品物流配送,由萧康医药提供服务。',
|
||||
// 物流仓提示单独挂 data,保证详情接口返回后一定能驱动 computed 重算
|
||||
deliveryWarehouseFlag: 0,
|
||||
deliveryWarehouseId: 0,
|
||||
deliveryWarehouseName: '',
|
||||
// 首次详情请求结束前展示骨架
|
||||
detailInitialLoading: true,
|
||||
introductionImages: [], // 商品介绍图数组
|
||||
delegateStoreId: '', // 委托诊所ID
|
||||
fromOrderId: '',
|
||||
@@ -316,6 +330,29 @@ export default {
|
||||
manufacturerName() {
|
||||
return (this.productDetail.supplier && this.productDetail.supplier.name) || '';
|
||||
},
|
||||
// 有仓药绑定时用仓文案,替代萧康 order-text(优先读独立字段,避免嵌套对象漏更新)
|
||||
hasDeliveryWarehouse() {
|
||||
if (Number(this.deliveryWarehouseFlag) === 1) {
|
||||
return true;
|
||||
}
|
||||
if (Number(this.deliveryWarehouseId || 0) > 0) {
|
||||
return true;
|
||||
}
|
||||
const d = this.productDetail || {};
|
||||
if (Number(d.has_delivery_warehouse) === 1) {
|
||||
return true;
|
||||
}
|
||||
return Number(d.delivery_warehouse_id || 0) > 0;
|
||||
},
|
||||
logisticsTipText() {
|
||||
const whName = this.deliveryWarehouseName
|
||||
|| (this.productDetail && this.productDetail.delivery_warehouse_name)
|
||||
|| '';
|
||||
if (this.hasDeliveryWarehouse && whName) {
|
||||
return '该商品由' + whName + '供应及配送';
|
||||
}
|
||||
return this.orderText || '';
|
||||
},
|
||||
showFunctionSection() {
|
||||
if (isHealthFood(this.productDetail)) return false;
|
||||
return canShowFunction(this.productDetail, this.prescriptionApproved)
|
||||
@@ -395,6 +432,10 @@ export default {
|
||||
});
|
||||
},
|
||||
getOrderText() {
|
||||
// 有配送仓时不请求萧康默认文案
|
||||
if (this.hasDeliveryWarehouse) {
|
||||
return;
|
||||
}
|
||||
getOrderTextApi().then((res) => {
|
||||
if (res.data && res.data.result) {
|
||||
this.orderText = res.data.result;
|
||||
@@ -404,7 +445,6 @@ export default {
|
||||
})
|
||||
},
|
||||
getDetail() {
|
||||
this.getOrderText();
|
||||
const params = {
|
||||
id: this.productDetail.id,
|
||||
};
|
||||
@@ -417,8 +457,13 @@ export default {
|
||||
}
|
||||
getStoreDrugDetailApi(params).then((res) => {
|
||||
if (res.data.code === 0) {
|
||||
const data = res.data.result;
|
||||
Object.assign(this.productDetail, data);
|
||||
const data = res.data.result || {};
|
||||
// 整对象替换:列表进详情时 name/price 常与接口一致,Object.assign 不会触发 Vue2 更新
|
||||
this.productDetail = Object.assign({}, this.productDetail, data);
|
||||
// 仓字段再写到顶层 data,确保物流提示 computed 一定重算
|
||||
this.deliveryWarehouseFlag = Number(data.has_delivery_warehouse || 0);
|
||||
this.deliveryWarehouseId = Number(data.delivery_warehouse_id || 0);
|
||||
this.deliveryWarehouseName = data.delivery_warehouse_name || '';
|
||||
if (data.prescription_approved !== undefined) {
|
||||
this.prescriptionApproved = !!data.prescription_approved;
|
||||
}
|
||||
@@ -438,9 +483,14 @@ export default {
|
||||
} else {
|
||||
this.introductionImages = [];
|
||||
}
|
||||
// 详情返回后再决定是否拉萧康 order-text
|
||||
this.getOrderText();
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('加载商品详情失败:', error);
|
||||
}).finally(() => {
|
||||
// 无论成功失败都关闭骨架,避免一直卡住
|
||||
this.detailInitialLoading = false;
|
||||
});
|
||||
},
|
||||
previewImage(index) {
|
||||
|
||||
@@ -331,6 +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://xk.ws.nailaoyun.cn/ws';
|
||||
const wsUrl = checkDev('dev')? 'ws://127.0.0.1:12080/ws': checkDev('test')? 'wss://xk.ws.nailaoyun.cn/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);
|
||||
Reference in New Issue
Block a user