Files
2026-11-10 18:03:52 +08:00

947 lines
50 KiB
Vue
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.
<template>
<div class="container-fluid">
<div>
<v-tabs>
<v-tab value="tab-1" @click="tab='tab-1'">
掛單資訊
</v-tab>
<v-tab value="tab-2" @click="tab='tab-2'">
掛單明細
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<!-- ================== Tab 1: 掛單資訊 ================== -->
<v-tab-item value="tab-1">
<div class="border rounded p-3 bg-white shadow-sm mt-3">
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">掛單單號(不可修改)</label>
<div class="col-sm-4 text-left">
<input class="form-control" v-model="guadanorder.order_form.orderNo" readonly />
</div>
<label class="col-sm-2 col-form-label text-center">關聯活動</label>
<div class="col-sm-4">
<select class="form-control" v-model="activityNum">
<option :value="null">未關聯</option>
<option v-for="activity in activityList" :key="activity.num" :value="activity.num">
{{activity.subject}}
</option>
</select>
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">預約開始日期</label>
<div class="col-sm-4 text-left">
<input class="form-control" type="date" disabled v-model="guadanorder.order_form.startdate" />
</div>
<label class="col-sm-2 col-form-label text-center">預約結束日期</label>
<div class="col-sm-4">
<input class="form-control" type="date" disabled v-model="guadanorder.order_form.enddate" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">預定人姓名</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerName" />
</div>
<label class="col-sm-2 col-form-label text-center">預定人電話</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerPhone" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">備註</label>
<div class="col-sm-4">
<textarea class="form-control" v-model="guadanorder.order_form.note"></textarea>
</div>
</div>
</div>
<div class="d-flex mb-3">
<!--v-if="guadanorder.order_form.uuid"-->
<button type="button" class="btn btn-primary mx-auto" style="width: 120px;" @click="updateGuadanOrder">
存檔
</button>
</div>
</v-tab-item>
<!-- ================== Tab 2: 掛單明細 ================== -->
<v-tab-item value="tab-2">
<v-row class="mt-2">
<!-- 左側:掛單明細列表 -->
<v-col cols="12" md="8">
<div class="d-flex align-items-center mb-3">
<v-btn color="primary" @click="showCheckInModal" class="me-3">
<v-icon left>mdi-plus</v-icon>設定人數
</v-btn>
<v-btn @click="showAutomaticBedAllocation">
自動分配床位
</v-btn>
</div>
<v-data-table :headers="guadanguest.headers" :items="guadanguest.items" class="elevation-1 rounded" dense>
<template #item.checkinat="{item}">
{{item.checkinat |timeString('YYYY-MM-DD')}}
</template>
<template #item.checkoutat="{item}">
{{item.checkoutat |timeString('YYYY-MM-DD')}}
</template>
<!--<template v-slot:item.name="{item}">-->
<!--</template>-->
<!-- 姓名欄位改為輸入框:未輸入時預設顯示當前順位編號 -->
<template v-slot:item.name="{item}">
<v-text-field v-model="item.fullName"
dense
hide-details
outlined
class="my-1"
:placeholder="item.fullName==null?item.name:(String(guadanguest.items.indexOf(item) + 1))"
style="min-width: 50px; font-size: 14px;"></v-text-field>
</template>
<template v-slot:item.sex="{item}">
{{item.gender}}
</template>
<template v-slot:item.room="{item}">
<span v-if="item.bedName || item.room">
{{ item.room ? item.room.resion.join("/") : '' }}
{{ item.room ? item.room.name : '' }}
{{ item.bedName || '' }}
</span>
<span v-else class="text-muted">
未分配床位
</span>
</template>
<template v-slot:item.indicators="{item}">
<div class="d-flex justify-center align-center">
<v-chip small :color="getStatusColor(item.statuscode)" text-color="white" class="me-2" style="font-size: 12px; height: 20px;">
{{ item.statusName || '待入住' }}
</v-chip>
<v-tooltip bottom v-if="item.note">
<template v-slot:activator="{ on, attrs }">
<v-icon color="warning" v-bind="attrs" v-on="on" size="20" class="me-1">mdi-note-text</v-icon>
</template>
<span>備註: {{ item.note }}</span>
</v-tooltip>
<v-icon v-else color="grey lighten-3" size="20" class="me-1">mdi-note-text-outline</v-icon>
<v-tooltip bottom v-if="item.specialNeeds && item.specialNeeds !== '無特殊需求'">
<template v-slot:activator="{ on, attrs }">
<v-icon color="error" v-bind="attrs" v-on="on" size="20">mdi-alert-circle</v-icon>
</template>
<span>特殊需求: {{ getSpecialNeedsText(item.specialNeeds) }}</span>
</v-tooltip>
<v-icon v-else color="grey lighten-3" size="20">mdi-alert-circle-outline</v-icon>
</div>
</template>
<template #item.actions="{ item }">
<div class="d-flex align-center">
<v-btn color="success" outlined small class="me-2" @click="selectGuestForBed(item)">
<v-icon left size="16">mdi-bed</v-icon>選床
</v-btn>
<v-menu offset-y transition="slide-y-transition">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" text small v-bind="attrs" v-on="on">
更多 <v-icon right size="18">mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item :disabled="item.statuscode !== '401'" @click="changeGuadanGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-cancel</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">變更</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '401'" @click="confirmDeleteGuadanOrderGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-cancel</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">取消預訂</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '401'" @click="checkinGuadanGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="error" size="20">mdi-login</v-icon></v-list-item-icon>
<v-list-item-title class="error--text">入住</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '402'" @click="showXuzhuGuestModalMethod(item)">
<v-list-item-icon class="me-2"><v-icon color="warning" size="20">mdi-calendar-plus</v-icon></v-list-item-icon>
<v-list-item-title class="warning--text">續住</v-list-item-title>
</v-list-item>
<v-list-item :disabled="item.statuscode !== '402'" @click="checkoutGuadanOrderGuest(item)">
<v-list-item-icon class="me-2"><v-icon color="primary" size="20">mdi-exit-run</v-icon></v-list-item-icon>
<v-list-item-title class="primary--text">退房</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</template>
</v-data-table>
</v-col>
<!-- 右側:可選的大樓與房間床位 -->
<v-col cols="12" md="4">
<v-card class="elevation-1 h-100" style="min-height: 400px;">
<v-card-title class="bg-light">
分配床位
</v-card-title>
<v-divider></v-divider>
<v-card-text v-if="selectedGuestForBed">
<div class="mb-3 text-primary font-weight-bold">
目前選擇:{{ selectedGuestForBed.fullName || (guadanguest.items.indexOf(selectedGuestForBed) + 1) }} ({{ selectedGuestForBed.gender }})
</div>
<!-- 最外層:大樓 -->
<v-expansion-panels v-model="activeBuildingPanel" accordion>
<v-expansion-panel v-for="building in filteredMockBuildings" :key="building.id">
<v-expansion-panel-header class="font-weight-bold">
<div class="d-flex align-center">
<v-icon color="indigo" class="me-2" size="20">mdi-office-building-marker</v-icon>
{{ building.name }} (剩餘: {{ buildingAvailableBedsCount(building) }})
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
<!-- 內層:房間與床位 (直接以列表呈現,避免雙層手風琴太雜亂) -->
<div v-for="(room, index) in building.rooms" :key="room.id" :class="{'mt-4': index > 0}">
<!-- 房間標題 -->
<div class="text-subtitle-2 mb-1 text-indigo">
<v-icon small color="indigo" class="me-1">mdi-door-closed</v-icon>
{{ room.name }} (剩餘: {{ roomAvailableBedsCount(room) }})
</div>
<!-- 床位列表 (加入 Tooltip 提示特殊條件) -->
<v-chip-group column>
<template v-for="bed in room.beds">
<!-- 封裝 Tooltip:有條件時提示內容,沒條件時提示無特殊限制 -->
<v-tooltip bottom :key="bed.id">
<template v-slot:activator="{ on, attrs }">
<v-chip :color="bed.id === selectedGuestForBed.bedUuid ? 'success' : (bed.isAvailableNow ? 'primary' : 'grey')"
:text-color="bed.id === selectedGuestForBed.bedUuid || bed.isAvailableNow ? 'white' : 'black'"
:disabled="!bed.isAvailableNow && bed.id !== selectedGuestForBed.bedUuid"
@click="assignBedToGuest(building, room, bed)"
v-bind="attrs"
v-on="on"
small>
{{ bed.name }}
<v-icon v-if="bed.id === selectedGuestForBed.bedUuid" right size="14">mdi-check-circle</v-icon>
<!-- 若床位有特殊條件,加上小圖示標示 -->
<v-icon v-if="bed.limits && bed.limits.length > 0" right size="12" color="amber lighten-3">mdi-star</v-icon>
</v-chip>
</template>
<span>
{{ bed.name }}
<template v-if="bed.limits && bed.limits.length > 0">
<br>【具備條件】:{{ getSpecialNeedsText(Array.isArray(bed.limits) ? bed.limits.join(',') : String(bed.limits)) }}
</template>
<template v-else>
<br>(無特殊限制床位)
</template>
</span>
</v-tooltip>
</template>
</v-chip-group>
<v-divider v-if="index < building.rooms.length - 1" class="mt-2"></v-divider>
</div>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<div v-if="filteredMockBuildings.length === 0" class="text-danger mt-3">
沒有符合該性別條件的大樓與房間。
</div>
</v-card-text>
<v-card-text v-else class="text-center mt-10 text-muted">
<v-icon size="64" color="grey lighten-2">mdi-hand-pointing-left</v-icon>
<div class="mt-4">請點選左側名單的「選床」按鈕<br>開始分配床位</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-tab-item>
</v-tabs-items>
</div>
<v-dialog v-model="message_dialog" max-width="500">
<v-card>
<v-card-title class="text-h6">系統提示</v-card-title>
<v-card-text class="pt-4">{{message}}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="message_dialog = false">確認</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="checkInGuest.showSelectGuadanOrderGuest" max-width="600">
<v-card>
<v-card-title>設定人數條件</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12"><v-text-field label="人數" type="number" v-model="e4"></v-text-field></v-col>
<v-col cols="12">
<v-menu v-model="menu2" :close-on-content-click="false" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-model="s_date" label="開始日期" prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="s_date" @input="menu2 = false"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12">
<v-menu v-model="menu3" :close-on-content-click="false" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-model="e_date" label="結束日期" prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="e_date" @input="menu3 = false"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12">
<v-select :items="e2" v-model="e5" label="性別"></v-select>
</v-col>
<v-col cols="12">
<v-select v-model="e6" :items="states" item-text="text" item-value="value" label="特殊需求" multiple hint="選擇條件" persistent-hint></v-select>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="checkInGuest.showSelectGuadanOrderGuest = false">關閉</v-btn>
<v-btn color="primary" @click.prevent="genGuestList">產生</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
module.exports = {
name: "edit-component",
props: {
formData: {
type: String,
default: "",
}
},
data() {
return {
tab: "tab-1",
e1: null,
e2: ['請選擇', '男眾', '女眾'],
e4: "",
e5: "",
e6: [],
s_date: null,
menu2: false,
menu3: false,
e_date: null,
states: [
//'無特殊需求', '需要輪椅', '需要拐杖', '需要陪同人員', '需要特別飲食', '其他需求'
],
message_dialog: false,
message: "",
activityList: [],
activityNum: null,
availableBedCount: { male: 0, female: 0 },
guadanorder: {
order_form: {
uuid: null,
startdate: null,
enddate: null,
note: null,
orderNo: null,
bookerName: null,
bookerPhone: null,
bookerFollowerNum: null,
activityNum: null,
},
},
guadanguest: {
headers: [
{ text: '標記/狀態', value: 'indicators', align: 'center', sortable: false, width: '130px' },
{ text: '姓名', value: 'name' },
{ text: '性別', value: 'sex' },
{ text: '掛單開始時間', value: 'checkinat' },
{ text: '掛單結束時間', value: 'checkoutat' },
{ text: '床位', value: 'room' },
{ text: '操作', value: 'actions', sortable: false },
],
items: [],
},
checkInGuest: {
showSelectGuadanOrderGuest: false,
inGuest: {
checkInAt: null,
checkOutAt: null,
orderNo: null,
}
},
automaticBedAllocation: {
showModal: false,
},
selectedGuestForBed: null,
activeBuildingPanel: null, // 控制大樓的展開面板
// 模擬資料層級: 大樓 -> 房間 -> 床位
mockBuildings: [],
}
},
watch: {
activityNum: {
handler(newVal) {
let act = this.activityList.find(activity => activity.num == newVal);
if (act) {
this.guadanorder.order_form.startdate = act.startDate;
this.guadanorder.order_form.enddate = act.endDate;
}
},
deep: true,
immediate: true
},
formData: {
handler(newVal) {
//用掛單的uuid去抓掛單資料跟明細檔
console.log('掛單uuid', newVal);
if (newVal != "") {
this.getGuadanData();
}
},
deep: true,
immediate: true
}
},
mounted() {
this.getActivityList()
this.getGuadanLimits()
},
computed: {
assignedBedUuids() {
return this.guadanguest.items
.map(item => item.bedUuid)
.filter(uuid => uuid);
},
filteredMockBuildings() {
if (!this.selectedGuestForBed) return [];
const guestGender = this.selectedGuestForBed.gender;
const currentGuestBedUuid = this.selectedGuestForBed.bedUuid;
const guestNeeds = (this.selectedGuestForBed.specialNeeds || '').split(',').filter(c => c);
return this.mockBuildings.map(building => {
// 過濾房間並處理床位動態狀態
const filteredRooms = building.rooms.map(room => {
return {
...room,
beds: room.beds.map(bed => {
const isTakenByOthers = this.assignedBedUuids.includes(bed.id) && bed.id !== currentGuestBedUuid;
// 檢查條件是否完全滿足
const bedConditions = bed.limits || [];
const isMatchConditions = guestNeeds.every(need => bedConditions.includes(need));
return {
...bed,
isAvailableNow: bed.isAvailable && !isTakenByOthers && isMatchConditions,
isMatchConditions: isMatchConditions
};
})
};
}).filter(room =>
!guestGender || guestGender === '請選擇' || room.genderLimit === guestGender
);
return {
...building,
rooms: filteredRooms
};
}).filter(building => building.rooms.length > 0); // 隱藏完全沒有符合條件房間的大樓
}
},
methods: {
getGuadanData() {//抓出掛單資料根明細檔
let self = this;
axios.post(HTTP_HOST + 'api/guadan/getorderbyuuid', { uuid: self.formData }).then((res) => {
if (res.status == 200) {
console.log('掛單資料', res.data);
self.guadanorder.order_form = res.data.order_form;
self.guadanguest.items = res.data.details;
}
})
},
getStatusColor(code) {
switch (code) {
case '401': return 'blue';
case '402': return 'green';
default: return 'grey';
}
},
// 算整棟大樓的可用床位總和
buildingAvailableBedsCount(building) {
let count = 0;
building.rooms.forEach(room => {
count += room.beds.filter(b => b.isAvailableNow).length;
});
return count;
},
// 算單一房間的可用床位
roomAvailableBedsCount(room) {
return room.beds.filter(b => b.isAvailableNow).length;
},
getSpecialNeedsText(specialNeedsCodes) {
if (!specialNeedsCodes) return '';
const codes = specialNeedsCodes.split(',');
let self = this;
const names = codes.map(code => {
const target = self.states.find(s => s.value === code);
return target ? target.text : code;
});
return names.join('、');
},
getGuadanLimits() {//抓掛單條件
this.states.length = 0; // 清空陣列
let self = this;
let data = [];
axios.post(HTTP_HOST + 'api/guadan/getGuadanLimit').then((res) => {
if (res.status == 200) {
res.data.forEach(item => {
self.states.push({
text: item.paramName,
value: item.paramNo
});
})
}
})
},
genGuestList() {
if (!this.e4) { console.log('請輸入人數'); return; }
if (!this.e5 || this.e5 == 0) { console.log("請選擇男眾或女眾"); return; }
if (!this.s_date) { console.log('請選擇開始日期'); return; }
if (!this.e_date) { console.log('請選擇結束日期'); return; }
const today = moment().format('YYYY-MM-DD');
if (this.s_date < today) {
this.message = '開始日期不可小於今天';
this.message_dialog = true;
return;
}
// 檢查 2:結束日期不可小於開始日期
if (this.e_date < this.s_date) {
this.message = '結束日期不可小於開始日期';
this.message_dialog = true;
return;
}
for (let i = 0; i < this.e4; i++) {
const tempUuid = 'temp-' + Date.now() + '-' + i;
this.guadanguest.items.push({
uuid: tempUuid,
fullName: '',
gender: this.e5,
checkinat: this.s_date,
checkoutat: this.e_date,
statuscode: '401',
statusName: '已預訂',
note: '',
specialNeeds: this.e6.join(",")
});
}
this.s_date = null;
this.e_date = null;
this.e5 = "";
this.e4 = null;
this.e6 = [];
this.checkInGuest.showSelectGuadanOrderGuest = false;
},
getActivityList() {
const params = { page: '1', pageSize: 10000 };
axios.post(HTTP_HOST + 'api/activity/GetListNewYet', {
up_time1: moment(new Date()).format('YYYY-MM-DD')
}, { params: params }).then((res) => {
if (res.status == 200 && res.data && res.data.list) {
this.activityList = res.data.list;
}
})
},
validateOrderForm() {
if (!this.guadanorder.order_form.startdate) { this.message = '請輸入開始日期'; this.message_dialog = true; return false; }
if (!this.guadanorder.order_form.enddate) { this.message = '請輸入結束日期'; this.message_dialog = true; return false; }
const startDate = this.guadanorder.order_form.startdate;
const endDate = this.guadanorder.order_form.enddate;
// 取得今天日期字串 (YYYY-MM-DD)
const today = moment().format('YYYY-MM-DD');
// 檢查 1:預約開始日期不可小於今天
if (startDate < today) {
this.message = '預約開始日期不可小於今天';
this.message_dialog = true;
return false;
}
// 檢查 2:預約結束日期不可小於預約開始日期
if (endDate < startDate) {
this.message = '預約結束日期不可小於預約開始日期';
this.message_dialog = true;
return false;
}
if (!this.guadanorder.order_form.bookerName) { this.message = '請輸入姓名'; this.message_dialog = true; return false; }
if (!this.guadanorder.order_form.bookerPhone) { this.message = '請輸入電話'; this.message_dialog = true; return false; }
// 檢查 3:若有掛單明細,檢查是否有「未分配床位」的人員
if (this.guadanguest.items && this.guadanguest.items.length > 0) {
// 找出沒有 bedUuid 的人員
const unassignedGuests = this.guadanguest.items.filter(item => !item.bedUuid);
if (unassignedGuests.length > 0) {
// 找出第一個未分配人員的顯示名稱或順位號碼
const firstUnassignedIdx = this.guadanguest.items.indexOf(unassignedGuests[0]) + 1;
const guestIdentifier = unassignedGuests[0].fullName || `第 ${firstUnassignedIdx} 位預訂人`;
this.message = `無法存檔!尚有 ${unassignedGuests.length} 位人員未分配床位(例如:${guestIdentifier})。請先完成床位分配再存檔!`;
this.message_dialog = true;
return false;
}
}
return true;
},
createGuadanOrder() {
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/create', this.guadanorder.order_form)
.then((res => {
this.message_dialog = true
this.message = '掛單資料建立成功'
this.guadanorder.order_form.uuid = res.data.uuid;
this.guadanorder.order_form.orderNo = res.data.guaDanOrderNo;
const currentUrl = window.location.origin + window.location.pathname;
window.location.href = `${currentUrl}?orderId=${res.data.guaDanOrderNo}`;
})).catch((error) => {
this.message_dialog = true
this.message = '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
});
},
updateGuadanOrder() {
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/updateGandan', { order: this.guadanorder.order_form })
.then((res => {
this.message_dialog = true
this.message = '掛單資料更新成功'
this.guadanorder.order_form.uuid = res.data.uuid;
})).catch((error) => {
this.message_dialog = true
this.message = '掛單資料更新失敗:' + (error.response?.data?.message || error.message)
});
},
showCheckInModal() {
this.checkInGuest.inGuest.checkInAt = this.guadanorder.order_form.startdate || null;
this.checkInGuest.inGuest.checkOutAt = this.guadanorder.order_form.enddate || null;
this.checkInGuest.inGuest.orderNo = this.guadanorder.order_form.orderNo;
this.s_date = this.guadanorder.order_form.startdate || null;
this.e_date = this.guadanorder.order_form.enddate || null;
this.checkInGuest.showSelectGuadanOrderGuest = true;
},
async showAutomaticBedAllocation() {
console.log(this.guadanguest.items);
// 1. 篩選出尚未分配床位的人員
const unassignedGuests = this.guadanguest.items.filter(item => !item.bedUuid);
if (unassignedGuests.length === 0) {
this.message = '目前所有人員皆已完成床位分配!';
this.message_dialog = true;
return;
}
unassignedGuests.sort((a, b) => {
const needsA = (a.specialNeeds || '').split(',').filter(c => c && c !== '無特殊需求').length;
const needsB = (b.specialNeeds || '').split(',').filter(c => c && c !== '無特殊需求').length;
return needsB - needsA; // 數量多的排在前面 (降冪)
});
let successCount = 0;
let failCount = 0;
// 2. 逐一處理未分配床位的人員
for (const guest of unassignedGuests) {
try {
// 取得該人員入住時間區間的可分配床位
const resp = await axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: guest.checkinat,
endTime: guest.checkoutat
});
const availableBeds = resp.data?.availableBeds || [];
// 過濾出真正的特殊需求 (排除「無特殊需求」與空字串)
const rawNeeds = (guest.specialNeeds || '').split(',');
const guestNeeds = rawNeeds.filter(need => need && need !== '無特殊需求');
// 3. 搜尋合適的床位
const matchedBed = availableBeds.find(bed => {
// A. 床位狀態需為可用 ('101')
if (bed.statusCode !== '101') return false;
// B. 不能已被目前列表中的其他人佔用
if (this.assignedBedUuids.includes(bed.uuid)) return false;
// C. 性別限制過濾
const regionName = bed.room?.regionName || '';
let roomGenderLimit = null;
if (regionName.includes('男眾')) roomGenderLimit = '男眾';
else if (regionName.includes('女眾')) roomGenderLimit = '女眾';
if (guest.gender && guest.gender !== '請選擇' && roomGenderLimit) {
if (guest.gender !== roomGenderLimit) return false;
}
// D. 特殊需求條件檢查 (人員需要的,床位必須具備)
const bedConditions = bed.limits || [];
const isMatchConditions = guestNeeds.every(need => bedConditions.includes(need));
if (!isMatchConditions) return false;
return true;
});
// 4. 若找到合適床位,進行分配
if (matchedBed) {
const buildingName = matchedBed.room?.regionName || '';
const roomName = matchedBed.room?.name || '';
const bedName = matchedBed.name || '';
this.$set(guest, 'bedName', `${buildingName} - ${roomName} - ${bedName}`);
this.$set(guest, 'bedUuid', matchedBed.uuid);
this.$set(guest, 'roomUuid', matchedBed.roomUuid);
successCount++;
} else {
failCount++;
}
} catch (error) {
console.error('自動分配床位發生錯誤:', error);
failCount++;
}
}
// 5. 分配完成提示
this.message = `自動分配完成!成功分配 ${successCount} 位,失敗/無合適床位 ${failCount} 位。`;
this.message_dialog = true;
//if (!this.guadanorder.order_form.uuid) {
// this.message_dialog = true;
// this.message = '請先儲存掛單資訊';
// return;
//}
//this.automaticBedAllocation.showModal = true;
//this.getavailablebedcountbytime(this.guadanorder.order_form.startdate, this.guadanorder.order_form.enddate);
},
getavailablebedcountbytime(startTime, endTime) {
axios.get(HTTP_HOST + 'api/region/bed/getavailablebedcountbytime', {
params: { startTime: startTime, endTime: endTime }
}).then((res) => {
this.availableBedCount = res.data;
})
},
selectGuestForBed(item) {
this.selectedGuestForBed = item;
this.activeBuildingPanel = null;
},
// 參數加入 building 用來串接完整名稱
assignBedToGuest(building, room, bed) {
if (!bed.isAvailableNow && bed.id !== this.selectedGuestForBed.bedUuid) return;
// 1. 安全轉換函數:強制將各種型別轉換為標準陣列
const ensureArray = (val) => {
if (!val) return [];
if (Array.isArray(val)) return val;
if (typeof val === 'string') return val.split(',').filter(c => c && c.trim() !== '');
if (typeof val === 'object') return Object.values(val).filter(c => c);
return [String(val)];
};
// 取出安全的床位條件陣列
const bedConditions = ensureArray(bed.limits);
// 2. 條件比對檢查 (檢核特殊需求)
const guestNeedsStr = this.selectedGuestForBed.specialNeeds || '';
// 將人員需求轉換為代碼陣列 (過濾空值)
const guestNeeds = guestNeedsStr ? guestNeedsStr.split(',').filter(c => c) : [];
// 床位條件 (若後端未傳回則預設為空陣列)
//const bedConditions = bed.limits || [];
// 檢查人員有的需求,床位是否未滿足 (找出不合適的條件代碼)
const unmatchedNeeds = guestNeeds.filter(need => !bedConditions.includes(need));
if (unmatchedNeeds.length > 0) {
// 將未滿足的需求代碼轉換為中文名稱
const unmatchedNames = unmatchedNeeds.map(code => {
const target = this.states.find(s => s.value === code);
return target ? target.text : code;
}).join('、');
// 跳出提示阻擋分配(或改為 confirm 提醒)
this.message = `不符合分配條件!該人員需要「${unmatchedNames}」,但此床位未具備對應條件。`;
this.message_dialog = true;
return; // 中斷分配
}
// 將大樓名稱也組進去顯示
// 2. 取得床位具備的特殊條件文字
let bedConditionNotice = '';
if (bedConditions.length > 0) {
const bedConditionNames = bedConditions.map(code => {
const target = this.states.find(s => s.value === code);
return target ? target.text : code;
}).join('、');
bedConditionNotice = `\n【床位具備條件】:${bedConditionNames}`;
} else {
bedConditionNotice = `\n【床位具備條件】:無特殊限制`;
}
// 3. 設定床位並跳出點擊後的二次提示
this.$set(this.selectedGuestForBed, 'bedName', `${building.name} - ${room.name} - ${bed.name}`);
this.$set(this.selectedGuestForBed, 'bedUuid', bed.id);
this.message = `已將「${building.name} ${room.name} ${bed.name}」分配給該明細!`;
this.message_dialog = true;
},
async getavailablebedbytime(startTime, endTime) {
axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: startTime, endTime: endTime
}).then((res) => {
//this.availableBedCount = res.data;
return res.data;
})
},
async selectGuestForBed(item) {
axios.post(HTTP_HOST + 'api/region/bed/getAvailableBeds', {
startTime: item.checkinat, endTime: item.checkoutat
}).then((resp) => {
const rawData = resp.data;
const transformData = (data) => {
// 1. 使用 reduce 依據 regionUuid 與 roomUuid 進行分群
const grouped = data.availableBeds.reduce((acc, bed) => {
const regionId = bed.room.regionUuid;
const regionName = bed.room.regionName;
const roomId = bed.roomUuid;
const roomName = bed.room.name;
// 自動判斷性別限制 (若區域名稱包含"男眾"則為"男眾",反之女眾)
const genderLimit = regionName.includes('男眾') ? '男眾' : '女眾';
// 假設 statusCode '101' 代表可用 (true),其他代表不可用 (false)
const isAvailable = bed.statusCode === '101';
// 如果大樓/區域還不存在,建立新大樓
if (!acc[regionId]) {
acc[regionId] = {
id: regionId,
name: regionName,
roomsMap: {} // 使用 Map 方便後續依 roomId 查找
};
}
// 如果該大樓下的客房還不存在,建立新客房
if (!acc[regionId].roomsMap[roomId]) {
acc[regionId].roomsMap[roomId] = {
id: roomId,
name: `${roomName} (${genderLimit})`, // 組合區分名稱,如:客房1 (男眾)
genderLimit: genderLimit,
beds: []
};
}
// 將床位資料推入該客房的 beds 陣列中
acc[regionId].roomsMap[roomId].beds.push({
id: bed.uuid,
name: bed.name,
isAvailable: isAvailable,
limits: bed.limits || [],
});
return acc;
}, {});
// 2. 將物件格式扁平化轉換成您要求的陣列格式 []
return Object.values(grouped).map(region => ({
id: region.id,
name: region.name,
rooms: Object.values(region.roomsMap) // 將 rooms 轉換成陣列
}));
};
// 執行轉換
const result = transformData(rawData);
this.mockBuildings = result;
this.selectedGuestForBed = item;
this.activeRoomPanel = null;
})
},
changeGuadanGuest(item) {//可以變更人員、床位
console.log('變更人員、床位', item);
let detail = {
uuid: item.uuid,
fullName: item.fullName,
gender: item.gender,
checkInAt: item.checkinat,
checkOutAt: item.checkoutat,
statuscode: item.statuscode,
statusName: item.statusName,
note: item.note,
handi: item.specialNeeds,
bedUuid: item.bedUuid,
roomUuid: item.roomUuid,
name: item.fullName,
guaDanOrderNo: this.guadanorder.order_form.orderNo
orderUuid: this.guadanorder.order_form.uuid
}
axios.post(HTTP_HOST + 'api/guadan/changeGuadanGuest', { guest: detail })
.then((res => {
this.message_dialog = true
this.message = '掛單資料更新成功'
this.guadanorder.order_form.uuid = res.data.uuid;
})).catch((error) => {
this.message_dialog = true
this.message = '掛單資料更新失敗:' + (error.response?.data?.message || error.message)
});
},
confirmDeleteGuadanOrderGuest(item) {
console.log('取消預訂', item);
},
checkinGuadanGuest(item) {
console.log('入住', item);
},
showXuzhuGuestModalMethod(item) {
console.log('續住', item);
},
checkoutGuadanOrderGuest(item) {
console.log('退房', item);
}
}
}
</script>
<style>
.form-group label {
font-weight: 600;
}
</style>