Files
xk-client-wx/subPackages/doctor/components/FollowUpDrugDrawer.vue
李琦 2d2a8abfd4 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 只读医生详情,
  隐藏预约挂号区
2026-07-19 16:08:04 +08:00

507 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="follow-up-drug-drawer">
<!-- 触发器部分优化阴影与圆角使用 hover-class 提升微信小程序点击反馈 -->
<view class="trigger" hover-class="trigger-hover" :hover-stay-time="150" @click="openPopup">
<view class="trigger-main">
<text v-if="!displaySummary" class="placeholder">请选择药品</text>
<text v-else class="summary">{{ displaySummary }}</text>
</view>
<u-icon name="arrow-right" color="#94A3B8" size="28"></u-icon>
</view>
<!-- 弹窗部分增加圆角弧度(40)视觉更加柔和 -->
<u-popup
v-model="popupVisible"
mode="bottom"
border-radius="40"
height="78%"
:safe-area-inset-bottom="true"
:closeable="true"
close-icon-color="#94A3B8"
@close="onPopupClose"
>
<view class="drawer-inner">
<!-- 头部区域 -->
<view class="drawer-header">
<view class="drawer-title">选择相关西药</view>
</view>
<!-- 搜索栏优化高度和背景色让输入框更圆润 -->
<view v-if="storeId" class="drawer-search">
<u-search
v-model="searchKeyword"
placeholder="输入药品名称或拼音首字母"
bg-color="#F8FAFC"
border-color="transparent"
search-icon-color="#94A3B8"
color="#0F172A"
:show-action="false"
:clearabled="true"
shape="round"
height="76"
@clear="onSearchClear"
/>
</view>
<!-- 状态展示居中增加图标使用更柔和的提示文本 -->
<view v-if="searchLoading" class="drawer-state">
<u-loading mode="circle" size="64" color="#4175ee"></u-loading>
<text class="state-text mt-20">正在努力加载中...</text>
</view>
<view v-else-if="!remoteDrugList.length" class="drawer-state empty-state">
<!-- 这里使用内置icon做空状态也可以替换为你自己的空状态插画 -->
<u-icon name="info-circle" size="96" color="#CBD5E1"></u-icon>
<text class="state-text mt-20">{{ emptyText }}</text>
</view>
<!-- 列表区域精美的卡片式 UI -->
<scroll-view v-else scroll-y class="drawer-scroll" scroll-with-animation>
<view class="drug-list">
<view
v-for="d in remoteDrugList"
:key="d.id"
class="drawer-row card-ui"
:class="{ 'is-active': getQty(d.id) > 0 }"
hover-class="card-ui-hover"
:hover-stay-time="100"
>
<!-- 勾选框 -->
<view class="drug-check" :class="{ on: getQty(d.id) > 0 }" @click.stop="toggleRow(d)">
<u-icon v-if="getQty(d.id) > 0" name="checkbox-mark" color="#fff" size="24"></u-icon>
</view>
<!-- 药品图片 -->
<image class="drug-thumb" :src="drugImage(d)" mode="aspectFill" @click.stop="toggleRow(d)" />
<!-- 药品信息 -->
<view class="drug-meta" @click.stop="toggleRow(d)">
<text class="dn">{{ d.drug_name }}</text>
<text class="ds" v-if="d.specification">{{ d.specification }}</text>
</view>
<!-- 数量操作 (阻止冒泡防止触发卡片点击) -->
<view class="drawer-row-actions" @click.stop>
<u-number-box
:value="getQty(d.id)"
:min="0"
:max="99"
:size="26"
:input-width="72"
:input-height="52"
:index="d.id"
bg-color="#F1F5F9"
@input="onQtyInput(d, $event)"
/>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏增强主次按钮对比度增大点击热区适配底部安全区 -->
<view class="drawer-footer safe-area-inset-bottom">
<view class="btn-text" hover-class="btn-text-hover" :hover-stay-time="100" @click="clearDraft">
清空已选
</view>
<view class="btn-primary" hover-class="btn-primary-hover" :hover-stay-time="100" @click="confirm">
确定保存
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
// 逻辑部分完全保持不变
import { getWesternDrugsForFollowUpApi } from '@/request/api/order';
export default {
name: 'FollowUpDrugDrawer',
props: {
storeId: {
type: [String, Number],
default: '',
},
value: {
type: Array,
default: () => [],
},
},
data() {
return {
popupVisible: false,
draftQty: {},
searchKeyword: '',
remoteDrugList: [],
searchLoading: false,
drugMetaCache: {},
searchTimer: null,
};
},
computed: {
displaySummary() {
const v = this.value || [];
if (!v.length) return '';
return v.map((i) => i.drug_name).filter(Boolean).join('');
},
emptyText() {
return (this.searchKeyword || '').trim() ? '未找到相关药品' : '暂无可选西药';
},
},
watch: {
searchKeyword(val) {
if (!this.popupVisible) return;
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
this.searchTimer = setTimeout(() => {
this.fetchRemoteDrugs(val);
}, 300);
},
},
beforeDestroy() {
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
},
methods: {
drugImage(d) {
const u = d && d.image;
return u && String(u).trim() ? u : 'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/mine/avatar_1.png';
},
getQty(id) {
const q = this.draftQty[id];
return q != null && q > 0 ? Number(q) : 0;
},
cacheDrugMeta(d) {
if (d && d.id) {
this.$set(this.drugMetaCache, d.id, d);
}
},
initDraftFromValue() {
const next = {};
for (const it of this.value || []) {
const id = it.id;
const q = Number(it.quantity);
if (id != null && q > 0) {
next[id] = q;
this.cacheDrugMeta(it);
}
}
this.draftQty = next;
},
async fetchRemoteDrugs(keyword = '') {
if (!this.storeId) {
this.remoteDrugList = [];
return;
}
this.searchLoading = true;
try {
const params = { store_id: this.storeId };
const kw = (keyword || '').trim();
if (kw) params.keyword = kw;
const res = await getWesternDrugsForFollowUpApi(params);
const list = Array.isArray(res.data?.result) ? res.data.result : [];
this.remoteDrugList = list;
list.forEach((d) => this.cacheDrugMeta(d));
} catch (e) {
console.warn('fetchRemoteDrugs', e);
this.remoteDrugList = [];
} finally {
this.searchLoading = false;
}
},
openPopup() {
if (this.searchTimer) {
clearTimeout(this.searchTimer);
this.searchTimer = null;
}
this.searchKeyword = '';
this.initDraftFromValue();
this.popupVisible = true;
this.fetchRemoteDrugs('');
},
onPopupClose() {
this.popupVisible = false;
},
onSearchClear() {
this.searchKeyword = '';
this.fetchRemoteDrugs('');
},
toggleRow(d) {
const id = d.id;
this.cacheDrugMeta(d);
if (this.getQty(id) > 0) {
this.$delete(this.draftQty, id);
} else {
this.$set(this.draftQty, id, 1);
}
},
onQtyInput(d, val) {
this.cacheDrugMeta(d);
const n = Number(val);
if (!n || n < 1) {
this.$delete(this.draftQty, d.id);
} else {
this.$set(this.draftQty, d.id, Math.min(99, n));
}
},
clearDraft() {
this.draftQty = {};
},
confirm() {
const out = [];
for (const idStr of Object.keys(this.draftQty)) {
const id = Number(idStr);
const q = this.getQty(id);
if (q > 0) {
const meta = this.drugMetaCache[id] || {};
out.push({
id,
drug_name: meta.drug_name || '',
quantity: q,
});
}
}
this.$emit('input', out);
this.$emit('confirm', out);
this.popupVisible = false;
},
},
};
</script>
<style lang="scss" scoped>
.follow-up-drug-drawer {
width: 100%;
}
/* ================= 外部触发器 ================= */
.trigger {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 30rpx;
margin-top: 12rpx;
background: #ffffff;
border-radius: 20rpx;
border: 1rpx solid #F1F5F9;
box-shadow: 0 4rpx 12rpx rgba(148, 163, 184, 0.08);
transition: all 0.2s ease;
}
/* 微信小程序推荐使用 hover-class 替代 :active */
.trigger-hover {
background: #F8FAFC;
transform: scale(0.995);
box-shadow: 0 2rpx 6rpx rgba(148, 163, 184, 0.04);
}
.trigger-main {
flex: 1;
min-width: 0;
margin-right: 16rpx;
}
.placeholder {
font-size: 30rpx;
color: #94A3B8;
}
.summary {
font-size: 30rpx;
color: #1E293B;
line-height: 1.5;
font-weight: 500;
}
/* ================= 弹窗内部容器 ================= */
.drawer-inner {
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
padding: 40rpx 28rpx 0;
background-color: #ffffff;
}
.drawer-header {
padding-bottom: 30rpx;
}
.drawer-title {
font-size: 36rpx;
font-weight: 600;
color: #0F172A;
text-align: center;
letter-spacing: 1rpx;
}
.drawer-search {
padding-bottom: 24rpx;
}
/* ================= 空状态/加载状态 ================= */
.drawer-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
.mt-20 {
margin-top: 24rpx;
}
.state-text {
color: #94A3B8;
font-size: 28rpx;
letter-spacing: 2rpx;
}
}
.empty-state {
opacity: 0.8;
}
/* ================= 滚动列表 ================= */
.drawer-scroll {
flex: 1;
max-height: calc(100vh * 0.78 - 320rpx);
min-height: 200rpx;
}
.drug-list {
padding: 8rpx 4rpx; /* 为阴影留出空间 */
}
/* ================= 卡片式 UI ================= */
.card-ui {
display: flex;
align-items: center;
padding: 24rpx;
margin-bottom: 24rpx;
background: #ffffff;
border-radius: 24rpx;
border: 2rpx solid #F1F5F9;
box-shadow: 0 4rpx 16rpx rgba(148, 163, 184, 0.05);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
/* 选中态全局高亮边框和背景 */
&.is-active {
border-color: #4175EE;
background: #F5F8FF;
box-shadow: 0 6rpx 20rpx rgba(65, 117, 238, 0.08);
}
}
/* 卡片点击反馈 */
.card-ui-hover {
background: #F8FAFC;
transform: scale(0.99);
}
/* ================= 自定义复选框 ================= */
.drug-check {
width: 44rpx;
height: 44rpx;
border: 3rpx solid #CBD5E1;
border-radius: 50%;
margin-right: 24rpx;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
background: #ffffff;
&.on {
background: #4175EE;
border-color: #4175EE;
transform: scale(1.05); /* 选中时有微小的回弹感 */
}
}
/* ================= 药品缩略图 ================= */
.drug-thumb {
width: 112rpx;
height: 112rpx;
border-radius: 16rpx;
margin-right: 24rpx;
flex-shrink: 0;
background: #F8FAFC;
border: 1rpx solid #F1F5F9;
}
/* ================= 药品文字信息 ================= */
.drug-meta {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
.dn {
font-size: 32rpx;
color: #0F172A;
font-weight: 500;
margin-bottom: 10rpx;
/* 单行省略 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ds {
font-size: 24rpx;
color: #64748B;
}
}
.drawer-row-actions {
flex-shrink: 0;
margin-left: 16rpx;
}
/* ================= 底部操作区 ================= */
.drawer-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0 32rpx;
gap: 30rpx;
background: #ffffff;
/* 去掉生硬的顶边框,改用轻微的上阴影 */
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.02);
z-index: 10;
}
/* 次要按钮 */
.btn-text {
flex: 1;
text-align: center;
font-size: 32rpx;
color: #475569;
padding: 26rpx 0;
border-radius: 20rpx;
background: #F1F5F9;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-text-hover {
background: #E2E8F0;
color: #334155;
}
/* 主要按钮 */
.btn-primary {
flex: 2;
text-align: center;
font-size: 32rpx;
color: #ffffff;
background: linear-gradient(135deg, #5B8AFF 0%, #3B6BE0 100%);
border-radius: 20rpx;
padding: 26rpx 0;
font-weight: 500;
box-shadow: 0 8rpx 20rpx rgba(65, 117, 238, 0.25);
transition: all 0.2s ease;
}
.btn-primary-hover {
transform: translateY(2rpx);
box-shadow: 0 4rpx 10rpx rgba(65, 117, 238, 0.15);
background: linear-gradient(135deg, #4A7AFF 0%, #325BCE 100%);
}
</style>