1. 優化報名頁面:彈出查詢頁面、顯示剛新增資料
2. 新增報名頁面加上取消鍵 3. 優化登入頁面:按下 enter 自動換格/送出 4. 修復新增報名頁面中,不同 search_dialog 中的 page 參數相互連動之異常 5. 修改報名頁面列印格式 6. 修復報名頁面匯出功能 7. 優化報到功能 8. 報名頁面中,無查詢資料時不可點選匯出/列印按鈕 9. 匯出/列印報名管理報表時,若無資料則顯示提示 10. 修復列印管理報表後父視窗 UI 不能點擊的問題 11. 新增報名管理表單匯出 excel 功能 12. 於新增信眾、新增活動頁面加上取消鍵 13. 優化報名管理匯出功能:若篩選條件包含特定活動,自動於「匯出條件」欄位標註活動名稱 14. 優化報名查詢匯出功能:匯出之文件中加上「匯出條件」欄位 15. 修復信眾資料頁面中,使用「生日」作為篩選基準時,後續執行「列印查詢資料」與「匯出查詢資料」會報錯 16. 修復「列印信眾查詢」功能中,電話搜尋欄位未正確帶入查詢條件之異常 17. 解決中文輸入法輸入電話號碼的跳字問題 18. 新增品項管理介面排序功能
This commit is contained in:
+216
-115
@@ -7,19 +7,20 @@
|
||||
<div class="mb-2 mb-sm-0">
|
||||
<ul class="nav ps-0">
|
||||
<li class="nav-item pe-3">
|
||||
<select class="form-select" v-model="search.kind" @change="btn_search">
|
||||
<select class="form-select" v-model="search.kind" @change="btn_search" :disabled="isEditing">
|
||||
<option value="">選擇分類</option>
|
||||
<option v-for="item in itemKindList" :value="item.num">{{item.kind}}</option>
|
||||
</select>
|
||||
</li>
|
||||
<li class="nav-item pe-1">
|
||||
<a href="item_reg.aspx" class="btn btn-primary">
|
||||
<a href="item_reg.aspx" class="btn btn-primary" :class="{ 'disabled': isEditing }" >
|
||||
<i class="mdi mdi-plus"></i>新增
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item pe-1">
|
||||
<a @click="deleteAll" class="btn btn-outline-danger" title="刪除勾選的資料" ><i class="mdi mdi-trash-can"></i> 刪除勾選</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item pe-1">
|
||||
<a @click="deleteAll" class="btn btn-outline-danger" title="刪除勾選的資料" :class="{ 'disabled': isEditing }" ><i class="mdi mdi-trash-can"></i> 刪除勾選</a>
|
||||
</li>
|
||||
</ul>
|
||||
<%-- <div class="input-group mb-3" data-search-control="search1" @click="search_show(search_dialog.controls.search1)">
|
||||
<input class="form-control search-text" type="text" readonly
|
||||
@@ -29,26 +30,31 @@
|
||||
<i class="mdi mdi-view-list-outline"></i>
|
||||
</button>
|
||||
</div>--%>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div class="">
|
||||
|
||||
<asp:LinkButton ID="excel" runat="server" CssClass="btn btn-outline-success" OnClick="excel_Click"><span class="fa-solid fa-file-excel"></span> 匯出Excel</asp:LinkButton>
|
||||
<a v-if="!isEditing" @click="editClick" class="btn btn-outline-secondary" title="編輯排列順序"><i class="mdi mdi-swap-vertical"></i> 編輯排列順序</a>
|
||||
<a v-else @click="editClick" class="btn btn-outline-secondary" title="完成編輯排列順序"><i class="mdi mdi-swap-vertical"></i> 完成編輯</a>
|
||||
<div :style="isEditing ? 'pointer-events: none; opacity: 0.5;' : ''" style="display:inline-block;">
|
||||
|
||||
<asp:LinkButton ID="excel" runat="server" CssClass="btn btn-outline-success" OnClick="excel_Click" ><span class="fa-solid fa-file-excel"></span> 匯出Excel</asp:LinkButton>
|
||||
|
||||
</div>
|
||||
</div> </div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" runat="Server">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js"></script>
|
||||
<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({
|
||||
let VueApp = new Vue({
|
||||
el: '#app',
|
||||
vuetify: new Vuetify(vuetify_options),
|
||||
data() {
|
||||
return {
|
||||
isEditing: false,
|
||||
options: { multiSort: false },
|
||||
data_table: {
|
||||
loading: true,
|
||||
@@ -63,20 +69,20 @@
|
||||
{ text: '項目分類', value: 'kindsTxt' },
|
||||
{ text: '類別', value: 'categoryTxt' },
|
||||
{ text: '預設金額', value: 'price' },
|
||||
{ text: '庫存狀態', value: 'stock' },
|
||||
{ text: '庫存狀態', value: 'stock' },
|
||||
{ text: '停用', value: 'status' },
|
||||
{ text: '', value: 'slot_btn', sortable: false, align: 'end' },
|
||||
|
||||
|
||||
],
|
||||
footer:{
|
||||
footer: {
|
||||
showFirstLastPage: true,
|
||||
itemsPerPageOptions:[5,10,20,30],
|
||||
itemsPerPageOptions: [5, 10, 20, 30],
|
||||
},
|
||||
},
|
||||
search: {
|
||||
kind: '',
|
||||
subject: '',
|
||||
selltime1:'',
|
||||
selltime1: '',
|
||||
selltime2: '',
|
||||
uptime1: '',
|
||||
uptime2: '',
|
||||
@@ -98,7 +104,7 @@
|
||||
{ id: 'kind', title: '品項分類', value: '' },
|
||||
],
|
||||
selected: {},
|
||||
select(t,data) {
|
||||
select(t, data) {
|
||||
data.search.kind = t.num;
|
||||
data.btn_search()
|
||||
console.log("select search1", t);
|
||||
@@ -120,9 +126,9 @@
|
||||
}, snackbar: {
|
||||
show: false,
|
||||
text: "",
|
||||
}, itemKindList:{
|
||||
}, itemKindList: {
|
||||
num: 0,
|
||||
kind:''
|
||||
kind: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -149,9 +155,9 @@
|
||||
if (!isNaN(actItemPage) && actItemPage !== 1) {
|
||||
this.options.page = actItemPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
methods: {
|
||||
initKindList() {
|
||||
axios
|
||||
//.get(HTTP_HOST + 'api/activity_kind')
|
||||
@@ -174,26 +180,45 @@
|
||||
|
||||
},
|
||||
getDefault(clearpage = false) {
|
||||
const { sortBy, sortDesc, page, itemsPerPage } = this.options
|
||||
const params = {
|
||||
sortBy: sortBy[0], sortDesc: sortDesc[0],
|
||||
page: clearpage ? '1' :page, pageSize: itemsPerPage
|
||||
};
|
||||
this.data_table.loading = true
|
||||
axios
|
||||
.post(HTTP_HOST + 'api/activity/GetItemList', this.search, { params: params })
|
||||
.then(response => {
|
||||
this.data_table.list = response.data.list
|
||||
this.data_table.count = response.data.count;
|
||||
this.data_table.loading = false
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
},
|
||||
editItem(item) {
|
||||
console.log("edit", item);
|
||||
if (this.isEditing) {
|
||||
const { sortBy, sortDesc, page, itemsPerPage } = this.options
|
||||
const params = {
|
||||
sortBy: "sort_order", sortDesc: sortDesc[0],
|
||||
page: clearpage ? '1' : page, pageSize: 0
|
||||
};
|
||||
this.data_table.loading = true
|
||||
axios
|
||||
.post(HTTP_HOST + 'api/activity/GetItemList', this.search, { params: params })
|
||||
.then(response => {
|
||||
this.data_table.list = response.data.list
|
||||
this.data_table.count = response.data.count;
|
||||
this.data_table.loading = false
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
}
|
||||
else {
|
||||
const { sortBy, sortDesc, page, itemsPerPage } = this.options
|
||||
const params = {
|
||||
sortBy: sortBy[0], sortDesc: sortDesc[0],
|
||||
page: clearpage ? '1' : page, pageSize: itemsPerPage
|
||||
};
|
||||
this.data_table.loading = true
|
||||
axios
|
||||
.post(HTTP_HOST + 'api/activity/GetItemList', this.search, { params: params })
|
||||
.then(response => {
|
||||
this.data_table.list = response.data.list
|
||||
this.data_table.count = response.data.count;
|
||||
this.data_table.loading = false
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
}
|
||||
|
||||
},
|
||||
deleteItem(item) {
|
||||
|
||||
editItem(item) {
|
||||
console.log("edit", item);
|
||||
},
|
||||
deleteItem(item) {
|
||||
|
||||
if (confirm('是否確定刪除此筆資料?')) {
|
||||
const index = this.data_table.list.indexOf(item)
|
||||
if (index != -1) {
|
||||
@@ -203,7 +228,7 @@
|
||||
this.getDefault();
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteAll() {
|
||||
@@ -225,100 +250,155 @@
|
||||
.catch(error => console.log(error))
|
||||
}
|
||||
},
|
||||
btn_search() {
|
||||
editClick() {
|
||||
if (this.isEditing) {
|
||||
this.isEditing = false;
|
||||
const sortedIds = this.data_table.list.map(item => item.num);
|
||||
axios.post(HTTP_HOST + 'api/activity/SaveItemList', { ids: sortedIds })
|
||||
.then(response => {
|
||||
this.isEditing = false;
|
||||
this.getDefault();
|
||||
})
|
||||
.catch(error => {
|
||||
alert("儲存排序失敗:" + error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.data_table.loading = false;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
this.isEditing = true;
|
||||
this.getDefault()
|
||||
}
|
||||
},
|
||||
btn_search() {
|
||||
this.getDefault(true)
|
||||
},
|
||||
btn_all() {
|
||||
clearObjProps(this.search);
|
||||
this.btn_search()
|
||||
},
|
||||
//===
|
||||
search_show(curr) {
|
||||
//console.log("btn_click:", curr, curr.api_url);
|
||||
this.search_dialog.current = curr;
|
||||
this.search_clear()
|
||||
//this.search_get()//清除完自動會重抓, 故取消
|
||||
this.search_dialog.show = true;
|
||||
},
|
||||
search_clear() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
this.search_dialog.current.keys.forEach((t, i) => { t.value = '' })
|
||||
this.search_get()
|
||||
},
|
||||
search_get() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
let api_url = this.search_dialog.current.api_url;
|
||||
let keys = this.search_dialog.current.keys;
|
||||
//const { page, itemsPerPage } = this.options
|
||||
//const { sortBy, sortDesc, page, itemsPerPage } = this.options
|
||||
this.search_dialog.page = this.options.page ?? 1
|
||||
let params = { page: this.search_dialog.page, pageSize: 10 };//url params
|
||||
var search = {};//post body
|
||||
keys.forEach((t, i) => {
|
||||
search[t.id] = t.value;
|
||||
});
|
||||
},
|
||||
//===
|
||||
search_show(curr) {
|
||||
//console.log("btn_click:", curr, curr.api_url);
|
||||
this.search_dialog.current = curr;
|
||||
this.search_clear()
|
||||
//this.search_get()//清除完自動會重抓, 故取消
|
||||
this.search_dialog.show = true;
|
||||
},
|
||||
search_clear() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
this.search_dialog.current.keys.forEach((t, i) => { t.value = '' })
|
||||
this.search_get()
|
||||
},
|
||||
search_get() {
|
||||
if (!this.search_dialog.current.keys) return;
|
||||
let api_url = this.search_dialog.current.api_url;
|
||||
let keys = this.search_dialog.current.keys;
|
||||
//const { page, itemsPerPage } = this.options
|
||||
//const { sortBy, sortDesc, page, itemsPerPage } = this.options
|
||||
this.search_dialog.page = this.options.page ?? 1
|
||||
let params = { page: this.search_dialog.page, pageSize: 10 };//url params
|
||||
var search = {};//post body
|
||||
keys.forEach((t, i) => {
|
||||
search[t.id] = t.value;
|
||||
});
|
||||
|
||||
console.log("search_get", api_url, search, params, this.options);
|
||||
this.search_dialog.loading = true
|
||||
axios.post(api_url, search, { params: params })
|
||||
.then(response => {
|
||||
this.search_dialog.list = response.data.list
|
||||
this.search_dialog.count = response.data.count
|
||||
this.search_dialog.loading = false
|
||||
console.log("search_get", api_url, search, params, this.options);
|
||||
this.search_dialog.loading = true
|
||||
axios.post(api_url, search, { params: params })
|
||||
.then(response => {
|
||||
this.search_dialog.list = response.data.list
|
||||
this.search_dialog.count = response.data.count
|
||||
this.search_dialog.loading = false
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
this.search_dialog.list = []
|
||||
this.search_dialog.count = 0
|
||||
this.search_dialog.loading = false
|
||||
this.snackbar.text = "錯誤:" + error
|
||||
this.snackbar.show = true
|
||||
})
|
||||
},
|
||||
search_headers() {
|
||||
if (!this.search_dialog.current.columns) return;
|
||||
r = [];
|
||||
this.search_dialog.current.columns.forEach((t, i) => {
|
||||
r.push({
|
||||
text: t.title,
|
||||
align: 'start',
|
||||
sortable: false,
|
||||
value: t.id,
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
this.search_dialog.list = []
|
||||
this.search_dialog.count = 0
|
||||
this.search_dialog.loading = false
|
||||
this.snackbar.text = "錯誤:" + error
|
||||
this.snackbar.show = true
|
||||
})
|
||||
},
|
||||
search_headers() {
|
||||
if (!this.search_dialog.current.columns) return;
|
||||
r = [];
|
||||
this.search_dialog.current.columns.forEach((t, i) => {
|
||||
r.push({
|
||||
text: t.title,
|
||||
align: 'start',
|
||||
sortable: false,
|
||||
value: t.id,
|
||||
})
|
||||
})
|
||||
return r
|
||||
},
|
||||
search_select(row) {
|
||||
let curr = this.search_dialog.current;
|
||||
let target = $(`[data-search-control=${curr.id}]`);
|
||||
curr.selected = row;
|
||||
target.children("input.search-text").val(curr.selected[curr.text_prop])//text
|
||||
target.children("input:hidden").val(curr.selected[curr.value_prop])//value
|
||||
if (curr.select instanceof Function) {
|
||||
curr.select(row,this);
|
||||
}
|
||||
this.search_dialog.show = false;
|
||||
},
|
||||
return r
|
||||
},
|
||||
search_select(row) {
|
||||
let curr = this.search_dialog.current;
|
||||
let target = $(`[data-search-control=${curr.id}]`);
|
||||
curr.selected = row;
|
||||
target.children("input.search-text").val(curr.selected[curr.text_prop])//text
|
||||
target.children("input:hidden").val(curr.selected[curr.value_prop])//value
|
||||
if (curr.select instanceof Function) {
|
||||
curr.select(row, this);
|
||||
}
|
||||
this.search_dialog.show = false;
|
||||
},
|
||||
saveOrder(event) {
|
||||
const movedItem = this.data_table.list.splice(event.oldIndex, 1)[0];
|
||||
this.data_table.list.splice(event.newIndex, 0, movedItem);
|
||||
},
|
||||
|
||||
},
|
||||
computed: {
|
||||
pageCount() {
|
||||
return Math.ceil(this.data_table.count / this.data_table.pageSize)
|
||||
},
|
||||
computedHeaders() {
|
||||
return this.data_table.header.map(h => {
|
||||
if (h.value === 'slot_btn') {
|
||||
return { ...h, sortable: false };
|
||||
}
|
||||
|
||||
return {
|
||||
...h,
|
||||
sortable: this.isEditing ? false : (h.sortable !== false)
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
currency: function (value) {
|
||||
return value == null || value == "" ? "" :
|
||||
('$' + parseFloat(value).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,").replace(".00", ""));
|
||||
},
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
sortableDataTable: {
|
||||
bind(el, binding, vnode) {
|
||||
const options = {
|
||||
animation: 150,
|
||||
onUpdate: function (event) {
|
||||
vnode.child.$emit('sorted', event)
|
||||
}
|
||||
}
|
||||
Sortable.create(el.getElementsByTagName('tbody')[0], options)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||
<uc1:alert runat="server" ID="L_msg" Text="" />
|
||||
<div id="content" class="container-fluid">
|
||||
<v-data-table
|
||||
<v-data-table
|
||||
v-sortable-data-table
|
||||
@sorted="saveOrder"
|
||||
:headers="computedHeaders"
|
||||
v-model="data_table.selected"
|
||||
:items="data_table.list"
|
||||
:search-props="search"
|
||||
@@ -332,8 +412,29 @@
|
||||
show-select
|
||||
hide-default-footer
|
||||
:page.sync="data_table.page"
|
||||
:items-per-page.sync="data_table.pageSize"
|
||||
:items-per-page.sync= "isEditing ? -1 :data_table.pageSize"
|
||||
class="elevation-1">
|
||||
<template v-slot:header.data-table-select="{ on, props }">
|
||||
<v-simple-checkbox
|
||||
v-if="!isEditing"
|
||||
v-bind="props"
|
||||
v-on="on"
|
||||
></v-simple-checkbox>
|
||||
|
||||
<v-icon v-else small>mdi-swap-vertical</v-icon>
|
||||
</template>
|
||||
<template v-slot:item.data-table-select="{ item, isSelected, select }">
|
||||
<v-simple-checkbox
|
||||
v-if="!isEditing"
|
||||
:value="isSelected"
|
||||
@input="select($event)"
|
||||
></v-simple-checkbox>
|
||||
|
||||
<div v-else class="handle" style="cursor: grab;">
|
||||
<v-icon color="grey darken-1">mdi-drag-vertical</v-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.price="{ item }" >
|
||||
{{item.price | currency }}
|
||||
</template>
|
||||
@@ -352,12 +453,12 @@
|
||||
</template>
|
||||
|
||||
|
||||
<template #item.slot_btn="{ item }">
|
||||
<a :href="'item_reg.aspx?num='+item.num" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-pencil-box-outline"></i>修改</a>
|
||||
<a @click="deleteItem(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-trash-can"></i>刪除</a>
|
||||
<template #item.slot_btn="{ item }" >
|
||||
<a v-if="!isEditing" :href="'item_reg.aspx?num='+item.num" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-pencil-box-outline"></i>修改</a>
|
||||
<a v-if="!isEditing" @click="deleteItem(item)" class="btn btn-outline-secondary btn-sm"><i class="mdi mdi-trash-can"></i>刪除</a>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-container>
|
||||
<v-container v-if="!isEditing">
|
||||
<v-row class="align-baseline" wrap>
|
||||
<v-col cols="12" md="9">
|
||||
<v-pagination
|
||||
|
||||
@@ -148,7 +148,7 @@ public partial class admin_activity_index2 : MyWeb.config
|
||||
}
|
||||
|
||||
var tdesc = publicFun.enum_desc<Model.activity.category>();
|
||||
qry = qry.OrderByDescending(o => o.num);
|
||||
qry = qry.OrderByDescending(o => o.sort_order);
|
||||
var list = qry.ToList();
|
||||
if (list.Count > 0)
|
||||
{
|
||||
|
||||
@@ -118,6 +118,7 @@ public partial class admin_activity_item_reg : MyWeb.config
|
||||
L_msg.Text = "";
|
||||
|
||||
Model.actItem actItem = new Model.actItem();//新增
|
||||
int maxSort = _db.actItems.Max(x => (int?)x.sort_order) ?? 0;
|
||||
actItem.subject = subject.Text;
|
||||
actItem.print_init = print_init.Text;
|
||||
actItem.partno = PARTNO.Text;
|
||||
@@ -131,6 +132,7 @@ public partial class admin_activity_item_reg : MyWeb.config
|
||||
actItem.is_reconcile = is_reconcile_item.Checked ? "Y" : "N";
|
||||
actItem.demo = demo.Text;
|
||||
actItem.customize_data = customize_data.Text;
|
||||
actItem.sort_order = maxSort + 1;
|
||||
|
||||
_db.actItems.Add(actItem);
|
||||
_db.SaveChanges();
|
||||
|
||||
@@ -827,7 +827,7 @@
|
||||
<div class="">
|
||||
<asp:Button ID="add" runat="server" Text="送出" OnClick="add_Click" CssClass="btn btn-primary" />
|
||||
<asp:Button ID="edit" runat="server" Text="修改" Visible="false" OnClick="edit_Click" CssClass="btn btn-primary" />
|
||||
<asp:Button ID="goback" runat="server" Text="回列表" Visible="false" CausesValidation="false" OnClick="goback_Click" CssClass="btn btn-outline-secondary" />
|
||||
<asp:Button ID="goback" runat="server" Text="取消" Visible="true" CausesValidation="false" OnClick="goback_Click" CssClass="btn btn-outline-secondary" />
|
||||
</div>
|
||||
</asp:Content>
|
||||
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||
|
||||
Reference in New Issue
Block a user