改太多

This commit is contained in:
minom committed 2026-10-30 17:47:01 +08:00
1 parent be5352efed
commit 1cb2a7a35e
48 files changed
+17970 -485

No files matched your search

+2 -2
View File
@@ -238,11 +238,11 @@
</template>
<template #item.slot_btn="{ item }">
<a :href="'reg.aspx?num='+item.num" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-pencil-box-outline"></i>編輯</a>
<a @click="copyItem(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-pencil-box-outline"></i>複製</a>
<a @click="copyItem(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-content-copy"></i>複製</a>
<a @click="deleteItem(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-trash-can"></i>刪除</a>
<% if (IsPreOrder == "true")
{ %>
<a @click="compare(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-trash-can"></i>比對</a>
<a @click="compare(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-file-compare"></i>比對</a>
<% } %>
</template>
</v-data-table>
+206
View File
@@ -0,0 +1,206 @@
body {
font-family: "Microsoft JhengHei", sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
display: flex;
gap: 20px;
}
#control-panel {
width: 240px;
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 15px;
height: fit-content;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
font-size: 14px;
}
select,
input[type="range"] {
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
button.action-btn {
padding: 12px;
background: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
font-weight: 700;
}
#preview-container {
display: flex;
flex-grow: 1;
background: #ddd;
padding: 20px;
overflow: auto;
position: relative;
}
#paper-preview {
background: #fff;
padding: 20mm;
box-sizing: border-box;
width: 210mm;
height: 297mm;
}
#editor {
width: 100%;
height: 100%;
outline: none;
writing-mode: vertical-rl;
text-orient: upright;
}
#editor > div {
min-height: 1em;
display: flex !important;
flex-direction: column;
align-items: flex-start;
margin-left: 10px;
padding: 0 4px;
}
#editor > div:hover {
background: rgba(0, 123, 255, 0.05);
}
#editor > div.selected-line {
background: rgba(0, 123, 255, 0.1) !important;
box-shadow: 0 0 0 1px #ff7675;
}
#line-floating-menu {
display: none;
position: fixed;
bottom: 20px;
left: 280px;
background: #fff0f1;
border: 2px solid #ff7675;
padding: 12px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
z-index: 99999;
flex-direction: column;
gap: 8px;
width: 210px;
}
#line-floating-menu h4 {
margin: 0;
color: #d63031;
font-size: 14px;
cursor: move;
background: #ffe4e6;
padding: 4px;
border-radius: 4px;
text-align: center;
user-select: none;
}
.menu-btn-group {
display: flex;
gap: 4px;
}
.menu-btn-group button {
flex: 1;
padding: 6px;
border: 1px solid #ccc;
background: #fff;
cursor: pointer;
border-radius: 4px;
font-weight: 700;
}
.menu-btn-group button.active {
background: #28a745;
color: #fff;
border-color: #28a745;
}
.adjust-row {
display: flex;
align-items: center;
gap: 5px;
}
.adjust-row button {
padding: 4px 10px;
border: 1px solid #ccc;
background: #fff;
cursor: pointer;
border-radius: 4px;
font-weight: 700;
user-select: none;
}
.adjust-row button:hover {
background: #eee;
}
.adjust-row input[type="range"] {
flex-grow: 1;
padding: 0;
}
@media print {
#control-panel,
#line-floating-menu {
display: none !important;
}
body,
#preview-container {
background: #fff;
padding: 0;
margin: 0;
}
#paper-preview {
width: 100% !important;
height: 100% !important;
box-shadow: none;
/* 1. 移除原本的 flex 與置中設定,改為一般的區塊顯示 */
display: block !important;
/* 2. 透過 padding 設定您需要的上邊界與右邊界 (數值可自由調整) */
padding-top: 30mm !important; /* 控制上方留白 */
padding-right: 30mm !important; /* 控制右側留白 */
padding-bottom: 0 !important;
padding-left: 0 !important;
/* padding: 0;
display: flex !important;
justify-content: center !important;
align-items: center !important; */
}
#editor {
height: auto !important;
/* width: auto !important; 加入這行,讓寬度貼合內容 */
max-height: 100% !important;
}
}
@media print {
@page {
size: A4 portrait;
margin: 0mm;
}
}
+369
View File
@@ -0,0 +1,369 @@
<template>
<v-container>
<v-card>
<v-card-title class="bg-primary white--text text-center">
<h5 class="mb-0">沖帳查詢</h5>
<button class="btn btn-default ms-auto"><i class="mdi mdi-close"></i></button>
</v-card-title>
<v-card-text>
<v-row class="mb-0 mt-4">
<v-col cols="12" md="2">
<v-text-field v-model="query.start_date" label="起始日" type="date" dense outlined></v-text-field>
</v-col>
<v-col cols="12" md="2">
<v-text-field v-model="query.end_date" label="結束日" type="date" dense outlined></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field v-model="query.activity_name" label="法會" dense outlined readonly>
<template v-slot:append>
<v-btn icon @click="showActivityDialog"><v-icon>mdi-magnify</v-icon></v-btn>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field v-model="query.follower_name" label="信眾" dense outlined readonly>
<template v-slot:append>
<v-btn icon @click="showFollowerDialog"><v-icon>mdi-magnify</v-icon></v-btn>
</template>
</v-text-field>
</v-col>
<v-col cols="12" md="2" class="d-flex justify-end">
<v-btn color="primary" class="mr-2" @click="search">查詢</v-btn>
<v-btn color="grey" @click="reset">重設</v-btn>
</v-col>
</v-row>
<v-data-table :headers="headers"
:items="items"
:loading="loading"
loading-text="載入中..."
class="elevation-1 verify-query-table"
item-key="id"
:footer-props="{ 'items-per-page-options': [10, 20, 50] }"
:expanded.sync="expanded"
show-expand
@item-expanded="handleRowClick">
<template v-slot:item.pay_type="{ item }">
<div>
<div class="font-weight-bold">{{ getPayType(item.pay_type) }}</div>
</div>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<div class="pa-4">
<h6 class="mb-3 text-primary">
<v-icon color="primary" small class="mr-1">mdi-receipt</v-icon>
沖帳明細
</h6>
<v-data-table :headers="detailHeaders"
:items="detailItem"
class="elevation-1"
hide-default-footer
:disable-pagination="true"
dense>
</v-data-table>
</div>
</td>
</template>
</v-data-table>
</v-card-text>
</v-card>
<!-- 法會選取 Dialog -->
<v-dialog v-model="activityDialog.show" max-width="700px">
<v-card>
<v-card-title class="grey lighten-2">
<v-icon color="primary" class="mr-2">mdi-table</v-icon>
選擇法會
<v-spacer></v-spacer>
<v-btn icon @click="activityDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
</v-card-title>
<v-card-text class="mt-4">
<v-text-field v-model="activityDialog.search" label="搜尋法會" dense outlined @keyup.enter="searchActivity"></v-text-field>
<v-data-table :headers="activityDialog.headers"
:items="activityDialog.items"
:loading="activityDialog.loading"
item-key="num"
class="elevation-1 mt-2"
@click:row="selectActivity"
hide-default-footer></v-data-table>
</v-card-text>
</v-card>
</v-dialog>
<!-- 信眾選取 Dialog -->
<v-dialog v-model="followerDialog.show" max-width="700px">
<v-card>
<v-card-title class="grey lighten-2">
<v-icon color="primary" class="mr-2">mdi-account</v-icon>
選擇信眾
<v-spacer></v-spacer>
<v-btn icon @click="followerDialog.show = false"><v-icon>mdi-close</v-icon></v-btn>
</v-card-title>
<v-card-text class="mt-4">
<v-text-field v-model="followerDialog.search" label="搜尋信眾" dense outlined
@keydown.enter.prevent="searchFollower"></v-text-field>
<v-data-table :headers="followerDialog.headers"
:items="followerDialog.items"
:loading="followerDialog.loading"
item-key="num"
class="elevation-1 mt-2"
@click:row="selectFollower"
hide-default-footer></v-data-table>
</v-card-text>
</v-card>
</v-dialog>
<!-- 詳細資訊對話框 -->
<v-dialog v-model="dialog.show" max-width="900px">
<v-card>
<v-card-title class="grey lighten-2">
<v-icon color="info" class="mr-2">mdi-information-outline</v-icon>
詳細資訊
<v-spacer></v-spacer>
<v-btn icon @click="dialog.show = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div v-if="dialog.selected">
<v-row>
<v-col cols="12" md="6">
<h6 class="mb-3 text-primary">匯款人資訊</h6>
<div class="mb-2"><span class="font-weight-bold">姓名:</span>{{ dialog.selected.transfer_name }}</div>
<div class="mb-2"><span class="font-weight-bold">電話:</span>{{ dialog.selected.transfer_phone }}</div>
<div class="mb-2"><span class="font-weight-bold">信眾:</span>{{ dialog.selected.follower }}</div>
</v-col>
<v-col cols="12" md="6">
<h6 class="mb-3 text-success">入帳資訊</h6>
<div class="mb-2"><span class="font-weight-bold">入帳帳戶:</span>{{ dialog.selected.acc_name }}</div>
<div class="mb-2"><span class="font-weight-bold">入帳日期:</span>{{ dialog.selected.transfer_check_date | date }}</div>
<div class="mb-2"><span class="font-weight-bold">入帳金額:</span>{{ dialog.selected.transfer_check_amount | currency }}</div>
<div class="mb-2"><span class="font-weight-bold">應收總額:</span>{{ dialog.selected.price_totals | currency }}</div>
</v-col>
</v-row>
<v-divider class="my-4"></v-divider>
<v-row>
<v-col cols="12" md="6">
<h6 class="mb-3 text-purple">活動資訊</h6>
<div class="mb-2"><span class="font-weight-bold">活動名稱:</span>{{ dialog.selected.activity_name || '-' }}</div>
<div class="mb-2"><span class="font-weight-bold">支付方式:</span>{{ payTypeText[dialog.selected.transfer_pay_type] || dialog.selected.transfer_pay_type }}</div>
<div class="mb-2"><span class="font-weight-bold">帳號後5碼:</span>{{ dialog.selected.transfer_account_last5 || '-' }}</div>
</v-col>
<v-col cols="12" md="6">
<h6 class="mb-3 text-info">核對記錄</h6>
<div class="mb-2"><span class="font-weight-bold">核對記錄:</span>{{ dialog.selected.transfer_verify_note || '-' }}</div>
<div class="mb-2"><span class="font-weight-bold">帳簿備註:</span>{{ dialog.selected.transfer_check_memo || '-' }}</div>
<div v-if="dialog.selected.transfer_proof_img" class="mb-2">
<span class="font-weight-bold">證明圖片:</span>
<a :href="'../../upload/transfer_proof/' + dialog.selected.transfer_proof_img" target="_blank">查看相片</a>
</div>
</v-col>
</v-row>
</div>
</v-card-text>
</v-card>
</v-dialog>
</v-container>
</template>
<script>
module.exports = {
data() {
return {
loading: false,
expanded: [],
query: {
start_date: '',
end_date: '',
activity_num: '',
activity_name: '',
follower_num: '',
follower_name: ''
},
headers: [
{ text: '信眾/匯款人', value: 'followerName' },
{ text: '法會/入帳帳戶', value: 'acc_name' },
{ text: '入帳金額', value: 'check_amount' },
{ text: '入帳日期', value: 'check_date' },
{ text: '付款方式', value: 'pay_type' },
{ text: '沖帳方式', value: 'pay_mode' },
{ text: '狀態/備註', value: 'check_status' },
//{ text: '操作', value: 'actions', sortable: false }
],
detailHeaders: [
{ text: '法會名稱', value:'法會名稱'},
{ text: '功德項目', value: '牌位/點燈項目' },
{
text: '實際功德人姓名', value: '實際功德人姓名'
},
{
text: '應收金額', value: '項目原價'
},
{
text: '沖帳金額', value: '本次實際沖帳金額' },
{ text: '備註', value: 'memo' }
],
items: [],
detailItem:[],
dialog: {
show: false,
selected: null
},
activityDialog: {
show: false,
search: '',
loading: false,
items: [],
headers: [
{ text: '編號', value: 'num' },
{ text: '名稱', value: 'subject' }
]
},
followerDialog: {
show: false,
search: '',
loading: false,
items: [],
headers: [
{ text: '編號', value: 'num' },
{ text: '姓名', value: 'u_name' }
]
},
payTypeText: [
{ text: '1', value: '現金' },
{ text: '2', value: '匯款' },
{ text: '3', value: '支票' }
]
}
},
filters: {
payType(val) {
//console.log("payType:",val)
let v = this.payTypeText.find(x => x.text === val);
return v.value;
},
currency(val) {
if (!val) return '0';
return Number(val).toLocaleString();
},
date(val) {
if (!val) return '';
const date = new Date(val);
return date.toLocaleDateString();
}
},
methods: {
getPayType(val) {
let v = this.payTypeText.find(x => x.text === val);
return v?v.value:val;
},
handleRowClick({item, value}) {
this.$set(item, 'loading', true);
if (value) {
axios.post('../../api/bill/GetAuditDetailData', { id: item.id })
.then(res => {
console.log("handleRowClick:", res.data.list[0].k);
if (res.status === 200 && res.data && res.data.list) {
this.detailItem = res.data.list[0].k;
}
});
}
},
search() {
this.loading = true;
// 串接查詢 API,帶入所有查詢條件
axios.post('../../api/bill/GetAuditList', this.query, { params: { page: 1, pageSize: 1000 } })
.then(res => {
//console.log("QQ:",res);
this.items = res.data.list;
this.expanded = []; // 重置展開狀態
})
.catch(() => {
this.items = [];
this.expanded = [];
})
.finally(() => {
this.loading = false;
});
},
reset() {
this.query.start_date = '';
this.query.end_date = '';
this.query.activity_num = '';
this.query.activity_name = '';
this.query.follower_num = '';
this.query.follower_name = '';
this.items = [];
this.expanded = [];
},
showDetailDialog(item) {
this.dialog.selected = item;
this.dialog.show = true;
},
showActivityDialog() {
this.activityDialog.show = true;
this.searchActivity();
},
searchActivity() {
this.activityDialog.loading = true;
axios.get('../../api/activity', { params: { keyword: this.activityDialog.search } })
.then(res => { this.activityDialog.items = res.data; })
.catch(() => { this.activityDialog.items = []; })
.finally(() => { this.activityDialog.loading = false; });
},
selectActivity(row) {
this.query.activity_num = row.num;
this.query.activity_name = row.subject;
this.activityDialog.show = false;
},
showFollowerDialog() {
this.followerDialog.show = true;
//this.searchFollower();
},
searchFollower() {
//event.preventDefault();
this.followerDialog.loading = true;
axios.post('/api/follower/GetList', { u_name: this.followerDialog.search }, { params: { page: 1, pageSize: 10 } })
.then(res => { this.followerDialog.items = res.data.list; })
.catch(() => { this.followerDialog.items = []; })
.finally(() => { this.followerDialog.loading = false; });
},
selectFollower(row) {
this.query.follower_num = row.num;
this.query.follower_name = row.u_name;
this.followerDialog.show = false;
},
getStatusColor(status) {
switch (status) {
case '90': return 'warning';
case '99': return 'success';
default: return 'grey';
}
},
getStatusText(status) {
switch (status) {
case '90': return '沖帳有剩餘';
case '99': return '沖帳完成';
default: return '未知狀態';
}
}
}
};
</script>
<style>
.v-data-table.verify-query-table .v-data-table__wrapper tbody tr {
min-height: 90px !important;
height: 90px !important;
}
.v-data-table.verify-query-table .v-data-table__wrapper tbody td {
min-height: 90px !important;
height: 90px !important;
vertical-align: middle !important;
}
</style>
+1 -1
View File
@@ -34,7 +34,7 @@
</template>
<template v-slot:item.actions="{ item }">
<v-btn small
color="success"
@click="showReconcileDialog(item)">
沖帳
</v-btn>
+43 -5
View File
@@ -17,16 +17,42 @@
</v-select>
</v-col>
<v-col cols="2" md="2">
<v-text-field label="法會時間(起)" v-model="query.s_date"></v-text-field>
<v-menu ref="menu"
v-model="menu"
:close-on-content-click="true"
transition="scale-transition"
offset-y
min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field label="法會日期(起)" v-model="query.s_date"
prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="query.s_date"
show-adjacent-months></v-date-picker>
</v-menu>
</v-col>
<v-col cols="2" md="2">
<v-text-field label="法會時間(迄)" v-model="query.e_date"></v-text-field>
<v-menu ref="menu1"
v-model="menu1"
:close-on-content-click="true"
transition="scale-transition"
offset-y
min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field label="法會日期(迄)" v-model="query.e_date"
prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="query.e_date"
show-adjacent-months></v-date-picker>
</v-menu>
<!--<v-text-field label="法會日期(迄)" v-model="query.e_date"></v-text-field>-->
</v-col>
<v-col cols="2" md="2">
<v-btn @click.prevent="getRpt()" :disabled="isdisabled">
匯出
</v-btn>
<img src="/admin/images/loading.gif" v-show="isloading"/>
<img src="/admin/images/loading.gif" v-show="isloading" />
</v-col>
</v-row>
</v-card-text>
@@ -42,7 +68,7 @@
</v-card-text>
</v-card>
</v-dialog>
</div>
</div>
</template>
<script>
module.exports = {
@@ -57,10 +83,22 @@
category_id: "",
isdisabled: false,
isloading: false,
query: {s_date:"",e_date:""}
query: { s_date: "", e_date: "" },
activePicker: null,
date: null,
menu: false,
menu1: false,
}
},
watch: {
menu(val) {
val && setTimeout(() => (this.activePicker = 'YEAR'))
},
},
methods: {
save(date) {
this.$refs.menu.save(date)
},
closeWindow() {
this.$emit('close-dialog');
},
File diff suppressed because it is too large. Load diff
+632
View File
@@ -0,0 +1,632 @@
<template>
<div class="container-fluid">
<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">
<v-tab-item value="tab-1">
<div class="border rounded p-3 bg-white shadow-sm ">
<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" 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" 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 class="row mt-3">
<button v-if="guadanorder.order_form.uuid" type="button" class="btn btn-primary mx-auto" style="width: 120px;" @click="updateGuadanOrder">存檔</button>
<button v-else v-on:click="createGuadanOrder" type="button" class="btn btn-primary mx-auto" style="width: 120px;">儲存</button>
</div>
</div>
</v-tab-item>
<v-tab-item value="tab-2">
<!-- 表格標題緊貼表格上方,居中 -->
<div class="d-flex align-items-center mb-3">
<!-- 左側按鈕 -->
<v-btn color="primary" variant="elevated" @click="showCheckInModal" class="me-3">
<v-icon start>mdi-plus</v-icon>
設定人數
</v-btn>
<v-btn @click="showAutomaticBedAllocation">
自動分配床位
</v-btn>
<!-- 中間標題(flex-grow撐開居中) -->
<div class="flex-grow-1 text-center">
<h5 class="text-primary fw-bold mb-0">
<i class="bi bi-people-fill me-2"></i>掛單蓮友
</h5>
</div>
</div>
<!-- v-data-table 表格 -->
<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}">
{{item.fullname}}
</template>
<template v-slot:item.sex="{item}">
{{item.gender}}
</template>
<template v-slot:item.specialNeeds="{item}">
{{item.specialNeeds}}
</template>
<template #item.actions="{ item }">
<div>
<!-- 取消預訂 -->
<v-btn color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '401'"
@click="confirmDeleteGuadanOrderGuest(item)">
取消預訂
</v-btn>
<v-btn color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '401'"
@click="checkinGuadanGuest(item)">
入住
</v-btn>
<v-btn color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '402'"
@click="showXuzhuGuestModalMethod(item)">
續住
</v-btn>
<!-- 退房 -->
<v-btn color="primary"
variant="outlined"
size="small"
:disabled="item.statuscode !== '402'"
@click="checkoutGuadanOrderGuest(item)">
<v-icon start>mdi-exit-run</v-icon>
退房
</v-btn>
</div>
</template>
</v-data-table>
</v-tab-item>
</v-tabs-items>
<v-dialog v-model="message_dialog"
max-width="1280"
min-height="900"
hide-overlay
transition="dialog-bottom-transition">
<v-card>
<v-card-text">
{{message}}
</v-card-text">
</v-card>
</v-dialog>
<v-dialog v-model="confirm_dialog"
max-width="1280"
min-height="900"
hide-overlay
transition="dialog-bottom-transition">
<v-card>
<v-card-text">
{{confirm}}
</v-card-text">
</v-card>
</v-dialog>
<v-dialog v-model="checkInGuest.showSelectGuadanOrderGuest"
max-width="720"
min-height="300"
hide-overlay
transition="dialog-bottom-transition">
<v-card>
<v-card-text>
<v-container>
<v-row>
<v-col>
<v-text-field label="人數" v-model="e4"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field label="開始日期" v-model="s_date"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field label="結束日期" v-model="e_date"></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select :items="e2" v-model="e5"
label="性別"></v-select>
</v-col>
</v-row>
<v-row>
<v-col>
<v-select v-model="e6"
:items="states"
:menu-props="{ maxHeight: '400' }"
label="特殊需求"
multiple
hint="選擇條件"
persistent-hint></v-select>
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn color="primary" @click="checkInGuest.showSelectGuadanOrderGuest = false">
關閉
</v-btn>
<v-btn color="primary" @click.prevent="genGuestList">產生</v-btn>
</v-col>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<script>
module.exports = {
name: "apply-component",
data() {
return {
e1: null,
e2: ['請選擇', '男眾', '女眾'],
e4: "",
e5: "",
e6: [],
s_date: null,
e_date: null,
states: [
'無特殊需求',
'需要輪椅',
'需要拐杖',
'需要陪同人員',
'需要特別飲食',
'其他需求'
],
message_dialog: false,
message: "",
confirm_dialog: false,
confirm: "",
tab: "tab-1",
activityList: [],
availableBedCount: {
male: 0,
female: 0,
},
activityNum: null,
guadanorder: {
order_form: {
uuid: '<%= Request.QueryString["orderid"] %>' || null,
startdate: null,
enddate: null,
note: null,
orderNo: null,
bookerName: null,
bookerPhone: null,
bookerFollowerNum: null,
activityNum: null,
},
status_items: [],
},
guadanguest: {
guest: {
uuid: null, // int?
fullName: '', // string
gender: null, // int?
phone: '', // string
idNumber: '', // string
birthday: null, // Date (建議用 date picker)
email: '', // string
address: '', // string
emergencyContact: '', // string
emergencyPhone: '', // string
status: null, // int?
notes: '', // string
specialNeeds: '', // string
},
headers: [{
text: '姓名',
value: 'name'
},
{
text: '性別',
value: 'sex'
},
{
text: '掛單開始時間',
value: 'checkinat'
},
{
text: '掛單結束時間',
value: 'checkoutat'
},
{
text: '床位',
value: 'bedName'
},
{
text: '狀態',
value: 'statusName'
},
{
text: '備註',
value: 'note'
},
{
text: '特殊需求',
value: 'specialNeeds'
},
{
text: '',
value: 'actions'
},
],
items: [],
showCreateGuestModal: false,
xuzhu: {
showXuzhuGuestModal: false,
currentCheckoutDate: null,
newCheckoutDate: null,
guestUuid: null,
guestBedUuid: null,
}
},
checkInGuest: {
showSelectGuadanOrderGuest: false,
isEdit: false,
inGuest: {
uuid: null,
orderNo: null,
followerNum: null,
roomUuid: null,
bedUuid: null,
checkInAt: null,
checkOutAt: null,
statuscode: null,
},
status: [],
},
region_modal: {
regions: [],
currentSelectRegion: null,
currentSelectRoom: null,
currentSelectBeds: [],
currentSelectBed: null,
showSelectBedModal: false,
selectedId: null, // 被選中項目ID
selectedType: null, // 'region' 或 'room'
expandAllFlag: false, // 控制全部展開
collapseAllFlag: false, // 控制全部收起
currentSelectBedText: null,
},
selectGuestModal: {
showSelectGuestModal: false,
currentSelectedGuest: null,
fullNameText: null,
headers: [{
text: '姓名',
value: 'u_name'
},
{
text: '電話',
value: 'phone'
},
{
text: '',
value: 'actions'
},
],
items: [],
options: { //v-data-table參數
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: [],
multiSort: false,
},
page: 1,
pageSize: 10,
count: 0,
footer: {
showFirstLastPage: true,
disableItemsPerPage: true,
itemsPerPageAllText: '',
itemsPerPageText: '',
},
searchNameOrPhone: null,
},
automaticBedAllocation: {
showModal: false,
// 蓮友選擇彈出視窗
followerModal: {
showModal: false,
followerList: [],
selectedFollowerItems: [],
page: 1,
pageSize: 10,
totalCount: 0,
searchNameOrPhone: '',
headers: [
{ text: '姓名', value: 'u_name' },
{ text: '電話', value: 'phone' },
{ text: '操作', value: 'actions', sortable: false }
],
},
// 已選擇的待分配列表
selectedFollowers: [],
preBeds: [],
headers: [
{ text: '姓名', value: 'u_name' },
{ text: '電話', value: 'phone' },
{ text: '性別', value: 'sex' },
{ text: '預分配床位', value: 'prebed' },
{ text: '', value: 'actions' }
],
},
}
},
watch: {
activityNum: {
handler(newVal, oldVal) {
console.log('使用者資料更新了!', newVal);
let act = this.activityList.find(activity => activity.num == newVal);
if (act) {
console.log('找到對應的活動:', act);
this.guadanorder.order_form.startdate = act.startDate;
this.guadanorder.order_form.enddate = act.endDate;
}
},
deep: true, // 監聽 userProfile.name 或 age 的變化
immediate: true
}
},
mounted() {
this.getActivityList()
},
methods: {
genGuestList() {//產生掛單明細
console.log('產生掛單明細', this.e4, this.e5, this.e6);
for (let i = 0; i < this.e4; i++) {
this.guadanguest.items.push({
uuid: null, // int?
fullName: '', // string
gender: this.e5, // int?
specialNeeds: this.e6.join(",") // string
});
}
},
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) => {
console.log(res);
if (res.status == 200 && res.data && res.data.list) {
this.activityList = res.data.list;
}
})
},
createGuadanOrder() {
if (!this.validateOrderForm()) {
return;
}
axios.post(HTTP_HOST + 'api/guadan/create', this.guadanorder.order_form)
.then((res => {
//this.$refs.messageModal.open({
// title: '掛單提示',
// message: '掛單資料建立成功'
//})
this.message_dialog = true
this.message = '掛單資料建立成功'
this.guadanorder.order_form.uuid = res.data.uuid;
this.guadanorder.order_form.orderNo = res.data.guaDanOrderNo;
console.log(this.guadanorder.order_form.uuid);
// 跳转到当前 URL 加上 ?orderId=UUID
const currentUrl = window.location.origin + window.location.pathname;
window.location.href = `${currentUrl}?orderId=${res.data.guaDanOrderNo}`;
})).catch((error) => {
//this.$refs.messageModal.open({
// title: '錯誤',
// message: '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
//});
this.message_dialog = true
this.message = '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
});
},
updateGuadanOrder() {
if (!this.validateOrderForm()) {
return;
}
axios.post(HTTP_HOST + 'api/guadan/update', this.guadanorder.order_form)
.then((res => {
//this.$refs.messageModal.open({
// title: '掛單提示',
// message: '掛單資料更新成功'
//})
this.message_dialog = true
this.message = '掛單資料更新成功'
this.guadanorder.order_form.uuid = res.data.uuid;
console.log(this.guadanorder.order_form.uuid);
})).catch((error) => {
//this.$refs.messageModal.open({
// title: '錯誤',
// message: '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
//});
this.message_dialog = true
this.message = '建立掛單資料失敗:' + (error.response?.data?.message || error.message)
});
},
showCheckInModal() {
console.log('showCheckInModal called');
if (!this.guadanorder.order_form.uuid) {
//this.$refs.messageModal.open({
// message: '請先創建掛單',
//});
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.checkInGuest.showSelectGuadanOrderGuest = true;
},
showAutomaticBedAllocation() {
if (!this.guadanorder.order_form.uuid) {
//this.$refs.messageModal.open({
// message: '請先儲存掛單資訊',
//});
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;
})
},
}
}
</script>
<style>
/* 調整 fieldset 風格 */
fieldset {
border: 1px solid #dee2e6;
border-radius: 0.5rem;
background-color: #fff;
}
legend {
font-size: 1.25rem;
font-weight: 700;
color: #0d6efd;
width: auto;
padding: 0 0.75rem;
}
.form-group label {
font-weight: 600;
}
/* 按鈕置右 */
.text-right {
text-align: right;
}
/* 選擇床位相關 */
.tree,
.tree ul {
list-style: none;
margin: 0;
padding-left: 1rem;
}
.toggle-icon {
cursor: pointer;
user-select: none;
width: 1rem;
display: inline-block;
color: #007bff;
}
.region-item-label {
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
display: inline-block;
}
.region-item-label.selected {
background-color: #eaf4ff;
color: #0d6efd;
font-weight: bold;
}
.selected-room {
background-color: #cce5ff;
font-weight: bold;
}
/* 選擇床位相關 */
</style>
+41 -14
View File
@@ -147,6 +147,8 @@
</v-data-table>
</fieldset>
<!-- 🟢 續住彈出視窗 -->
<div>
<v-dialog v-model="guadanguest.xuzhu.showXuzhuGuestModal" max-width="50%">
@@ -212,14 +214,32 @@
<!-- 內容區 -->
<v-card-text class="py-6">
<v-container>
<v-row>
<v-col cols="12" sm="2">
<v-btn color="blue lighten-2" variant="elevated" block>
<v-icon left class="me-2">mdi-plus</v-icon>
新增時段床位
</v-btn>
</v-col>
<!-- 蓮友選擇 -->
<v-col cols="12" sm="2">
<div class="d-flex align-center" style="height: 100%;">
<v-btn color="teal lighten-3" variant="outlined" block @click="showSelectGuestModalMethod">
<v-icon left class="me-2">mdi-account-search</v-icon>
<!--選擇掛單蓮友-->
{{ selectGuestModal?.fullNameText || '選擇蓮友' }}
</v-btn>
</div>
</v-col>
</v-row>
<v-row dense>
<!-- 掛單單號 -->
<v-col cols="12">
<v-col cols="12" sm="2">
<v-text-field v-model="checkInGuest.inGuest.orderNo" label="掛單單號" readonly variant="underlined"></v-text-field>
</v-col>
<!-- 入住時間 -->
<v-col cols="12" sm="6">
<v-col cols="12" sm="2">
<v-text-field
v-model="checkInGuest.inGuest.checkInAt"
label="入住時間" type="date"
@@ -227,23 +247,23 @@
</v-col>
<!-- 退房時間 -->
<v-col cols="12" sm="6">
<v-col cols="12" sm="2">
<v-text-field
v-model="checkInGuest.inGuest.checkOutAt"
label="退房時間" type="date"
variant="underlined"></v-text-field>
</v-col>
<!-- 蓮友選擇 -->
<v-col cols="12" sm="6">
<%-- <v-col cols="12" sm="2">
<div class="d-flex align-center" style="height: 100%;">
<v-btn color="teal" variant="outlined" block @click="showSelectGuestModalMethod">
<v-btn color="teal lighten-3" variant="outlined" block @click="showSelectGuestModalMethod">
<v-icon left class="me-2">mdi-account-search</v-icon>
選擇掛單蓮友
{{ selectGuestModal?.fullNameText || '選擇蓮友' }}
</v-btn>
</div>
</v-col>
</v-col>--%>
<v-col cols="12" sm="6">
<%-- <v-col cols="12" sm="6">
<div class="d-flex flex-column justify-center" style="height: 100%;">
<div>
<div class="text-muted text-sm">
@@ -252,25 +272,32 @@
<div class="text-info text-xs mt-1">如無符合,請先新增蓮友資料</div>
</div>
</div>
</v-col>
</v-col>--%>
<!-- 床位選擇 -->
<v-col cols="12" sm="6">
<v-col cols="12" sm="2">
<div class="d-flex align-center" style="height: 100%;">
<v-btn color="indigo" variant="outlined" block @click="openSelectBedModal">
<%--<div class="text-muted text-sm">
已選床位:<strong>{{ region_modal.currentSelectBedText }}</strong>
</div>--%>
<v-btn color="indigo lighten-2" variant="elevated" block @click="openSelectBedModal">
<v-icon left class="me-2">mdi-bed</v-icon>
選擇床位
<%--選擇床位--%>
{{ region_modal.currentSelectBedText || "選擇床位" }}
</v-btn>
</div>
</v-col>
<v-col cols="12" sm="6">
<%-- <v-col cols="12" sm="2">
<v-text-field v-model="checkInGuest.inGuest.orderNo" label="說明" variant="underlined"></v-text-field>
</v-col>--%>
<%-- <v-col cols="12" sm="6">
<div class="d-flex flex-column justify-center" style="height: 100%;">
<div class="text-muted text-sm">
已選床位:<strong>{{ region_modal.currentSelectBedText }}</strong>
</div>
<div class="text-info text-xs mt-1">請從床位清單中選擇</div>
</div>
</v-col>
</v-col>--%>
</v-row>
</v-container>
</v-card-text>
+78 -213
View File
@@ -1,10 +1,11 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_guadan_index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
<script src="/js/httpVueLoader.js"></script>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<a href="create.aspx" class="btn btn-primary" >新建掛單</a>
<%-- <nav>
<a href="create.aspx" class="btn btn-primary" >新增</a>
</nav>
<div class="d-flex align-items-center gap-3">
<label class="mb-0">掛單單號</label>
@@ -20,246 +21,110 @@
<button class="btn btn-primary" type="button" @click="handleSearch">查詢</button>
<button class="btn btn-outline-primary" type="button" @click="clearSearch">清除條件</button>
</div>
</div>--%>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div id="content" class="container-fluid py-4">
<keep-alive>
<component :is="currentView" :form-data="$data"
:num="twoData"
@custom-event="changeView">
</component>
</keep-alive>
<div class="mx-5">
<v-data-table
:items="items"
:headers="headers"
:item-class="item => item.is_timeout ? 'row-timeout' : ''"
hide-default-footer>
<template #item.actions="{item}">
<a :href="'view.aspx?orderId='+item.guaDanOrderNo" class="btn btn-primary">查看</a>
<a :href="'create.aspx?orderId='+item.guaDanOrderNo" class="btn btn-secondary"
:style="item.guadan_status?.code == 502 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''">編輯</a>
<a
class="btn btn-outline-danger"
href="#"
:style="item.guest_count != 0 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''"
@click.prevent="item.guest_count != 0 ? null : deleteGuadanOrder(item)"
>
取消
</a>
</template>
<template #item.room="{item}">
{{item.room.name}}
</template>
<template #item.bed="{item}">
{{item.bed.name}}
</template>
<template #item.guadan_status="{item}">
{{item.guadan_status?.name}}
</template>
<template #item.start_date="{item}">
{{item.start_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.end_date="{item}">
{{item.end_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.created_at="{item}">
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
</template>
<template #item.activity="{item}">
{{item.activity?.subject}}
</template>
<template #item.is_timeout="{item}">
{{item.is_timeout ? '已超時': '否'}}
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap="false">
<v-col cols="12" md="8">
<v-pagination
v-model="options.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ total }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field
v-model="options.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="options.page = parseInt($event, 10)"
></v-text-field>
</v-col>
<!-- 每頁條數選擇 -->
<v-col cols="12" md="1">
<v-select
v-model="options.itemsPerPage"
:items="[5, 10, 20, 50]"
label="每頁條數"
dense
hide-details
style="width: 100px;"
></v-select>
</v-col>
</v-row>
</v-container>
</div>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
<!-- 刪除確認彈出視窗 -->
<confirm-modal ref="confirmModal"></confirm-modal>
<template>
<v-dialog
v-model="dialog"
max-width="1280"
min-height="900"
hide-overlay
transition="dialog-bottom-transition">
<v-card>
<v-toolbar dark color="primary">
<v-btn icon dark @click="dialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
<v-toolbar-title>掛單作業</v-toolbar-title>
<%-- <v-spacer></v-spacer>
<v-toolbar-items>
<v-btn
dark
text
@click="dialog = false"
>
Save
</v-btn>
</v-toolbar-items>--%>
</v-toolbar>
<%--<v-card-title>Cafe Badilico</v-card-title>--%>
<v-card-text>
<apply-component v-if="dialog" @close="dialog = false" />
</v-card-text>
</v-card>
</v-dialog>
</template>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<script>
Vue.filter('timeString', function (value, myFormat) {
return value == null || value == "" ? "" : moment(value).format(myFormat || 'YYYY-MM-DD, HH:mm:ss');
});
new Vue({
let VueApp = new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
vuetify: new Vuetify(),
mounted() {
},
data() {
return {
items: [],
headers: [
{ text: '登記掛單蓮友', value: 'bookerName' },
{ text: '掛單單號', value: 'guaDanOrderNo'},
{ text: '起始日期', value: 'start_date', align: 'center' },
{ text: '結束日期', value: 'end_date', align: 'center' },
{ text: '掛單人數', value: 'guest_count' },
{ text: '狀態', value: 'guadan_status', align: 'center' },
{ text: '建立時間', value: 'created_at', align: 'center' },
{ text: '關聯活動', value: 'activity', align: 'center' },
{ text: '超時退房', value: 'is_timeout', align: 'center' },
{ text: '操作', value: 'actions', align: 'center' }
],
options: {
page: 1, // 當前頁
itemsPerPage: 10, // 每頁條數
sortBy: [],
sortDesc: []
},
search: {
startDate: null,
endDate: null,
guadanUser: null,
guaDanOrderNo: null,
},
total: 0,
loading: false,
dialog: false,
notifications: false,
sound: true,
widgets: false,
currentView: 'main-component',
twoData: {},
}
};
},
methods: {
resetTableOptions() {
this.options = {
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: []
};
},
handleSearch() {
let orderNo = this.search.guaDanOrderNo;
if (orderNo) {
orderNo = orderNo.replace(/\s+/g, '');
this.search.guaDanOrderNo = orderNo;
}
const val = this.search.guadanUser;
// 驗證是否包含空格
if (val && /\s/.test(val)) {
this.$refs.messageModal.open({
message: '掛單登記人不能包含空格'
});
return;
}
// 驗證長度
if (val && val.length > 10) {
this.$refs.messageModal.open({
message: '掛單登記人不能超過 10 個字'
});
return;
}
this.resetTableOptions();
},
clearSearch() {
this.search.startDate = null;
this.search.endDate = null;
this.search.guadanUser = null;
this.search.guaDanOrderNo = null;
this.resetTableOptions();
},
getGuadanOrder() {
if (this.loading) return;
axios.post(HTTP_HOST + 'api/guadan/list', {
startDate: this.search.startDate,
endDate: this.search.endDate,
guadanUser: this.search.guadanUser,
guaDanOrderNo: this.search.guaDanOrderNo,
page: this.options.page,
pageSize: this.options.itemsPerPage
})
.then((res) => {
this.items = res.data.data;
this.total = res.data.total;
}).catch((err) => {
console.log(err);
}).finally(() => {
this.loading = false;
});
},
deleteGuadanOrder(order) {
this.$refs.confirmModal.open({
message: '確認取消掛單?',
onConfirm: () => {
axios.post(HTTP_HOST + 'api/guadan/cancel', null, {
params: {
uuid: order.uuid
}
}).then((res) => {
this.items = this.items.filter(a => a.uuid != order.uuid)
this.$refs.messageModal.open({
message: '取消成功'
})
}).catch((error) => {
this.$refs.messageModal.open({
message: error.response?.data || '取消失敗'
})
})
}
});
},
components: {
'main-component': httpVueLoader('./main.vue'),
'apply-component': httpVueLoader('./apply.vue'),
},
watch: {
options: {
handler() {
this.getGuadanOrder(); // 監聽分頁、排序變化,自動載入數據
},
deep: true,
},
},
methods: {
changeView(item) {
console.log("index changeView", item);
if (item.action === "new") {
console.log("go to new");
this.dialog = true;
} else if (item.action === "view") {
console.log("go to view");
} else if (item.action === "edit") {
console.log("go to edit");
}
}
},
mounted() {
this.getGuadanOrder();
},
computed: {
pageCount() {
return Math.ceil(this.total / this.options.itemsPerPage)
},
}
});
</script>
<style>
.row-timeout {
background-color: #ffdddd !important;
background-color: #ffdddd !important;
}
</style>
</asp:Content>
+254
View File
@@ -0,0 +1,254 @@
<template>
<div class="container-fluid">
<nav>
<!--<a href="create.aspx" class="btn btn-primary">新增</a>-->
<button class="btn btn-primary" @click.prevent="changeViews()">新增</button>
</nav>
<div class="d-flex align-items-center gap-3">
<label class="mb-0">掛單單號</label>
<input class="form-control w-auto" style="width:150px;" v-model="search.guaDanOrderNo" />
<label class="mb-0">掛單登記人</label>
<input class="form-control w-auto" style="width:150px;" v-model="search.guadanUser" />
<label class="mb-0">開始時間</label>
<input class="form-control w-auto" style="width:150px;" type="date" v-model="search.startDate" />
<label class="mb-0">結束時間</label>
<input class="form-control w-auto" style="width:150px;" type="date" v-model="search.endDate" />
<button class="btn btn-primary" type="button" @click="handleSearch">查詢</button>
<button class="btn btn-outline-primary" type="button" @click="clearSearch">清除條件</button>
</div>
<div class="mx-5">
<v-data-table :items="items"
:headers="headers"
:item-class="item => item.is_timeout ? 'row-timeout' : ''"
hide-default-footer>
<template #item.actions="{item}">
<button class="btn btn-primary">查看</button>
<!--<a :href="'view.aspx?orderId='+item.guaDanOrderNo" class="btn btn-primary">查看</a>-->
<button class="btn btn-secondary"
:style="item.guadan_status?.code == 502 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''">編輯</button>
<!--<a :href="'create.aspx?orderId='+item.guaDanOrderNo" class="btn btn-secondary"
:style="item.guadan_status?.code == 502 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''">編輯</a>-->
<button class="btn btn-outline-danger" :style="item.guest_count != 0 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''"
@click.prevent="item.guest_count != 0 ? null : deleteGuadanOrder(item)">
取消
</button>
<!--<a class="btn btn-outline-danger"
href="#"
:style="item.guest_count != 0 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''"
@click.prevent="item.guest_count != 0 ? null : deleteGuadanOrder(item)">
取消
</a>-->
</template>
<template #item.room="{item}">
{{item.room.name}}
</template>
<template #item.bed="{item}">
{{item.bed.name}}
</template>
<template #item.guadan_status="{item}">
{{item.guadan_status?.name}}
</template>
<template #item.start_date="{item}">
{{item.start_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.end_date="{item}">
{{item.end_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.created_at="{item}">
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
</template>
<template #item.activity="{item}">
{{item.activity?.subject}}
</template>
<template #item.is_timeout="{item}">
{{item.is_timeout ? '已超時': '否'}}
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap="false">
<v-col cols="12" md="8">
<v-pagination v-model="options.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ total }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field v-model="options.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="options.page = parseInt($event, 10)"></v-text-field>
</v-col>
<!-- 每頁條數選擇 -->
<v-col cols="12" md="1">
<v-select v-model="options.itemsPerPage"
:items="[5, 10, 20, 50]"
label="每頁條數"
dense
hide-details
style="width: 100px;"></v-select>
</v-col>
</v-row>
</v-container>
</div>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
<!-- 刪除確認彈出視窗 -->
<confirm-modal ref="confirmModal"></confirm-modal>
</div>
</template>
<script>
Vue.filter('timeString', function (value, myFormat) {
return value == null || value == "" ? "" : moment(value).format(myFormat || 'YYYY-MM-DD, HH:mm:ss');
});
module.exports = {
props: ['formData'],
vuetify: new Vuetify(vuetify_options),
data() {
return {
items: [],
headers: [
{ text: '登記掛單蓮友', value: 'bookerName' },
{ text: '掛單單號', value: 'guaDanOrderNo' },
{ text: '起始日期', value: 'start_date', align: 'center' },
{ text: '結束日期', value: 'end_date', align: 'center' },
{ text: '掛單人數', value: 'guest_count' },
{ text: '狀態', value: 'guadan_status', align: 'center' },
{ text: '建立時間', value: 'created_at', align: 'center' },
{ text: '關聯活動', value: 'activity', align: 'center' },
{ text: '超時退房', value: 'is_timeout', align: 'center' },
{ text: '操作', value: 'actions', align: 'center' }
],
options: {
page: 1, // 當前頁
itemsPerPage: 10, // 每頁條數
sortBy: [],
sortDesc: []
},
search: {
startDate: null,
endDate: null,
guadanUser: null,
guaDanOrderNo: null,
},
total: 0,
loading: false,
}
},
methods: {
changeViews() {
console.log("changeViews");
this.$emit('custom-event', { action: 'new', item: "1" });
},
resetTableOptions() {
this.options = {
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: []
};
},
handleSearch() {
let orderNo = this.search.guaDanOrderNo;
if (orderNo) {
orderNo = orderNo.replace(/\s+/g, '');
this.search.guaDanOrderNo = orderNo;
}
const val = this.search.guadanUser;
// 驗證是否包含空格
if (val && /\s/.test(val)) {
this.$refs.messageModal.open({
message: '掛單登記人不能包含空格'
});
return;
}
// 驗證長度
if (val && val.length > 10) {
this.$refs.messageModal.open({
message: '掛單登記人不能超過 10 個字'
});
return;
}
this.resetTableOptions();
},
clearSearch() {
this.search.startDate = null;
this.search.endDate = null;
this.search.guadanUser = null;
this.search.guaDanOrderNo = null;
this.resetTableOptions();
},
getGuadanOrder() {
if (this.loading) return;
axios.post(HTTP_HOST + 'api/guadan/list', {
startDate: this.search.startDate,
endDate: this.search.endDate,
guadanUser: this.search.guadanUser,
guaDanOrderNo: this.search.guaDanOrderNo,
page: this.options.page,
pageSize: this.options.itemsPerPage
})
.then((res) => {
this.items = res.data.data;
this.total = res.data.total;
}).catch((err) => {
console.log(err);
}).finally(() => {
this.loading = false;
});
},
deleteGuadanOrder(order) {
this.$refs.confirmModal.open({
message: '確認取消掛單?',
onConfirm: () => {
axios.post(HTTP_HOST + 'api/guadan/cancel', null, {
params: {
uuid: order.uuid
}
}).then((res) => {
this.items = this.items.filter(a => a.uuid != order.uuid)
this.$refs.messageModal.open({
message: '取消成功'
})
}).catch((error) => {
this.$refs.messageModal.open({
message: error.response?.data || '取消失敗'
})
})
}
});
},
},
watch: {
options: {
handler() {
this.getGuadanOrder(); // 監聽分頁、排序變化,自動載入數據
},
deep: true,
}
},
mounted() {
this.getGuadanOrder();
},
computed: {
pageCount() {
return Math.ceil(this.total / this.options.itemsPerPage)
},
}
};
</script>
+6
View File
@@ -0,0 +1,6 @@
<template>
</template>
<script>
</script>
+6
View File
@@ -0,0 +1,6 @@
<template>
</template>
<script>
</script>
+2
View File
@@ -7,6 +7,7 @@ using System.Configuration;
using Newtonsoft.Json;
using System.Web;
using System.Web.UI.HtmlControls;
using NPOI.OpenXmlFormats;
public partial class admin_index : MyWeb.function
{
@@ -199,6 +200,7 @@ public partial class admin_index : MyWeb.function
if (!isErr)
{
Response.Redirect("index2.aspx");
}
}
+11 -1
View File
@@ -52,7 +52,17 @@
//return "<a href='order/index2.aspx?dd=" + yesterday+"' >"+ yesterday +"</a>"
},
getDefault() {
axios
.post(HTTP_HOST + 'api/adminUser/GetMyDefaultPage', {u_id:"17168ERP"})
.then(response => {
console.log(response.data)
if (response.data.result == "ok") {
window.location.href = response.data.data.default_url
}
})
.catch(error => console.log(error))
this.data_table.loading = true
axios
.post(HTTP_HOST + 'api/activity/GetActivityBoard', this.search)
+5
View File
@@ -68,6 +68,7 @@
<template #item.slot_btn="{ item }">
<a @click="gotoSignup(item)" class="btn btn-outline-secondary btn-sm">報名</a>
<a @click="gotoBatchSignup(item)" class="btn btn-outline-secondary btn-sm">批次報名</a>
<a @click="gotoImport(item)" class="btn btn-outline-secondary btn-sm">匯入Excel</a>
<a @click="gotoSignupList(item)" class="btn btn-outline-secondary btn-sm">明細</a>
</template>
@@ -213,6 +214,10 @@
this.search.subject = ""
this.search.up_time1=""
},
gotoImport(item) {
this.$emit('custom-event', { action: 'import', item: item });
},
gotoBatchSignup(item) {
this.$emit('custom-event', { action: 'batchSign', item: item });
},
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="container-fluid">
<v-card>
<v-card-title class="bg-primary white--text text-center">
<h4>Excel匯入</h4>
<div class="ms-auto">
<!--<v-btn @click.prevent="batchSave()">儲存</v-btn>
<v-btn @click.prevent="back01()">返回</v-btn>-->
</div>
</v-card-title>
<v-card-text>
<v-select :items="data_table.list"
label="選擇法會"
item-text="subject"
item-value="num" v-model="selected">
</v-select>
<v-file-input v-model="selectedFiles" truncate-length="15" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel" label="請選擇要匯入的excel" outlined dense></v-file-input>
<v-btn @click.prevent="importList">匯入</v-btn>
</v-card-text>
</v-card>
<v-dialog v-model="message_dialog.show" class="dialog_width">
<template>
<v-card>
<v-card-title>
{{ message_dialog.message }}
</v-card-title>
<v-card-text>
<v-btn @click.prevent="message_dialog.show=false" v-if="!message_dialog.show_confirm">關閉</v-btn>
</v-card-text>
</v-card>
</template>
</v-dialog>
</div>
</template>
<script>
module.exports = {
props: {
num: {
type: Number
}
},
data() {
return {
options: { multiSort: false },
search_options: { multiSort: false },
message_dialog: { show: false, show_confirm: false, message: "" },
data_table: {
loading: true,
list: [],
selected: [],
singleSelect: false,
count: 0,
page: 1,
pageSize: -1,
},
selected: null,
search: { subject: "" },
selectedFiles:[],
}
},
activated() {
this.getActivity()
},
watch: {
selected: {
handler() {
this.getProOrder()
},
deep: true,
},
},
methods: {
importList() {
if (!this.selectedFiles || this.selectedFiles.length === 0) return;
const formData = new FormData();
// If multiple files are enabled, loop through and append each file
for (let i = 0; i < this.selectedFiles.length; i++) {
formData.append('files', this.selectedFiles[i]);
}
//try {
// const response = await axios.post('/api/upload', formData, {
// headers: {
// 'Content-Type': 'multipart/form-data'
// }
// });
// console.log('Upload success:', response.data);
//} catch (error) {
// console.error('Upload failed:', error);
//}
},
back01() {//返回
this.$emit('custom-event', { action: 'signup1', item: this.num });
},
getActivity() {//撈法會資料
//const { sortBy, sortDesc, page, itemsPerPage } = this.options
const params = {
sortBy: "startDate_solar", sortDesc: true,
page: 1, pageSize: -1
};
this.data_table.loading = true
const self = this
axios
.post(HTTP_HOST + 'api/activity/GetListNew', this.search, { params: params })
.then(response => {
this.data_table.list = response.data.list
this.data_table.list = this.data_table.list.filter(x => x.num != self.num)
this.data_table.count = response.data.count;
this.data_table.loading = false
})
.catch(error => console.log(error))
}
}
}
</script>
<style>
.v-dialog {
width: 80% !important;
}
</style>
+4
View File
@@ -96,6 +96,7 @@
'fastSignUp3-component': httpVueLoader('./fastSignUp3.vue'),
'fastSignUp4-component': httpVueLoader('./fastSignUp4.vue'),
'fastSignUp5-component': httpVueLoader('./fastSignUp5.vue'),
'fastSignUp6-component': httpVueLoader('./fastSignUp6.vue'),
},
watch: {
options: {
@@ -126,6 +127,9 @@
} else if (item.action === "batchSign") {
this.twoData = item.item.num
this.currentView = "fastSignUp5-component"
} else if (item.action === "import") {
this.twoData = item.item.num
this.currentView = "fastSignUp6-component"
}
//console.log("item.action:",this.twoData)
},
+2 -2
View File
@@ -621,11 +621,11 @@
</template>
<!-- 功德項目欄位格式化 -->
<template v-for="meritItem in tab4MeritItems" v-slot:[`item.${meritItem}`]="{ item }">
<%--<template v-for="meritItem in tab4MeritItems" v-slot:[`item.${meritItem}`]="{ item }">
<span :class="{'grey--text': item[meritItem] === 0}">
{{ tab4.valueType === 'amount' ? formatCurrency(item[meritItem]) : (item[meritItem] || '-') }}
</span>
</template>
</template>--%>
<!-- 無資料提示 -->
<template v-slot:no-data>
+19 -16
View File
@@ -326,7 +326,7 @@
:page.sync="tab2CurrentPage"
:server-items-length="filteredRegistrations.length"
class="elevation-1"
item-key="報名編號"
item-key="功德序號"
:footer-props="{
'items-per-page-options': [10, 20, 50, 100]
}"
@@ -393,7 +393,7 @@
<v-col cols="12" md="3">
<v-text-field
v-model.number="tab3Filter.minCount"
label="最少參與次數"
label="最少功德數量"
type="number"
dense
outlined
@@ -416,8 +416,8 @@
<v-select
v-model="tab3SortBy"
:items="[
{text: '參與次數(降序)', value: 'count_desc'},
{text: '參與次數(升序)', value: 'count_asc'},
{text: '功德數量(降序)', value: 'count_desc'},
{text: '功德數量(升序)', value: 'count_asc'},
{text: '總金額(降序)', value: 'amount_desc'},
{text: '總金額(升序)', value: 'amount_asc'}
]"
@@ -434,16 +434,16 @@
<v-data-table
:headers="tab3Headers"
:items="filteredFollowerAnalysis"
:items-per-page="20"
:items-per-page="500"
class="elevation-1"
item-key="信眾編號"
>
<template v-slot:item.信眾編號="{ item }">
<v-chip small color="info">{{ item.信眾編號 }}</v-chip>
</template>
<template v-slot:item.參與次數="{ item }">
<v-chip small :color="item.參與次數 >= 5 ? 'success' : 'default'">
{{ item.參與次數 }}
<template v-slot:item.功德數量="{ item }">
<v-chip small :color="item.功德數量 >= 5 ? 'success' : 'default'">
{{ item.功德數量 }}
</v-chip>
</template>
<template v-slot:item.總金額="{ item }">
@@ -657,7 +657,7 @@
tab3Headers: [
{ text: '信眾編號', value: '信眾編號', width: 120 },
{ text: '姓名', value: '姓名', width: 120 },
{ text: '參與次數', value: '參與次數', width: 100, align: 'center' },
{ text: '功德數量', value: '功德數量', width: 100, align: 'center' },
{ text: '總金額', value: '總金額', width: 150, align: 'end' },
{ text: '平均金額', value: '平均金額', width: 150, align: 'end' },
{ text: '最近參與日期', value: '最近參與日期', width: 130 }
@@ -719,6 +719,9 @@
// Tab 2: 不重複信眾數
uniqueFollowersCount() {
//console.log("uniqueFollowersCount:", this.filteredRegistrations.length)
//let qq = this.filteredRegistrations.filter(x => x.信眾姓名 === "周阿着")
//console.log(qq);
if (!this.filteredRegistrations.length) return 0;
const followers = new Set(this.filteredRegistrations.map(x => x.信眾編號));
return followers.size;
@@ -747,13 +750,13 @@
followerMap[fNum] = {
信眾編號: fNum,
姓名: item.信眾姓名,
參與次數: 0,
功德數量: 0,
總金額: 0,
最近參與日期: item.報名日期
};
}
followerMap[fNum].參與次數++;
followerMap[fNum].功德數量++;
followerMap[fNum].總金額 += (item.金額 * item.數量);
// 更新最近參與日期
@@ -765,7 +768,7 @@
// 轉換為陣列,並計算平均金額
return Object.values(followerMap).map(f => ({
...f,
平均金額: Math.round(f.總金額 / f.參與次數)
平均金額: Math.round(f.總金額 / f.功德數量)
}));
},
@@ -778,9 +781,9 @@
data = data.filter(x => x.信眾編號 && x.信眾編號.includes(this.tab3Filter.followerCode));
}
// 過濾:最少參與次數
// 過濾:最少功德數量
if (this.tab3Filter.minCount) {
data = data.filter(x => x.參與次數 >= this.tab3Filter.minCount);
data = data.filter(x => x.功德數量 >= this.tab3Filter.minCount);
}
// 過濾:最少總金額
@@ -791,10 +794,10 @@
// 排序
switch (this.tab3SortBy) {
case 'count_desc':
data.sort((a, b) => b.參與次數 - a.參與次數);
data.sort((a, b) => b.功德數量 - a.功德數量);
break;
case 'count_asc':
data.sort((a, b) => a.參與次數 - b.參與次數);
data.sort((a, b) => a.功德數量 - b.功德數量);
break;
case 'amount_desc':
data.sort((a, b) => b.總金額 - a.總金額);
+38
View File
@@ -0,0 +1,38 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="preview.aspx.cs" Inherits="admin_print_preview" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div id="content" class="container-md">
<v-data-table
v-model="order_data.selected"
:items="order_data.list"
item-key="order_no"
:headers="order_data.header"
:loading="order_data.loading"
show-select
class="elevation-1">
<template #item.detail ="{item}">
{{item.detail.count}}
</template>
<template #item.slot_type ="{item}">
{{item.detail.actItem?.substring(0,3)}}
</template>
<template #item.up_time ="{item}">
{{item.up_time|timeString('YYYY-MM-DD')}}
</template>
<template #item.print="{item}">
<v-icon color="blue" class="mr-2" @click="yulan_single(item);" >
mdi-printer
</v-icon>
</template>
</v-data-table>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class admin_print_preview : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
+228 -62
View File
@@ -237,6 +237,7 @@
data.forEach(x => {
param.push({ order_no: x.order_no, num: x.num });
});
console.log("param", param);
await axios
.post(this.http_host + 'api/orderdetail/GetDetailToPrint', { param: param })
.then(response => {
@@ -629,6 +630,7 @@
<span class="sub-text">${parts[1] || ''}</span>
</div>`;
} else if (d.elementID === "address") {
console.log(address, d.fontSize, d.height)
newFontSize = self.scaleFontSize(address, d.fontSize, d.height);
html = self.renderNormal(address, d)
} else if (d.elementID === "lefttitle") {
@@ -1022,23 +1024,31 @@
let h = parseFloat(el.height);
let w = parseFloat(el.width);
let th = parseFloat(el.textHeight) || h;
let v_gap = parseFloat(el.textSpan) || 0;
let h_gap = 5;
let shrinkLimit = 4;
if (Number(el.height) < 300) {
shrinkLimit = 8;
} else if (Number(el.height) < 500) {
shrinkLimit = 6;
}
let s = parseFloat(el.fontSize);
if (names.length == 2) {
h = th + v_gap;
}
if (Number(el.height) < 300) shrinkLimit = 8;
else if (Number(el.height) < 500) shrinkLimit = 6;
// 🚀 新增:取得這批名單的最大字數,確保所有格子高度統一
let maxLen = Math.max(...names.map(x => x.length), 1);
let s = parseFloat(el.style ? el.style.fontSize : el.fontSize);
if (names.length == 2) h = th + v_gap;
// 🚀 關鍵修復:刪除了
// 確保 liveList 總高度永遠維持使用者設定的原始高度,不會異常縮小!
// 1. 建立 items 陣列,精準判斷短字串與長字串 (佔滿一整行)
const init_size_px = s * 1.333;
let items = names.map(rawName => {
let name = rawName.trim();
let l = name.length;
// 6個字以內,或換算後能塞入預設區塊的視為短名
let isShort = (l <= 6) || ((l * init_size_px) <= (th + (v_gap / 2)));
return { name: name, l: l, isShort: isShort, isMulti: false };
});
// 取得短名的最大字數作為標準網格基準
let maxShortLen = Math.max(...items.filter(x => x.isShort).map(x => x.l), 1);
// 2. 單一變數 s 全局統一縮放,長字串為主計算
while (s >= shrinkLimit) {
let size_px = s * 1.333;
let current_col_h = 0;
@@ -1046,29 +1056,16 @@
let cols_width = [];
let any_wrapped = false;
for (let i = 0; i < names.length; i++) {
let l = names[i].length;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let l = item.l;
// 🚀 修正:排版預測時,統一使用最大字數來計算所需高度
let natural_max_h = maxLen * size_px;
let item_h = Math.max(th, natural_max_h);
if (item.isShort) {
// 短字串照原規則疊加
let natural_max_h = maxShortLen * size_px;
let item_h = Math.max(th, natural_max_h);
if (item_h > natural_max_h * 1.5) item_h = natural_max_h * 1.5;
if (item_h > natural_max_h * 1.5) {
item_h = natural_max_h * 1.5;
}
if (item_h > (h - 5)) {
any_wrapped = true;
if (current_col_h > 0) {
cols_width.push(current_col_w);
current_col_h = 0;
current_col_w = 0;
}
let chars_per_col = Math.floor((h - 5) / size_px);
if (chars_per_col < 1) chars_per_col = 1;
let item_cols = Math.ceil(l / chars_per_col);
cols_width.push(item_cols * (size_px * 1.1));
} else {
if (current_col_h === 0) {
current_col_h = item_h;
current_col_w = size_px;
@@ -1080,17 +1077,34 @@
current_col_h = item_h;
current_col_w = size_px;
}
} else {
// 長字串處理:一旦遇到長字串,強制換行並獨佔一整欄 (配發 h 高度)
if (current_col_h > 0) {
cols_width.push(current_col_w);
current_col_h = 0;
current_col_w = 0;
}
let item_w = size_px;
if ((l * size_px) > (h - 10)) {
item.isMulti = true;
any_wrapped = true;
let chars_per_col = Math.floor((h - 10) / size_px);
if (chars_per_col < 1) chars_per_col = 1;
let item_cols = Math.ceil(l / chars_per_col);
item_w = item_cols * (size_px * 1.1);
} else {
item.isMulti = false;
}
cols_width.push(item_w);
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
let totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0);
if (totalWidth <= w && (!any_wrapped || s === shrinkLimit)) {
if (totalWidth <= w) {
break;
}
s -= 1;
}
@@ -1104,56 +1118,60 @@
"align-items": "center",
"margin": "auto",
"width": `${w}px`,
"height": `${h}px`,
"height": `${h}px`, // 這裡的 h 已經恢復維持原設定值!
"border": "0px solid transparent",
"padding": "0px",
"font-family": el.fontFamily,
"font-family": el.style ? el.style.fontFamily : el.fontFamily,
"column-gap": `${v_gap}px`
});
let self = this;
names.forEach(name => {
// 🚀 將 maxLen 傳遞給渲染函式
$namelist.append(self.renderLiveSpan(name, el, s, maxLen));
// 3. 將 item 物件傳入,讓 renderLiveSpan 知道它是長或短
items.forEach(item => {
$namelist.append(self.renderLiveSpan(item, el, s, maxShortLen));
});
return $namelist;
},
// 🚀 接收 maxLen 參數
renderLiveSpan(name, el, fontSize, maxLen = 1) {
renderLiveSpan(item, el, fontSize, maxLen = 1) {
let h = parseFloat(el.height);
let th = parseFloat(el.textHeight) || h;
let size_px = fontSize * 1.333;
let l = name.length;
// 支援舊有單純傳入字串的防呆
let nameStr = typeof item === 'string' ? item : item.name;
let isShort = typeof item === 'string' ? true : item.isShort;
let isMulti = typeof item === 'string' ? false : item.isMulti;
let l = nameStr.length;
// 4. 高度分配邏輯:長字串佔滿全高 h,短字串用網格標準高度
let natural_max_h = maxLen * size_px;
let boxH_num = Math.max(th, natural_max_h);
let boxH_num = isShort ? Math.max(th, natural_max_h) : h;
if (boxH_num > natural_max_h * 1.5) {
if (isShort && boxH_num > natural_max_h * 1.5) {
boxH_num = natural_max_h * 1.5;
}
let minH = `${boxH_num}px`;
let alignStyle = "justify";
let spanDisplay = "block";
let spanJustifyContent = "flex-start";
let contentH = l * size_px;
let emptySpace = boxH_num - contentH;
let gapsCount = l > 1 ? (l - 1) : 1;
let avgGap = emptySpace / gapsCount;
// 預設對齊:長字串/多行靠上 (start),短字串預設分散 (justify)
let alignStyle = isMulti || (!isMulti && !isShort) ? "start" : "justify";
// 🚀 關鍵修正:當陽上名字字距過大觸發防過度拉扯時,改為「靠上對齊 (start)」而不是置中 (center)
if (alignStyle === "justify" && avgGap > (size_px * 1.5)) {
alignStyle = "justify"; // 改為靠上,讓字首切齊
spanDisplay = "block";
spanJustifyContent = "space-between"; // Flex 靠上
if (isShort) {
let contentH = l * size_px;
let emptySpace = boxH_num - contentH;
let gapsCount = l > 1 ? (l - 1) : 1;
let avgGap = emptySpace / gapsCount;
// 短名防過度拉扯:字距超過字體的 1.5 倍時自動靠上切齊
if (alignStyle === "justify" && avgGap > (size_px * 1.5)) {
alignStyle = "start";
}
}
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
"display": spanDisplay,
"justify-content": spanJustifyContent,
"align-items": "center",
// 完全使用 block 與原生對齊,避免 Flex 破壞直書對齊
return $(`<span style="font-size: ${fontSize}pt;">${nameStr}</span>`).css({
"display": "block",
"min-height": minH,
"max-height": `${h}px`,
"width": "auto",
@@ -1170,6 +1188,154 @@
// let h = parseFloat(el.height);
// let w = parseFloat(el.width);
// let th = parseFloat(el.textHeight) || h;
// let v_gap = parseFloat(el.textSpan) || 0;
// let h_gap = 5;
// let shrinkLimit = 4;
// if (Number(el.height) < 300) {
// shrinkLimit = 8;
// } else if (Number(el.height) < 500) {
// shrinkLimit = 6;
// }
// let s = parseFloat(el.fontSize);
// if (names.length == 2) {
// h = th + v_gap;
// }
// // 🚀 新增:取得這批名單的最大字數,確保所有格子高度統一
// let maxLen = Math.max(...names.map(x => x.length), 1);
// while (s >= shrinkLimit) {
// let size_px = s * 1.333;
// let current_col_h = 0;
// let current_col_w = 0;
// let cols_width = [];
// let any_wrapped = false;
// for (let i = 0; i < names.length; i++) {
// let l = names[i].length;
// // 🚀 修正:排版預測時,統一使用最大字數來計算所需高度
// let natural_max_h = maxLen * size_px;
// let item_h = Math.max(th, natural_max_h);
// if (item_h > natural_max_h * 1.5) {
// item_h = natural_max_h * 1.5;
// }
// if (item_h > (h - 5)) {
// any_wrapped = true;
// if (current_col_h > 0) {
// cols_width.push(current_col_w);
// current_col_h = 0;
// current_col_w = 0;
// }
// let chars_per_col = Math.floor((h - 5) / size_px);
// if (chars_per_col < 1) chars_per_col = 1;
// let item_cols = Math.ceil(l / chars_per_col);
// cols_width.push(item_cols * (size_px * 1.1));
// } else {
// if (current_col_h === 0) {
// current_col_h = item_h;
// current_col_w = size_px;
// } else if (current_col_h + v_gap + item_h <= h) {
// current_col_h += v_gap + item_h;
// if (size_px > current_col_w) current_col_w = size_px;
// } else {
// cols_width.push(current_col_w);
// current_col_h = item_h;
// current_col_w = size_px;
// }
// }
// }
// if (current_col_h > 0) cols_width.push(current_col_w);
// let totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0);
// if (totalWidth <= w && (!any_wrapped || s === shrinkLimit)) {
// break;
// }
// s -= 1;
// }
// if (s < shrinkLimit) s = shrinkLimit;
// let $namelist = $(`<div class='liveList'></div>`).css({
// "writing-mode": "vertical-rl",
// "display": "flex",
// "flex-flow": "wrap",
// "align-content": "center",
// "align-items": "center",
// "margin": "auto",
// "width": `${w}px`,
// "height": `${h}px`,
// "border": "0px solid transparent",
// "padding": "0px",
// "font-family": el.fontFamily,
// "column-gap": `${v_gap}px`
// });
// let self = this;
// names.forEach(name => {
// // 🚀 將 maxLen 傳遞給渲染函式
// $namelist.append(self.renderLiveSpan(name, el, s, maxLen));
// });
// return $namelist;
//},
//// 🚀 接收 maxLen 參數
//renderLiveSpan(name, el, fontSize, maxLen = 1) {
// let h = parseFloat(el.height);
// let th = parseFloat(el.textHeight) || h;
// let size_px = fontSize * 1.333;
// let l = name.length;
// let natural_max_h = maxLen * size_px;
// let boxH_num = Math.max(th, natural_max_h);
// if (boxH_num > natural_max_h * 1.5) {
// boxH_num = natural_max_h * 1.5;
// }
// let minH = `${boxH_num}px`;
// let alignStyle = "justify";
// let spanDisplay = "block";
// let spanJustifyContent = "space-between";
// let contentH = l * size_px;
// let emptySpace = boxH_num - contentH;
// let gapsCount = l > 1 ? (l - 1) : 1;
// let avgGap = emptySpace / gapsCount;
// // 🚀 關鍵修正:當陽上名字字距過大觸發防過度拉扯時,改為「靠上對齊 (start)」而不是置中 (center)
// if (alignStyle === "justify" && avgGap > (size_px * 1.5)) {
// alignStyle = "start"; // 改為靠上,讓字首切齊
// spanDisplay = "block";
// spanJustifyContent = "flex-start"; // Flex 靠上
// }
// return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
// "display": spanDisplay,
// "justify-content": spanJustifyContent,
// "align-items": "center",
// "min-height": minH,
// "max-height": `${h}px`,
// "width": "auto",
// "text-align": alignStyle,
// "text-align-last": alignStyle,
// "margin-left": "0px",
// "margin-top": "0px",
// "text-justify": "inter-character",
// "word-break": "break-all",
// "white-space": "pre-line"
// });
//},
//renderLiveList(names, el) {
// let h = parseFloat(el.height);
// let w = parseFloat(el.width);
// let th = parseFloat(el.textHeight) || h;
// let h_gap = parseFloat(el.textSpan) || 0; // 陽上的 textSpan 通常作為水平欄距 (column-gap)
// let v_gap = 0; // 陽上通常不需要垂直間距
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class admin_print_print_multi_preview : MyWeb.function
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
}
}
+8 -3
View File
@@ -29,15 +29,20 @@ function init_print() {
}
//console.log(n,t,t.trim().length);
});
//console.log(i, $(this).html(),$(this).text());
//console.log(i, txt==htm);
//console.log(i, txt_arr, line, line_len);
console.log(i, $(this).html(),$(this).text());
console.log(i, txt==htm);
console.log(i, txt_arr, line, line_len);
line_len = Math.ceil(line_len / 3) * 3;
//if(line>1){
css = $(this).attr("style")||"";
if(css.length>0){
css += ";";
}
if (line_len > 20) {
line = Math.ceil( line_len / 20);
line_len = 20;
}
css += "--lines:" + line + ";--line_len:" + line_len + ";";
//字少時, 加空間
+20 -3
View File
@@ -152,7 +152,7 @@
.catch(error => console.log(error))
},
yulan_print() {
_url = HTTP_HOST + 'admin/printpw/index.aspx';
_url = HTTP_HOST + 'admin/print/print_multi_preview.aspx'; //'admin/printpw/index.aspx';
var form = document.createElement("form");
form.method = "POST";
form.action = _url;
@@ -166,20 +166,27 @@
hiddenField.value = value;
form.appendChild(hiddenField);
};
let orders=[]
let orders = []
let list = []
console.log("Selected orders:", this.order_data.selected);
this.order_data.selected.forEach(x => {
orders.push(x.order_no)
list.push({
num: x.f_num, order_no: x.order_no
})
})
if (orders.length==0) {
return false;
}
localStorage.setItem("list", JSON.stringify(list));
addHiddenField("orders", orders);
document.body.appendChild(form); // Not entirely sure if this is necessary
form.submit();
document.body.removeChild(form);
},
yulan_single(item) {
_url = HTTP_HOST + '/admin/printpw/index.aspx';
_url = HTTP_HOST + 'admin/print/print_multi_preview.aspx'; //'/admin/printpw/index.aspx';
var form = document.createElement("form");
form.method = "POST";
form.action = _url;
@@ -193,6 +200,16 @@
hiddenField.value = value;
form.appendChild(hiddenField);
};
console.log("Selected orders:", this.order_data.selected);
let list = []
//console.log("print_data.selected:",this.print_data.item);
this.order_data.selected.forEach(item => {
list.push({
num: item.f_num, order_no: item.order_no
})
})
console.log("Selected orders list:", list);
localStorage.setItem("list", JSON.stringify(list));
addHiddenField("order_no", item.order_no);
document.body.appendChild(form); // Not entirely sure if this is necessary
form.submit();
+183 -6
View File
@@ -6,7 +6,7 @@
<div class="mb-2 mb-sm-0 d-flex align-items-center gap-2">
<v-text-field v-model="query.start_date" label="活動起始日" type="date" dense outlined></v-text-field>
<v-text-field v-model="query.end_date" label="活動結束日" type="date" dense outlined></v-text-field>
<button class="btn btn-primary" @click="queryActivity">查詢</button>
<button class="btn btn-primary" @click.prevent="queryActivity">查詢</button>
<%--<span>查看疏文</span>
<select v-model="currentActivityNum" @change="onActivityChange" class="form-select" style="width: auto;">
<option value="">請選擇要查看的活動</option>
@@ -40,8 +40,80 @@
<v-col cols="12" md="3">
<v-text-field v-model="query.sStart" label="超薦列印編號前置" type="text" dense outlined></v-text-field>
</v-col>
<v-col cols="12" md="2">
<button class="btn btn-primary" @click.prevent="processShuwen">產生疏文</button>
</v-col>
</v-row>
<v-data-table
v-model="data_table.selected"
:items="data_table.list"
:search-props="search"
item-key="num"
:options.sync="options"
:headers="data_table.header"
:footer-props="data_table.footer"
:server-items-length="data_table.count"
:loading="data_table.loading"
:single-select="data_table.singleSelect"
show-select
hide-default-footer
:page.sync="data_table.page"
:items-per-page.sync="data_table.pageSize"
class="elevation-1">
<template #item.up_time="{ item }" >
{{ item.up_time|timeString('YYYY/MM/DD') }}
</template>
<template #item.u_name="{ item }" >
{{ item.u_name }}
</template>
<template #item.status="{ item }" >
<a v-if="item.f_num != null && item.activity_num != null && item.status == 0" @click="checkIn(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-account"></i>報到</a>
<a v-if="item.f_num != null && item.activity_num != null && item.status == 1" class="btn btn-outline-secondary btn-sm opacity-50" style="pointer-events: none" ><i class="mdi mdi-account-check"></i>已報到</a>
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap>
<v-col cols="12" md="9">
<v-pagination
v-model="data_table.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ data_table.count }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field
v-model="data_table.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="data_table.page = parseInt($event, 10)"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-container>
<v-snackbar
v-model="snackbar.show"
timeout="2000">
{{ snackbar.text }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="snackbar.show = false"
>
關閉
</v-btn>
</template>
</v-snackbar>
</template>
<%--<div v-if="currentActivityNum">
<div><h2 style="text-align:center">消災疏文</h2></div>
@@ -76,6 +148,9 @@
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<script>
Vue.filter('timeString', function (value, myFormat) {
return value == null || value == "" ? "" : moment(value).format(myFormat || 'YYYY-MM-DD, HH:mm:ss');
});
let VueApp = new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
@@ -87,27 +162,124 @@
ShuWenItem: '',
updateShuWenLoading: false,
latest: false,
options: { multiSort: false },
snackbar: {
show: false,
text: "",
},
data_table: {
loading: false,
list: [],
selected: [],
singleSelect: false,
count: 0,
page: 1,
pageSize: 10,
header: [
{ text: '法會名稱', value: 'subject', align: 'start' },
{ text: '開始日期', value: 'startDate' },
{ text: '結束日期', value: 'endDate' },
//{ text: '', value: 'slot_btn', sortable: false, align: 'end' }
],
footer: {
showFirstLastPage: true,
itemsPerPageOptions: [5, 10, 20, 30],
},
},
query: {
start_date: '',
end_date: '',
xStart: '',//消災
sSatrt: ''//超薦
},
search: {},
}
},
methods: {
queryActivity() {//列出區間內的活動/法會
processShuwen() {
// console.log(this.data_table.selected)
if (this.query.xStart==="" || this.query.sStart==="") {
this.snackbar.text = "請輸入消災及超薦牌位列印編號前置碼";
this.snackbar.show = true;
return;
}
if (this.data_table.selected.length===0) {
this.snackbar.text = "請勾選要列印的法會";
this.snackbar.show = true;
return;
}
let activityNums = []
this.data_table.selected.forEach((x) => {
activityNums.push(x.num);
});
if (activityNums && activityNums.length>0) {
axios.post(HTTP_HOST + 'api/shuwen/RenderShuWenByGong', {
activitynum: activityNums.join(","),
xStart: this.query.xStart,sStart:this.query.sStart
})
.then(res => {
if (res.status == 200) {
//const blob = new Blob([res.data.filePath]);
//const url = window.URL.createObjectURL(blob);
// 2. Create a hidden anchoring element
const link = document.createElement('a');
link.href = res.data.filePath;
// 3. Define the desired filename
link.setAttribute('download', 'template.docx');
// 4. Append to DOM, click it to trigger save, and clean up
document.body.appendChild(link);
link.click();
// Free up memory and remove temporary elements
link.remove();
//window.URL.revokeObjectURL(url);
}
this.data_table.loading = false;
})
.catch(err => {
if (err.response && err.response.status === 400) {
alert('錯誤訊息:' + err.response.data.Message);
this.snackbar.text = '錯誤訊息:' + err.response.data.Message
} else {
//alert('發生錯誤:' + err.response.data.Message);
//console.log(err);
this.snackbar.text = '發生錯誤:'+err;// + err.response.data.Message
}
this.snackbar.show = true;
});
}
},
initActivity() {
axios.get(HTTP_HOST + 'api/activity')
queryActivity() {//列出區間內的活動/法會
this.data_table.loading=true
axios.post(HTTP_HOST + 'api/activity/GetListNew', { startDate_solar: this.query.start_date, endDate_solar: this.query.end_date }, { params: { page: 1, pageSize: 10000, sortDesc: false } })
.then(response => {
this.activityList = response.data;
// console.log("queryActivity:", response.data.list);
this.data_table.list = response.data.list;
this.data_table.count = response.data.count;
})
.catch(error => {
console.log(error)
this.snackbar.text = '發生錯誤:' + error.response.data.Message
this.snackbar.show = true;
}).finally(() => {
this.data_table.loading = false;
})
},
initActivity() {
//axios.get(HTTP_HOST + 'api/activity')
// .then(response => {
// this.activityList = response.data;
// })
// .catch(error => {
// console.log(error)
// })
},
onActivityChange() {
this.latest = false
this.getShuWen();
@@ -165,6 +337,11 @@
this.getShuWen()
}
},
computed: {
pageCount() {
return Math.ceil(this.data_table.count / this.data_table.pageSize)
},
}
})
</script>
</asp:Content>