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

637 lines
30 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" disabled>
<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" disabled type="date" 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" disabled type="date" 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" readonly 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" readonly 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>
</v-tab-item>
<!-- ================== Tab 2: 掛單明細 ================== -->
<v-tab-item value="tab-2">
<v-row class="mt-2">
<!-- 左側:掛單明細列表 -->
<v-col cols="12" md="12">
<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.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.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;max-width:120px; font-size: 14px;"></v-text-field>
</template>
<template v-slot:item.sex="{item}">
{{item.gender}}
</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>
</v-data-table>
</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" 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: "view-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()
//console.log('掛單明細', this.formData);
},
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;
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;
return {
...bed,
isAvailableNow: bed.isAvailable && !isTakenByOthers
};
})
};
}).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';
}
},
getSpecialNeedsText(specialNeedsCodes) {
if (!specialNeedsCodes) return '';
const codes = specialNeedsCodes.split(',');
let self = this;
const names = codes.map(code => {
console.log("getSpecialNeedsText:", self.states, this.states);
const target = self.states.find(s => s.value === code);
return target ? target.text : code;
});
return names.join('、');
},
// 算整棟大樓的可用床位總和
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;
},
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 => {
console.log('掛單條件', 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; }
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; }
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; }
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() {
let detail = []
this.guadanguest.items.forEach(item => {
console.log('明細', item);
detail.push({
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,
name: item.fullName
})
})
//console.log('掛單主檔', this.guadanorder.order_form);
if (!this.validateOrderForm()) return;
axios.post(HTTP_HOST + 'api/guadan/saveGandan', { order: this.guadanorder.order_form, detail: 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)
});
},
showCheckInModal() {
//if (!this.guadanorder.order_form.uuid) {
// this.message_dialog = true;
// this.message = '請先儲存掛單資訊';
// return;
//}
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;
},
showAutomaticBedAllocation() {
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;
// 將大樓名稱也組進去顯示
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) {
console.log('選床', 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
});
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);
//console.log(JSON.stringify(result, null, 4));
this.mockBuildings = result;
this.selectedGuestForBed = item;
this.activeRoomPanel = null;
})
},
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>