Files
MediCompanions/app/view/PatientAPP/Nursing/index.html
T
2026-06-22 11:23:05 +08:00

319 lines
18 KiB
HTML

{extend name="PatientAPP/layout" /}
{block name="css"}
<link rel="stylesheet" href="index.css" />
{/block}
{block name="app"}
<div id="app" v-cloak>
<!-- 头部区域 -->
<div class="header-section">
<!-- 搜索栏 -->
<div class="header-search" @click="showSearch = true">
<van-icon name="search" size="16" color="#999" />
<span>搜索护工姓名/技能...</span>
</div>
<!-- 筛选标签栏 -->
<div class="filter-bar">
<div class="filter-tag" :class="{ active: filterSkill }" @click="toggleFilter('skill')">{{ filterSkill || '技能类型' }}</div>
<div class="filter-tag" :class="{ active: filterPrice }" @click="toggleFilter('price')">{{ currentPriceLabel }}</div>
<div class="filter-tag" :class="{ active: filterExp }" @click="toggleFilter('exp')">{{ currentExpLabel }}</div>
<div class="filter-tag" :class="{ active: filterDateRange.length > 0 }" @click="showDateCalendar = true">{{ currentDateLabel }}</div>
<div class="filter-tag" :class="{ active: sortBy !== 'rating' }" @click="toggleFilter('sort')">{{ currentSortLabel }}</div>
</div>
<!-- 筛选面板(内联展开) -->
<transition name="slide">
<div class="filter-panel" v-if="showFilterPanel" @click.stop>
<div class="filter-panel-list">
<span v-for="item in popupOptions" :key="item.value"
class="filter-panel-item" :class="{ selected: isPopupSelected(item.value) }"
@click="onPopupSelect(item.value)">{{ item.text }}</span>
</div>
</div>
</transition>
</div>
<!-- 统计信息 -->
<div class="stats-bar">
<div class="stats-item">
<span class="stats-num">{{ filteredList.length }}</span>
<span class="stats-label">位护工</span>
</div>
<div class="stats-divider"></div>
<div class="stats-item">
<span class="stats-num">98.6%</span>
<span class="stats-label">好评率</span>
</div>
<div class="stats-divider"></div>
<div class="stats-item">
<span class="stats-num">24h</span>
<span class="stats-label">快速响应</span>
</div>
</div>
<!-- 护工列表 -->
<div class="nurse-list-wrapper">
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div class="nurse-card-wrap" v-for="(nurse, index) in filteredList" :key="nurse.id" @click="viewNurse(nurse)">
<div class="nurse-card-main">
<!-- 左侧头像 -->
<div class="nurse-avatar-box">
<div class="nurse-avatar-inner" :class="{ 'avatar-online': nurse.online }">
<van-icon name="contact" size="36" color="#fff" />
</div>
<span class="online-dot" v-if="nurse.online"></span>
</div>
<!-- 中间信息 -->
<div class="nurse-detail">
<div class="nurse-name-row">
<span class="nurse-name">{{ nurse.name }}</span>
<van-tag type="primary" size="small" plain v-if="nurse.isHot">热门</van-tag>
<van-tag type="success" size="small" plain v-if="nurse.certified">认证</van-tag>
</div>
<div class="nurse-meta-row">
<van-rate :value="nurse.rating" readonly size="12" color="#ff976a" void-icon="star" void-color="#eee" />
<span class="nurse-score">{{ nurse.rating }}</span>
<span class="nurse-sep">|</span>
<span class="nurse-exp-text">{{ nurse.exp }}年经验</span>
<span class="nurse-sep">|</span>
<span class="nurse-orders-text">服务{{ nurse.orders }}次</span>
</div>
<div class="nurse-skill-tags">
<van-tag type="primary" plain size="medium" v-for="(skill, i) in nurse.skills.slice(0, 4)" :key="'s'+i">{{ skill }}</van-tag>
</div>
</div>
<!-- 右侧价格 -->
<div class="nurse-price-col">
<div class="nurse-price-row">
<span class="price-symbol">&yen;</span>
<span class="price-num">{{ nurse.price }}</span>
<span class="price-unit">/时</span>
</div>
<van-button type="primary" size="small" round class="book-btn" @click.stop="bookNurse(nurse)">预约</van-button>
</div>
</div>
<!-- 底部认证信息 -->
<div class="nurse-cert-row" v-if="nurse.certs && nurse.certs.length">
<van-icon name="shield-o" size="14" color="#07c160" />
<span class="cert-text">{{ nurse.certs.join(' · ') }}</span>
</div>
</div>
</van-list>
</van-pull-refresh>
</div>
<!-- 时间范围选择弹窗 -->
<van-popup v-model:show="showDateCalendar" round :style="{ width: '85%', padding: '20px' }">
<div style="font-size: 16px; font-weight: 600; margin-bottom: 16px; text-align: center;">选择时间范围</div>
<div style="display: flex; flex-direction: column; gap: 12px;">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 14px; color: #666; width: 36px;">开始</span>
<input type="date" v-model="dateStart" style="flex:1; border:1px solid #e0e0e0; border-radius:8px; padding:10px 12px; font-size:14px; outline:none;" />
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 14px; color: #666; width: 36px;">结束</span>
<input type="date" v-model="dateEnd" style="flex:1; border:1px solid #e0e0e0; border-radius:8px; padding:10px 12px; font-size:14px; outline:none;" />
</div>
</div>
<div style="margin-top: 18px; display:flex; gap: 12px;">
<van-button style="flex:1" @click="showDateCalendar = false">取消</van-button>
<van-button style="flex:1" type="primary" @click="onDateConfirm">确定</van-button>
</div>
</van-popup>
<!-- 搜索弹窗 -->
<van-popup v-model:show="showSearch" position="top" :style="{ height: 'auto' }">
<van-search
v-model="searchKeyword"
placeholder="搜索护工姓名/技能/资质"
shape="round"
show-action
@search="onSearch"
@cancel="showSearch = false"
>
<template #action>
<div @click="onSearch">搜索</div>
</template>
</van-search>
</van-popup>
<!-- 底部栏 -->
{include file="PatientAPP/Component/van-tabbar" /}
</div>
{/block}
{block name="js"}
<script>
const app = Vue.createApp({
data() {
return {
activeTab: 'nurses',
showSearch: false,
searchKeyword: '',
refreshing: false,
loading: false,
finished: true,
filterSkill: '',
filterPrice: '',
filterExp: '',
sortBy: 'rating',
showFilterPanel: false,
currentFilterType: '',
showDateCalendar: false,
filterDateRange: [],
dateStart: '',
dateEnd: '',
skillOptions: [
{ text: '全部技能', value: '' },
{ text: '术后护理', value: '术后护理' },
{ text: '老年照护', value: '老年照护' },
{ text: '母婴护理', value: '母婴护理' },
{ text: '康复训练', value: '康复训练' },
{ text: '重症监护', value: '重症监护' },
{ text: '输液换药', value: '输液换药' },
{ text: '心理疏导', value: '心理疏导' },
],
priceOptions: [
{ text: '价格排序', value: '' },
{ text: '50元以下', value: 'low' },
{ text: '50-70元', value: 'mid' },
{ text: '70元以上', value: 'high' },
],
expOptions: [
{ text: '经验不限', value: '' },
{ text: '3年以下', value: 'junior' },
{ text: '3-5年', value: 'mid' },
{ text: '5年以上', value: 'senior' },
],
sortOptions: [
{ text: '评分最高', value: 'rating' },
{ text: '价格最低', value: 'price_asc' },
{ text: '服务最多', value: 'orders' },
{ text: '经验最久', value: 'exp' },
],
nurseList: [
{ id: 1, name: '张秀芳', rating: 4.9, price: 68, exp: 8, orders: 320, isHot: true, certified: true, online: true, certs: ['护理证', '健康证'], skills: ['术后护理', '老年照护', '康复训练'] },
{ id: 2, name: '李国华', rating: 4.8, price: 75, exp: 10, orders: 456, isHot: true, certified: true, online: true, certs: ['护士资格证', '急救证'], skills: ['重症监护', '输液换药', '心理疏导'] },
{ id: 3, name: '王美玲', rating: 4.9, price: 62, exp: 5, orders: 218, isHot: false, certified: true, online: false, certs: ['护理证', '育婴师'], skills: ['母婴护理', '月子照护', '营养配餐'] },
{ id: 4, name: '陈建国', rating: 4.7, price: 58, exp: 6, orders: 167, isHot: false, certified: true, online: true, certs: ['护理证', '康复师'], skills: ['行动辅助', '日常起居', '陪同就医'] },
{ id: 5, name: '刘晓红', rating: 4.6, price: 55, exp: 3, orders: 89, isHot: false, certified: false, online: true, certs: ['护理证'], skills: ['基础护理', '饮食照料', '卫生清洁'] },
{ id: 6, name: '赵文斌', rating: 4.8, price: 80, exp: 12, orders: 520, isHot: true, certified: true, online: false, certs: ['护士资格证', '急救证', '康复师'], skills: ['重症监护', '术后护理', '康复训练', '心理疏导'] },
{ id: 7, name: '孙丽萍', rating: 4.5, price: 48, exp: 2, orders: 56, isHot: false, certified: false, online: true, certs: ['护理员培训证'], skills: ['生活照料', '陪伴聊天', '协助活动'] },
{ id: 8, name: '周明辉', rating: 4.7, price: 65, exp: 7, orders: 289, isHot: false, certified: true, online: true, certs: ['健康证', '急救证'], skills: ['老年照护', '慢性病管理', '用药提醒'] },
{ id: 9, name: '吴秀英', rating: 4.9, price: 72, exp: 9, orders: 380, isHot: true, certified: true, online: true, certs: ['护理证', '育婴师', '营养师证'], skills: ['母婴护理', '月子照护', '营养配餐', '产后恢复'] },
{ id: 10, name: '郑海涛', rating: 4.6, price: 60, exp: 4, orders: 145, isHot: false, certified: true, online: false, certs: ['护理证', '康复师'], skills: ['康复训练', '行动辅助', '按摩理疗'] },
],
};
},
computed: {
filteredList() {
let list = [...this.nurseList];
if (this.searchKeyword) {
const kw = this.searchKeyword.toLowerCase();
list = list.filter(n =>
n.name.includes(kw) ||
n.skills.some(s => s.includes(kw)) ||
n.certs.some(c => c.includes(kw))
);
}
if (this.filterSkill) list = list.filter(n => n.skills.includes(this.filterSkill));
if (this.filterPrice === 'low') list = list.filter(n => n.price < 50);
else if (this.filterPrice === 'mid') list = list.filter(n => n.price >= 50 && n.price <= 70);
else if (this.filterPrice === 'high') list = list.filter(n => n.price > 70);
if (this.filterExp === 'junior') list = list.filter(n => n.exp < 3);
else if (this.filterExp === 'mid') list = list.filter(n => n.exp >= 3 && n.exp <= 5);
else if (this.filterExp === 'senior') list = list.filter(n => n.exp > 5);
if (this.sortBy === 'rating') list.sort((a, b) => b.rating - a.rating);
else if (this.sortBy === 'price_asc') list.sort((a, b) => a.price - b.price);
else if (this.sortBy === 'orders') list.sort((a, b) => b.orders - a.orders);
else if (this.sortBy === 'exp') list.sort((a, b) => b.exp - a.exp);
return list;
},
currentPriceLabel() { const item = this.priceOptions.find(o => o.value === this.filterPrice); return item ? item.text : '价格区间'; },
currentExpLabel() { const item = this.expOptions.find(o => o.value === this.filterExp); return item ? item.text : '工作经验'; },
currentSortLabel() { const item = this.sortOptions.find(o => o.value === this.sortBy); return item ? item.text : '默认排序'; },
currentDateLabel() {
if (this.filterDateRange.length === 2) {
return `${this.filterDateRange[0]} ~ ${this.filterDateRange[1]}`;
}
return '时间范围';
},
popupTitle() {
const map = { skill: '技能类型', price: '价格区间', exp: '工作经验', sort: '排序方式' };
return map[this.currentFilterType] || '';
},
popupOptions() {
const map = { skill: this.skillOptions, price: this.priceOptions, exp: this.expOptions, sort: this.sortOptions };
return map[this.currentFilterType] || [];
},
},
methods: {
onBack() { history.back(); },
onSearch() { this.showSearch = false; vant.showToast({ message: `搜索: ${this.searchKeyword}`, position: 'top' }); },
toggleFilter(type) {
if (this.showFilterPanel && this.currentFilterType === type) {
this.showFilterPanel = false;
} else {
this.currentFilterType = type;
this.showFilterPanel = true;
}
},
isPopupSelected(val) {
if (this.currentFilterType === 'skill') return this.filterSkill === val;
if (this.currentFilterType === 'price') return this.filterPrice === val;
if (this.currentFilterType === 'exp') return this.filterExp === val;
if (this.currentFilterType === 'sort') return this.sortBy === val;
return false;
},
onPopupSelect(val) {
if (this.currentFilterType === 'skill') this.filterSkill = val;
else if (this.currentFilterType === 'price') this.filterPrice = val;
else if (this.currentFilterType === 'exp') this.filterExp = val;
else if (this.currentFilterType === 'sort') this.sortBy = val;
this.showFilterPanel = false;
},
onDateConfirm() {
if (this.dateStart && this.dateEnd) {
this.filterDateRange = [this.dateStart, this.dateEnd];
} else {
this.filterDateRange = [];
}
this.showDateCalendar = false;
},
onRefresh() {
setTimeout(() => { this.refreshing = false; vant.showToast('刷新成功'); }, 1000);
},
onLoad() { this.loading = false; this.finished = true; },
viewNurse(nurse) {
vant.showDialog({
title: nurse.name,
message: `评分: ${nurse.rating}分\n价格: ¥${nurse.price}/小时\n经验: ${nurse.exp}年\n服务: ${nurse.orders}次\n资质: ${nurse.certs.join('、')}\n技能: ${nurse.skills.join('、')}`,
});
},
bookNurse(nurse) {
vant.showConfirmDialog({
title: '确认预约',
message: `确定要预约 ${nurse.name} 吗?\n价格: ¥${nurse.price}/小时`,
}).then(() => {
vant.showToast({ message: '预约成功,请等待确认', icon: 'success' });
}).catch(() => {});
},
},
watch: {
activeTab(val) { if (val !== 'nurses') { onTabBarChange(val); } },
},
});
app.use(vant);
app.use(vant.Lazyload);
app.mount('#app');
</script>
{/block}