Files
17168ERP/web/admin/print/tablet_edit/editor.html
2025-11-29 10:56:19 +08:00

758 lines
39 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="../../../js/vuetify.css" rel="stylesheet" />
<link href="../../../js/mdi-font/css/materialdesignicons.min.css" rel="stylesheet" />
<script src="../../../js/vue.min.js"></script>
<script src="../../../js/vuetify.min.js"></script>
<script src="../../../js/axios.min.js"></script>
<style>
html {
overflow: hidden;
}
.scrollable-list {
max-height: 250px;
height: 220px;
overflow-y: auto;
overflow-x: hidden;
}
.scrollable-list .v-list-item__content {
padding: 4px 0px;
}
.scrollable-list .v-list-item {
min-height: 30px;
padding: 0 8px;
}
.scrollable-list .v-list-item__action {
min-width: 0 !important;
width: auto !important;
margin-right: 0 !important;
}
.scrollable-list .v-btn--icon.v-size--default {
height: 24px;
width: 24px;
}
#app .v-card__subtitle,
#app .v-card__text,
#app .v-card__title {
padding: 12px 16px 0 16px;
}
#app .v-list-item.hover-item .v-list-item__subtitle {
/*display: none !important;*/
display: flex !important;
}
#app .v-list-item.hover-item:hover .v-list-item__subtitle {
display: flex !important;
}
.badge-deceased {
background-color: #ffc904;
color: #300a0a;
padding: 2px 4px;
border-radius: 20px;
font-weight: bold;
font-size: 10px;
display: inline-block;
position: relative;
top: -2px;
}
.badge-zero {
background-color: #CCC;
color: #FFF;
padding: 2px 4px;
border-radius: 20px;
font-weight: bold;
font-size: 10px;
display: inline-block;
position: relative;
top: -2px;
}
.equal-card {
min-height: 260px;
}
.checkbox-narrow {
min-width: 32px;
width: 32px;
padding: 0;
margin-right: 4px;
}
/* num=0 刪除按鈕警示樣式 */
.btn-delete-warning:hover {
background-color: #fff3cd !important;
border-radius: 50%;
}
.btn-delete-warning:hover .v-icon {
color: #856404 !important;
}
</style>
<link rel="stylesheet" href="print.css">
</head>
<body>
<div id="app">
<v-app>
<v-container>
<v-row>
<v-col cols="4" md="4">
<v-card class="pa-2" outlined tile>
<div class="docs editor">
<div class="page border" data-body-class="tblt-l a3">
<div class="content">
<div class="bg"><img alt="Alternate Text" :src="'../' + item_type_image"></div>
<div class="text">
<div class="top_text_1"></div>
<div class="top_text_2">{{tabletItem.print_id}}</div>
<div class="top_text_3"></div>
<div class="right_text text-block fit-text vertical text-start border"
style="--lines:0;--line_len:5;"></div>
<div class="mid_text text-block fit-text vertical border"
:style="mid_text_style" v-html="join_mid_text"></div>
<div class="mid_text_2 text-block fit-text vertical border"
style="--lines:0;--line_len:5;"></div>
<div class="left_text text-block fit-text vertical text-start border"
:style="left_text_style" v-html="join_left_text"></div>
<div class="txt_up vertical" v-if="item_type == 'B'">陽上</div>
<div class="txt_down vertical" v-if="item_type == 'B'">拜薦</div>
</div>
</div>
</div>
</div>
</v-card>
<div class="mt-2">
<v-btn color="primary" @click="saveData">儲存牌位</v-btn>
</div>
</v-col>
<v-col cols="4" md="4" id="type_edit_B" v-if="item_type==='B'" class="d-flex flex-column">
<v-card elevation="2" class="mb-2 equal-card">
<v-card-text class="py-2 px-2">
<v-row no-gutters align="center">
<v-col cols="auto">
<v-checkbox v-model="isAllSelected"
@change="toggleSelectAll"
color="primary"
hide-details
label="疏文代表"
class="ma-0"></v-checkbox>
</v-col>
<v-col cols="auto" class="ml-4">
<span class="subtitle-2 grey--text text--darken-1">超渡-已選</span>
</v-col>
<v-spacer></v-spacer>
<v-col cols="auto">
<v-btn color="primary" icon small @click="openAddDialog('Y')">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-col>
</v-row>
</v-card-text>
<v-list class="scrollable-list">
<v-list-item-group>
<v-list-item v-for="(member, index) in family_deceased_Y_selected" :key="index" class="hover-item">
<v-list-item-action>
<v-checkbox v-model="member.IsShuWen"
color="primary"
class="checkbox-narrow d-inline-flex align-center" />
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
<span class="badge-zero" v-if="member.num==0">0</span>
<span class="badge-deceased" v-if="member.deceased">{{ member.deceased ? '卍' : '' }}</span>
{{ member.fam_name }}
<v-chip v-if="member.fam_title" small>
{{ member.fam_title }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle class="d-flex justify-end">
<v-btn v-if="member.num === 0" icon @click="toggleNoSpace(member)" :title="'不加空格'">
<v-icon :color="member.nospace ? 'green' : 'grey lighten-1'">mdi-backspace</v-icon>
</v-btn>
<v-btn icon @click="moveUp(member, 'Y', index)" :disabled="index === 0">
<v-icon color="grey lighten-1">mdi-arrow-up</v-icon>
</v-btn>
<v-btn icon @click="moveDown(member, 'Y', index)" :disabled="index === family_deceased_Y_selected.length - 1">
<v-icon color="grey lighten-1">mdi-arrow-down</v-icon>
</v-btn>
<v-btn icon @click="confirmRemove(member, index, 'Y')" :class="{'btn-delete-warning': member.num === 0}">
<v-icon color="grey lighten-1">mdi-minus</v-icon>
</v-btn>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action class="align-self-start">
<v-btn icon @click="breakAfter(member, 'Y')">
<v-icon :color="member.option_break ? 'blue' : 'grey lighten-1'">
mdi-subdirectory-arrow-left
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
<v-card elevation="2" class="equal-card">
<v-card-subtitle>超渡-可選</v-card-subtitle>
<v-list class="scrollable-list">
<v-list-item-group>
<v-list-item v-for="member in family_deceased_Y_unselected" :key="member.fam_name">
<v-list-item-content>
<v-list-item-title>
<span class="badge-deceased" v-if="member.deceased">{{ member.deceased ? '卍' : '' }}</span>
{{ member.fam_name }}
<v-chip v-if="member.fam_title" small>
{{ member.fam_title }}
</v-chip>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click="addToSelected(member, 'Y')">
<v-icon color="grey lighten-1">mdi-plus</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
<v-col cols="4" md="4" id="type_edit_AB" class="d-flex flex-column">
<v-card elevation="2" class="mb-2 equal-card">
<v-card-text class="py-2 px-2">
<v-row no-gutters align="center">
<v-col cols="auto" v-if="item_type==='A'">
<v-checkbox v-model="isAllSelected"
@change="toggleSelectAll"
color="primary"
hide-details
label="疏文代表"
class="ma-0"></v-checkbox>
</v-col>
<v-col cols="auto" :class="{'ml-4': item_type==='A'}">
<span class="subtitle-2 grey--text text--darken-1">陽上/祈福-已選</span>
</v-col>
<v-spacer></v-spacer>
<v-col cols="auto">
<v-btn color="primary" icon small @click="openAddDialog('N')">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-col>
</v-row>
</v-card-text>
<v-list class="scrollable-list">
<v-list-item-group>
<v-list-item v-for="(member, index) in family_deceased_N_selected" :key="index" class="hover-item">
<v-list-item-action v-if="item_type==='A'">
<v-checkbox v-model="member.IsShuWen"
color="primary"
class="checkbox-narrow d-inline-flex align-center" />
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
<span class="badge-zero" v-if="member.num==0">0</span>
<span class="badge-deceased" v-if="member.deceased">{{ member.deceased ? '卍' : '' }}</span>
{{ member.fam_name }}
<v-chip v-if="member.fam_title" small>
{{ member.fam_title }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle class="d-flex justify-end">
<v-btn v-if="member.num === 0" icon @click="toggleNoSpace(member)" :title="'不加空格'">
<v-icon :color="member.nospace ? 'green' : 'grey lighten-1'">mdi-backspace</v-icon>
</v-btn>
<v-btn icon @click="moveUp(member, 'N', index)" :disabled="index === 0">
<v-icon color="grey lighten-1">mdi-arrow-up</v-icon>
</v-btn>
<v-btn icon @click="moveDown(member, 'N', index)" :disabled="index === family_deceased_N_selected.length - 1">
<v-icon color="grey lighten-1">mdi-arrow-down</v-icon>
</v-btn>
<v-btn icon @click="confirmRemove(member, index, 'N')" :class="{'btn-delete-warning': member.num === 0}">
<v-icon color="grey lighten-1">mdi-minus</v-icon>
</v-btn>
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action class="align-self-start">
<v-btn icon @click="breakAfter(member, 'N')">
<v-icon :color="member.option_break ? 'blue' : 'grey lighten-1'">
mdi-subdirectory-arrow-left
</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
<v-card elevation="2" class="equal-card">
<v-card-subtitle>陽上/祈福-可選</v-card-subtitle>
<v-list class="scrollable-list">
<v-list-item-group>
<v-list-item v-for="member in family_deceased_N_unselected" :key="member.fam_name">
<v-list-item-content>
<v-list-item-title>
<span class="badge-deceased" v-if="member.deceased">{{ member.deceased ? '卍' : '' }}</span>
{{ member.fam_name }}
<v-chip v-if="member.fam_title" small>
{{ member.fam_title }}
</v-chip>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click="addToSelected(member, 'N')">
<v-icon color="grey lighten-1">mdi-plus</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
</v-row>
</v-container>
<div style="display:block;">
<textarea id="desc_iframe" style="width: 900px; height: 100px;">{{tabletItem}}</textarea>
</div>
<!-- 新增項目對話框 -->
<v-dialog v-model="addDialog" max-width="400">
<v-card>
<v-card-title style="font-size: 1rem;">新增項目</v-card-title>
<v-card-text>
<div class="mb-2">
<v-chip
v-for="(phrase, idx) in phrases"
:key="idx"
small
class="mr-1 mb-1"
:title="phrase.length > 5 ? phrase : ''"
@click="newItemText = (newItemText || '') + phrase"
style="cursor: pointer;"
>{{ phrase.length > 5 ? phrase.slice(0, 5) + '...' : phrase }}</v-chip>
</div>
<v-text-field
v-model="newItemText"
label="請輸入名稱"
outlined
dense
clearable
autofocus
@keyup.enter="confirmAddItem"
></v-text-field>
</v-card-text>
<v-card-actions>
<div class="d-flex align-center">
<v-checkbox
class="pt-0"
v-model="noSpace"
label="不加空格"
dense
hide-details
></v-checkbox>
<v-icon class="ml-2 mt-1">mdi-backspace</v-icon>
</div>
<v-spacer></v-spacer>
<v-btn text @click="addDialog = false">取消</v-btn>
<v-btn color="primary" @click="confirmAddItem">確定</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 確認刪除對話框 -->
<v-dialog v-model="deleteDialog" max-width="350">
<v-card>
<v-card-title style="font-size: 1rem;">確認刪除</v-card-title>
<v-card-text>
確定要刪除「<strong>{{ deleteMemberName }}</strong>」嗎?<br>
<span class="red--text">此為手動新增項目,刪除後無法復原。</span>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="deleteDialog = false">取消</v-btn>
<v-btn color="error" @click="doRemove">確定刪除</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-app>
</div>
<script>
var VueApp = new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
isAllSelected: false,
dialog: false,
addDialog: false, // 新增項目對話框
addDialogType: '', // 'Y' 或 'N'
newItemText: '', // 新增項目的文字
noSpace: true, // 不加空格 checkbox預設勾選
deleteDialog: false, // 確認刪除對話框
deleteIndex: -1, // 要刪除的 index
deleteType: '', // 'Y' 或 'N'
deleteMemberName: '', // 要刪除的名稱(顯示用)
phrases: [], // 常用片語
familyMembers: [], // 親友名單
tabletItem: {}, // 傳入的資料(信眾/牌位資訊)
item: {},
family_deceased_Y_selected: [], // 超渡/超薦/超冤名單
family_deceased_N_selected: [] // 消災/陽上名單
};
},
watch: {
family_deceased_N_selected: {
handler(newValue) {
if (this.item_type === 'A') {
if (Array.isArray(newValue) && newValue.length > 0) {
this.isAllSelected = newValue.every(member => member.IsShuWen === true);
}
}
},
deep: true
},
family_deceased_Y_selected: {
handler(newValue) {
if (this.item_type === 'B') {
if (Array.isArray(newValue) && newValue.length > 0) {
this.isAllSelected = newValue.every(member => member.IsShuWen === true);
}
}
},
deep: true
}
},
computed: {
family_deceased_Y_unselected() {
return this.familyMembers.filter(member =>
member.deceased === true &&
!this.family_deceased_Y_selected.some(selected => selected.num === member.num));
},
family_deceased_N_unselected() {
return this.familyMembers.filter(member =>
member.deceased === false &&
!this.family_deceased_N_selected.some(selected => selected.num === member.num));
},
item_type() {
return this.tabletItem?.actitem_num_selected?.text?.includes('超') ? 'B' : 'A';
// A:消災, 陽上.....
// B:超渡, 超薦.....
},
item_type_image() {
if (this.item_type === 'B') {
return 'tablet-1.svg';
} else {
return 'tablet-2.svg';
}
},
join_mid_text() {
const target = this.item_type === 'B'
? this.family_deceased_Y_selected
: this.family_deceased_N_selected;
return this.join_text(target);
},
join_left_text() {
let target = '';
if (this.item_type === 'B') {
target = this.join_text(this.family_deceased_N_selected);
}
return target;
},
mid_text_style() {
const text = this.join_mid_text; // Remove function call since join_mid_text is a computed property
return this.calculateTextStyle(text);
},
left_text_style() {
if (this.item_type !== 'B') return '--lines:1;--line_len:5';
return this.calculateTextStyle(this.join_left_text);
}
},
mounted() {
// 載入常用片語
fetch('phrases.json')
.then(res => res.json())
.then(data => { this.phrases = data; })
.catch(err => console.error('載入 phrases.json 失敗:', err));
// 監聽來自父頁面的消息
window.addEventListener('message', (event) => {
this.isAllSelected = false;
console.log('editor.html - received message event');
console.log('editor.html - event origin:', event.origin);
console.log('editor.html - parent origin:', window.parent.location.origin);
if (event.origin === window.parent.location.origin) {
console.log('editor.html - origin check passed');
console.log('editor.html - received data:', event.data);
const receivedData = event.data;
this.family_deceased_Y_selected = [];
this.family_deceased_N_selected = [];
if (receivedData.tabletItem) {
console.log('editor.html - updating tabletItem:', receivedData.tabletItem);
this.tabletItem = receivedData.tabletItem;
if (receivedData.familyMembers) {
this.familyMembers = receivedData.familyMembers; // 更新 familyMembers
console.log("app mounted, window message: ", this.familyMembers, receivedData);
}
if (receivedData.tabletItem) {
this.tabletItem = receivedData.tabletItem;
}
// 處理 f_num_tablet 資料
if (this.tabletItem.f_num_tablet) {
try {
const data = JSON.parse(this.tabletItem.f_num_tablet);
if (this.item_type === 'B') {
// B類型超渡、超薦等
this.family_deceased_Y_selected = data.mid_items || [];
this.family_deceased_N_selected = data.left_items || [];
this.family_deceased_Y_selected.forEach(item => {
if (item.IsShuWen === undefined) {
Vue.set(item, 'IsShuWen', false);
}
});
if (this.family_deceased_Y_selected.length > 0) {
this.isAllSelected = this.family_deceased_Y_selected.every(member => member.IsShuWen === true);
} else {
this.isAllSelected = false;
}
} else {
// A類型消災、陽上等
this.family_deceased_Y_selected = [];
this.family_deceased_N_selected = data.mid_items || [];
this.family_deceased_N_selected.forEach(item => {
if (item.IsShuWen === undefined) {
Vue.set(item, 'IsShuWen', false);
}
});
if (this.family_deceased_Y_selected.length > 0) {
this.isAllSelected = this.family_deceased_N_selected.every(member => member.IsShuWen === true);
} else {
this.isAllSelected = false;
}
console.log(this.family_deceased_N_selected)
}
} catch (e) {
console.error('解析牌位資料時發生錯誤:', e);
}
}
}
}
});
},
methods: {
toggleSelectAll(checked) {
this.isAllSelected = checked
if (this.item_type === 'B') {
this.family_deceased_Y_selected.forEach(member => {
member.IsShuWen = checked;
});
}
else if (this.item_type === 'A') {
this.family_deceased_N_selected.forEach(member => {
member.IsShuWen = checked;
});
}
console.log(checked)
console.log(this.family_deceased_N_selected)
console.log(this.family_deceased_Y_selected)
},
openAddDialog(type) {
this.addDialogType = type;
this.newItemText = '';
this.noSpace = true; // 重置為預設勾選
this.addDialog = true;
},
confirmAddItem() {
if (!this.newItemText.trim()) {
return; // 不允許空白
}
const newItem = {
num: 0,
fam_name: this.newItemText.trim(),
fam_gender: '',
deceased: false,
fam_title: '',
option_break: false,
IsShuWen: this.isAllSelected,
nospace: this.noSpace
};
if (this.addDialogType === 'Y') {
this.family_deceased_Y_selected.push(newItem);
} else if (this.addDialogType === 'N') {
this.family_deceased_N_selected.push(newItem);
}
this.addDialog = false;
this.newItemText = '';
},
addToSelected(member, type) {
const selectedMember = {
num: member.num,
fam_name: member.fam_name,
fam_gender: member.fam_gender,
deceased: member.deceased,
fam_title: member.fam_title, // 新增,讓上方顯示稱謂 tag
option_break: false, // 默認為 false根據需要可以更改
IsShuWen: this.isAllSelected //是否加入疏文名單,默認false
};
console.log(this.isAllSelected)
if (type === 'Y') {
this.family_deceased_Y_selected.push(selectedMember);
console.log(this.family_deceased_Y_selected)
} else if (type === 'N') {
this.family_deceased_N_selected.push(selectedMember);
console.log(this.family_deceased_N_selected)
}
},
confirmRemove(member, index, type) {
if (member.num === 0) {
// num=0 需確認
this.deleteIndex = index;
this.deleteType = type;
this.deleteMemberName = member.fam_name;
this.deleteDialog = true;
} else {
// num!=0 直接刪除
this.removeFromSelected(index, type);
}
},
doRemove() {
this.removeFromSelected(this.deleteIndex, this.deleteType);
this.deleteDialog = false;
},
removeFromSelected(index, type) {
if (type === 'Y') {
this.family_deceased_Y_selected.splice(index, 1);
} else if (type === 'N') {
this.family_deceased_N_selected.splice(index, 1);
}
},
moveUp(member, type, index) {
if (index > 0) {
const array = type === 'Y' ? this.family_deceased_Y_selected : this.family_deceased_N_selected;
const temp = array[index - 1];
this.$set(array, index - 1, member);
this.$set(array, index, temp);
}
},
moveDown(member, type, index) {
if (index < (type === 'Y' ? this.family_deceased_Y_selected : this.family_deceased_N_selected).length - 1) {
const array = type === 'Y' ? this.family_deceased_Y_selected : this.family_deceased_N_selected;
const temp = array[index + 1];
this.$set(array, index + 1, member);
this.$set(array, index, temp);
}
},
breakAfter(member, type) {
console.log("breakAfter", member, type);
member.option_break = !member.option_break;
},
toggleNoSpace(member) {
this.$set(member, 'nospace', !member.nospace);
},
join_text(target) {
let result = '';
target.forEach((member, index) => {
// 每項前面加空格,但例外:
// 1. 第一項前面不加空格 (index === 0)
// 2. nospace === true 的項目前面不加空格
if (index > 0 && !member.nospace) {
result += ' ';
}
result += member.fam_name;
// 處理換行:如果 option_break 為 true 且不是最後一項,加 <br>
if (member.option_break && index < target.length - 1) {
result += '<br>';
}
});
result = result.replace('|', '<br>');
return result;
},
calculateTextStyle(text) {
if (!text) return '--lines:1;--line_len:5';
const lines = text.split('<br>');
const line_len = Math.max(...lines.map(line => {
return Array.from(line).reduce((acc, char) => {
// Full width (Chinese) characters
if (char.match(/[\u4e00-\u9fa5]/)) {
return acc + 1;
}
// Half width (English, numbers, etc.)
return acc + 0.5;
}, 0);
}));
return `--lines:${lines.length};--line_len:${Math.ceil(line_len)}`;
},
saveData() {
// 其它資料欄位, 不應在這裡修改
// 應該在進來的時候, 就已經修改(insert)好了
let tablet_data = {};
if (this.item_type === 'B') {// B:超渡, 超薦.....
tablet_data = {
mid_items: this.family_deceased_Y_selected,
left_items: this.family_deceased_N_selected
}
}
else {// A:消災, 陽上.....
tablet_data = {
mid_items: this.family_deceased_N_selected,
left_items: null
}
}
console.log(tablet_data)
const ret = {
source: 'editor.btn.click',
data: {
tabletItem: this.tabletItem,
tablet_data: tablet_data
}
};
console.log("儲存牌位資料", ret);
window.parent.postMessage(ret, '/');
}
}
});
</script>
<script>
const desc_iframe = document.getElementById('desc_iframe');
const btn = document.getElementById('btn');
//window.addEventListener('message', (event) => {
// if (event.source === window.parent) {
// let data = event.data;
// if (data.source === 'order.btn.click') {
// console.log('editor message', event.source, window.parent);
// app.item = data;
// console.log(app);
// desc_iframe.value = JSON.stringify(data);
// }
// }
//});
/*
btn.addEventListener('click', () => {
const ret = {
source: 'editor.btn.click',
desc: desc_iframe.value,
}
debugger;
console.log("btn click", ret);
window.parent.postMessage(ret, '/');
});
*/
</script>
</body>
</html>