Files
2026-06-22 11:23:05 +08:00

554 lines
25 KiB
HTML
Raw Permalink 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.
{extend name="PatientAPP/layout" /}
{block name="css"}
<link rel="stylesheet" href="/PatientAPP/Order/create.css" />
{/block}
{block name="app"}
<div id="app" v-cloak>
<!-- 头部区域 -->
<div class="header-section">
<div class="header-top">
<div class="header-left">
<van-icon name="arrow-left" size="20" @click="goBack" />
<span class="page-title">发布订单</span>
</div>
<div class="header-actions">
<div class="icon-btn" @click="onDraft">
<van-icon name="description" size="17" />
<span style="font-size:11px;margin-left:3px;">草稿</span>
</div>
</div>
</div>
</div>
<!-- 表单主体 -->
<div class="form-wrapper">
<!-- 服务类型选择 -->
<div class="form-card form-card-type">
<div class="fc-label"><van-icon name="apps-o" color="#018fa5" size="16" /> 选择服务类型</div>
<div class="type-grid">
<div class="type-item"
:class="{ 'type-active': form.serviceType === item.value }"
v-for="(item, index) in serviceTypes"
:key="index"
@click="selectServiceType(item.value)">
<div class="type-icon-wrap" :class="'wrap-' + item.value">
<van-icon :name="item.icon" size="24" :color="form.serviceType === item.value ? '#fff' : item.color" />
</div>
<span class="type-name">{{ item.label }}</span>
<span class="type-desc">{{ item.desc }}</span>
</div>
</div>
</div>
<!-- 患者信息 -->
<div class="form-card">
<div class="fc-header">
<div class="fc-label"><van-icon name="contact" color="#018fa5" size="16" /> 患者信息</div>
</div>
<div class="fc-body">
<van-field v-model="form.patientName" label="姓名" placeholder="请输入患者姓名" clearable />
<van-field v-model="form.patientPhone" label="联系电话" type="tel" placeholder="请输入联系电话" clearable />
<van-field v-model="form.patientAge" label="年龄" type="digit" placeholder="请输入年龄" />
<van-field v-model="form.patientGender" label="性别" readonly placeholder="请选择" is-link @click="showGenderPicker = true" />
</div>
</div>
<!-- 服务时间 -->
<div class="form-card">
<div class="fc-header">
<div class="fc-label"><van-icon name="clock-o" color="#018fa5" size="16" /> 服务时间</div>
</div>
<div class="fc-body">
<van-field
:model-value="form.startDate || ''"
label="开始日期"
readonly
placeholder="请选择开始日期"
is-link
@click="openStartDatePicker"
/>
<van-field
:model-value="form.startTime || ''"
label="开始时间"
readonly
placeholder="请选择开始时间"
is-link
@click="openStartTimePicker"
/>
<van-field
:model-value="form.endDate || ''"
label="结束日期"
readonly
placeholder="请先选开始日期"
is-link
@click="openEndDatePicker"
/>
<van-field
:model-value="form.endTime || ''"
label="结束时间"
readonly
placeholder="请先选结束日期"
is-link
@click="openEndTimePicker"
/>
<div class="duration-tip" v-if="calcDuration">
<van-icon name="info-o" size="14" />
<span>预计服务时长:<strong>{{ calcDuration }}</strong></span>
</div>
</div>
</div>
<!-- 服务地点 -->
<div class="form-card">
<div class="fc-header">
<div class="fc-label"><van-icon name="location-o" color="#018fa5" size="16" /> 服务地点</div>
<van-tag type="primary" size="medium" plain round @click="useCurrentLocation">使用当前位置</van-tag>
</div>
<div class="fc-body">
<van-field v-model="form.hospitalName" label="医院/地点" placeholder="请输入医院名称或服务地点" />
<van-field
v-model="form.detailAddress"
label="详细地址"
type="textarea"
rows="2"
autosize
placeholder="请输入详细地址,如病房号、楼层等"
/>
</div>
</div>
<!-- 护工要求 -->
<div class="form-card">
<div class="fc-header">
<div class="fc-label"><van-icon name="shield-o" color="#018fa5" size="16" /> 护工要求</div>
</div>
<div class="fc-body">
<van-field v-model="form.nurseGender" label="护工性别" readonly placeholder="不限" is-link @click="showNurseGenderPicker = true" />
<van-field v-model="form.experienceReq" label="经验要求" readonly placeholder="不限" is-link @click="showExpPicker = true" />
<div class="skill-tags-section">
<div class="skill-tags-label">特殊技能需求(可多选)</div>
<div class="skill-tags-list">
<span
class="skill-tag"
:class="{ 'skill-active': form.skills.includes(skill) }"
v-for="(skill, index) in skillOptions"
:key="index"
@click="toggleSkill(skill)"
>{{ skill }}</span>
</div>
</div>
</div>
</div>
<!-- 备注说明 -->
<div class="form-card">
<div class="fc-header">
<div class="fc-label"><van-icon name="edit" color="#018fa5" size="16" /> 备注说明</div>
</div>
<div class="fc-body">
<van-field
v-model="form.remark"
type="textarea"
rows="4"
autosize
maxlength="200"
show-word-limit
placeholder="请详细描述患者的身体状况、护理需求等特殊情况,以便匹配合适的护工..."
/>
</div>
</div>
<!-- 费用预估 -->
<div class="form-card price-preview" v-if="estimatedPrice > 0">
<div class="price-row">
<span class="price-label">费用预估</span>
<div class="price-value">
<span class="yen">&yen;</span>
<span class="amount">{{ estimatedPrice }}</span>
</div>
</div>
<div class="price-detail">
<div class="price-item">
<span>基础费用({{ basePrice }}元/小时 x {{ totalHours }}小时)</span>
<span>&yen;{{ basePrice * totalHours }}</span>
</div>
<div class="price-item" v-if="form.skills.length > 0">
<span>技能加成({{ form.skills.length }}项技能 +10%</span>
<span>&yen;{{ Math.round(basePrice * totalHours * 0.1) }}</span>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="bottom-bar">
<div class="bottom-left">
<div class="total-price">
<span class="tp-label">预估总价:</span>
<span class="tp-value"><span class="tp-yen">&yen;</span>{{ estimatedPrice || '0' }}</span>
</div>
<div class="tp-desc">实际价格以接单后确认为准</div>
</div>
<van-button type="primary" class="submit-btn" round :loading="submitting" @click="submitOrder">
发布订单
</van-button>
</div>
<!-- 性别选择器 -->
<van-popup v-model:show="showGenderPicker" position="bottom" round>
<van-picker :columns="genderColumns" @confirm="onGenderConfirm" @cancel="showGenderPicker = false" title="选择性别" />
</van-popup>
<!-- 护工性别选择器 -->
<van-popup v-model:show="showNurseGenderPicker" position="bottom" round>
<van-picker :columns="nurseGenderColumns" @confirm="onNurseGenderConfirm" @cancel="showNurseGenderPicker = false" title="选择护工性别" />
</van-popup>
<!-- 经验要求选择器 -->
<van-popup v-model:show="showExpPicker" position="bottom" round>
<van-picker :columns="expColumns" @confirm="onExpConfirm" @cancel="showExpPicker = false" title="选择经验要求" />
</van-popup>
<!-- 开始日期选择器 -->
<van-popup v-model:show="showStartDatePicker" position="bottom" round>
<van-date-picker
:key="'sd-' + startDateKey"
v-model="startDatePickerValue"
title="选择开始日期"
:min-date="minDate"
:max-date="maxDate"
@confirm="onStartDateConfirm"
@cancel="showStartDatePicker = false"
/>
</van-popup>
<!-- 开始时间选择器 -->
<van-popup v-model:show="showStartTimePicker" position="bottom" round>
<van-time-picker
:key="'st-' + startTimeKey"
v-model="startTimePickerValue"
title="选择开始时间"
:columns-type="['hour', 'minute']"
@confirm="onStartTimeConfirm"
@cancel="showStartTimePicker = false"
/>
</van-popup>
<!-- 结束日期选择器 -->
<van-popup v-model:show="showEndDatePicker" position="bottom" round>
<van-date-picker
:key="'ed-' + endDateKey"
v-model="endDatePickerValue"
title="选择结束日期"
:min-date="startMinEndDate"
:max-date="maxDate"
@confirm="onEndDateConfirm"
@cancel="showEndDatePicker = false"
/>
</van-popup>
<!-- 结束时间选择器 -->
<van-popup v-model:show="showEndTimePicker" position="bottom" round>
<van-time-picker
:key="'et-' + endTimeKey"
v-model="endTimePickerValue"
title="选择结束时间"
:columns-type="['hour', 'minute']"
@confirm="onEndTimeConfirm"
@cancel="showEndTimePicker = false"
/>
</van-popup>
</div>
{/block}
{block name="js"}
<script>
const app = Vue.createApp({
data() {
const now = new Date();
return {
submitting: false,
showGenderPicker: false,
showNurseGenderPicker: false,
showExpPicker: false,
minDate: new Date(),
maxDate: new Date(Date.now() + 365 * 86400000),
// 开始日期选择器
showStartDatePicker: false,
startDateKey: 0,
startDatePickerValue: ['2026', '5', '9'],
// 开始时间选择器
showStartTimePicker: false,
startTimeKey: 0,
startTimePickerValue: ['8', '0'],
// 结束日期选择器
showEndDatePicker: false,
endDateKey: 0,
endDatePickerValue: ['2026', '5', '10'],
// 结束时间选择器
showEndTimePicker: false,
endTimeKey: 0,
endTimePickerValue: ['18', '0'],
genderColumns: ['男', '女'],
nurseGenderColumns: ['不限', '男', '女'],
expColumns: ['不限', '1年以上', '3年以上', '5年以上', '8年以上'],
serviceTypes: [
{ value: 'hospital', label: '住院陪护', icon: 'hotel-o', desc: '院内专业护理', color: '#018fa5' },
{ value: 'outpatient', label: '门诊陪护', icon: 'guide-o', desc: '陪同就诊检查', color: '#e08c28' },
{ value: 'home', label: '居家陪护', icon: 'home-o', desc: '上门居家照料', color: '#2ba245' },
{ value: 'maternal', label: '母婴护理', icon: 'like-o', desc: '月子期专业照护', color: '#e91e8b' },
],
skillOptions: ['术后护理', '老年照护', '康复训练', '输液换药', '重症监护', '母婴护理', '营养配餐', '心理疏导', '行动辅助', '中医理疗'],
form: {
serviceType: '',
patientName: '',
patientPhone: '',
patientAge: '',
patientGender: '',
startDate: '',
startTime: '',
endDate: '',
endTime: '',
hospitalName: '',
detailAddress: '',
nurseGender: '',
experienceReq: '',
skills: [],
remark: '',
},
basePriceMap: {
hospital: 68,
outpatient: 58,
home: 52,
maternal: 88,
},
};
},
computed: {
startMinEndDate() {
if (!this.form.startDate) return this.minDate;
const d = new Date(this.form.startDate.replace(/-/g, '/'));
return d;
},
totalHours() {
if (!this.form.startDate || !this.form.endDate) return 0;
const start = new Date(`${this.form.startDate.replace(/-/g, '/')} ${this.form.startTime || '00:00'}`);
const end = new Date(`${this.form.endDate.replace(/-/g, '/')} ${this.form.endTime || '00:00'}`);
let diff = (end - start) / 3600000;
if (diff <= 0) diff = 0;
return Math.round(diff * 10) / 10;
},
totalDays() {
if (!this.form.startDate || !this.form.endDate) return 0;
const s = new Date(this.form.startDate.replace(/-/g, '/'));
const e = new Date(this.form.endDate.replace(/-/g, '/'));
return Math.ceil((e - s) / 86400000);
},
basePrice() {
return this.basePriceMap[this.form.serviceType] || 60;
},
estimatedPrice() {
if (this.totalHours === 0) return 0;
let price = this.basePrice * this.totalHours;
if (this.form.skills.length > 0) {
price = Math.round(price * 1.1);
}
return price;
},
calcDuration() {
if (!this.form.startDate || !this.form.endDate) return '';
const days = this.totalDays;
const hours = this.totalHours;
if (days <= 0 && hours <= 0) return '';
if (days > 0) return `${days}${hours}小时`;
return `${hours}小时`;
},
},
methods: {
goBack() { history.back(); },
onDraft() { vant.showToast('暂无草稿'); },
selectServiceType(type) { this.form.serviceType = type; },
useCurrentLocation() {
vant.showToast({ message: '正在获取位置...', loading: true, duration: 1500 });
setTimeout(() => {
this.form.hospitalName = '当前位置附近';
vant.clearToast();
vant.showToast('已获取位置');
}, 1000);
},
toggleSkill(skill) {
const idx = this.form.skills.indexOf(skill);
if (idx > -1) {
this.form.skills.splice(idx, 1);
} else {
this.form.skills.push(skill);
}
},
padZero(n) { return String(n).padStart(2, '0'); },
openStartDatePicker() {
if (this.form.startDate) {
const p = this.form.startDate.split('-');
this.startDatePickerValue = [p[0], String(parseInt(p[1])), String(parseInt(p[2]))];
} else {
const now = new Date();
this.startDatePickerValue = [
String(now.getFullYear()),
String(now.getMonth() + 1),
String(now.getDate())
];
}
this.startDateKey++;
this.showStartDatePicker = true;
},
openStartTimePicker() {
if (!this.form.startDate) {
vant.showToast('请先选择开始日期');
return;
}
if (this.form.startTime) {
const p = this.form.startTime.split(':');
this.startTimePickerValue = [String(parseInt(p[0])), String(parseInt(p[1]))];
} else {
this.startTimePickerValue = ['8', '0'];
}
this.startTimeKey++;
this.showStartTimePicker = true;
},
openEndDatePicker() {
if (!this.form.startDate) {
vant.showToast('请先选择开始日期');
return;
}
if (this.form.endDate) {
const p = this.form.endDate.split('-');
this.endDatePickerValue = [p[0], String(parseInt(p[1])), String(parseInt(p[2]))];
} else {
const start = new Date(this.form.startDate.replace(/-/g, '/'));
start.setDate(start.getDate() + 1);
this.endDatePickerValue = [
String(start.getFullYear()),
String(start.getMonth() + 1),
String(start.getDate())
];
}
this.endDateKey++;
this.showEndDatePicker = true;
},
openEndTimePicker() {
if (!this.form.endDate) {
vant.showToast('请先选择结束日期');
return;
}
if (this.form.endTime) {
const p = this.form.endTime.split(':');
this.endTimePickerValue = [String(parseInt(p[0])), String(parseInt(p[1]))];
} else {
this.endTimePickerValue = ['18', '0'];
}
this.endTimeKey++;
this.showEndTimePicker = true;
},
onStartDateConfirm({ selectedValues }) {
const v = selectedValues;
this.form.startDate = `${v[0]}-${this.padZero(v[1])}-${this.padZero(v[2])}`;
// 如果已选结束日期早于开始日期,清空结束日期
if (this.form.endDate && new Date(this.form.endDate.replace(/-/g, '/')) < new Date(this.form.startDate.replace(/-/g, '/'))) {
this.form.endDate = '';
this.form.endTime = '';
}
this.showStartDatePicker = false;
},
onStartTimeConfirm({ selectedValues }) {
const v = selectedValues;
this.form.startTime = `${this.padZero(v[0])}:${this.padZero(v[1])}`;
this.showStartTimePicker = false;
},
onEndDateConfirm({ selectedValues }) {
const v = selectedValues;
this.form.endDate = `${v[0]}-${this.padZero(v[1])}-${this.padZero(v[2])}`;
// 校验结束日期不能早于开始日期
if (new Date(this.form.endDate.replace(/-/g, '/')) < new Date(this.form.startDate.replace(/-/g, '/'))) {
vant.showToast('结束日期不能早于开始日期');
this.form.endDate = '';
return;
}
this.showEndDatePicker = false;
},
onEndTimeConfirm({ selectedValues }) {
const v = selectedValues;
const endTimeStr = `${this.padZero(v[0])}:${this.padZero(v[1])}`;
// 校验结束时间须晚于开始时间
if (this.form.endDate === this.form.startDate && this.form.startTime) {
const endFull = new Date(`${this.form.endDate.replace(/-/g, '/')} ${endTimeStr}`);
const startFull = new Date(`${this.form.startDate.replace(/-/g, '/')} ${this.form.startTime}`);
if (endFull <= startFull) {
vant.showToast('结束时间须晚于开始时间');
return;
}
}
this.form.endTime = endTimeStr;
this.showEndTimePicker = false;
},
onGenderConfirm({ selectedOptions }) { this.form.patientGender = selectedOptions[0]?.text || ''; this.showGenderPicker = false; },
onNurseGenderConfirm({ selectedOptions }) { this.form.nurseGender = selectedOptions[0]?.text || ''; this.showNurseGenderPicker = false; },
onExpConfirm({ selectedOptions }) { this.form.experienceReq = selectedOptions[0]?.text || ''; this.showExpPicker = false; },
formatDateOnly(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
},
validateForm() {
if (!this.form.serviceType) { vant.showToast('请选择服务类型'); return false; }
if (!this.form.patientName) { vant.showToast('请输入患者姓名'); return false; }
if (!this.form.patientPhone) { vant.showToast('请输入联系电话'); return false; }
if (!/^1\d{10}$/.test(this.form.patientPhone)) { vant.showToast('手机号格式不正确'); return false; }
if (!this.form.startDate || !this.form.startTime) { vant.showToast('请选择开始服务时间'); return false; }
if (!this.form.endDate || !this.form.endTime) { vant.showToast('请选择结束服务时间'); return false; }
if (this.totalHours <= 0) { vant.showToast('结束时间须晚于开始时间'); return false; }
if (!this.form.hospitalName) { vant.showToast('请输入服务地点'); return false; }
return true;
},
submitOrder() {
if (!this.validateForm()) return;
vant.showConfirmDialog({
title: '确认发布',
message: `服务类型:${this.serviceTypes.find(s => s.value === this.form.serviceType)?.label}\n服务时间:${this.form.startDate} ${this.form.startTime} ~ ${this.form.endDate} ${this.form.endTime}\n服务时长:${this.calcDuration}\n预估费用:¥${this.estimatedPrice}`,
confirmButtonText: '确认发布',
cancelButtonText: '再想想',
}).then(() => {
this.submitting = true;
setTimeout(() => {
this.submitting = false;
vant.showDialog({
title: '发布成功',
message: '订单已发布,系统将为您匹配合适的护工,请耐心等待接单。',
confirmButtonText: '查看订单',
}).then(() => {
window.location.href = '/PatientApp/Order/index';
});
}, 1500);
}).catch(() => {});
},
},
watch: {},
});
app.use(vant);
app.use(vant.Lazyload);
app.mount('#app');
</script>
{/block}