update from old git

This commit is contained in:
2025-09-04 18:30:54 +08:00
parent af2c152ef6
commit 61502cb3bd
46 changed files with 6420 additions and 0 deletions

View File

@@ -74,6 +74,102 @@
<script src="<%=ResolveUrl("~/js/moment.min.js")%>"></script>
<script src="<%=ResolveUrl("~/js/sweetalert2/sweetalert2.all.min.js") %>"></script>
<script src="<%=ResolveUrl("~/admin/Templates/TBS5ADM001/js/Script.js")%>"></script>
<script>
//全局的VUE組件操作提示組件
Vue.component('message-modal', {
data() {
return {
show: false,
title: '提示',
message: '',
status: 'success', // 'success' or 'error'
callback: null
};
},
methods: {
open({ title = '提示', message = '', status = 'success', callback = null }) {
this.title = title;
this.message = message;
this.status = status;
this.callback = callback;
this.show = true;
},
close() {
this.show = false;
if (this.callback) this.callback();
}
},
template: `
<div class="modal fade" tabindex="-1" :class="{ show: show }" style="display: block;" v-if="show" @click.self="close">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header" :class="status === 'success' ? 'bg-success text-white' : 'bg-danger text-white'">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" @click="close"></button>
</div>
<div class="modal-body">
<p>{{ message }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn" :class="status === 'success' ? 'btn-success' : 'btn-danger'" @click="close">確定</button>
</div>
</div>
</div>
</div>
`
});
Vue.component('confirm-modal', {
data() {
return {
show: false,
title: '確認操作',
message: '是否確定要執行此操作?',
confirmCallback: null,
cancelCallback: null
};
},
methods: {
open(opts) {
this.title = opts.title || '確認操作';
this.message = opts.message || '是否確定要執行此操作?';
this.confirmCallback = opts.onConfirm || null;
this.cancelCallback = opts.onCancel || null;
this.show = true;
},
confirm() {
this.show = false;
if (this.confirmCallback) this.confirmCallback();
},
cancel() {
this.show = false;
if (this.cancelCallback) this.cancelCallback();
}
},
template: `
<div class="modal fade" tabindex="-1"
:class="{ show: show }"
style="display:block;" v-if="show"
@click.self="cancel">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
<h5 class="modal-title">{{ title }}</h5>
<button type="button" class="btn-close" @click="cancel"></button>
</div>
<div class="modal-body">
<p>{{ message }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="cancel">取消</button>
<button type="button" class="btn btn-danger" @click="confirm">確認</button>
</div>
</div>
</div>
</div>`
});
</script>
<asp:ContentPlaceHolder id="footer_script" runat="server">
</asp:ContentPlaceHolder>

1548
web/admin/guadan/create.aspx Normal file

File diff suppressed because it is too large Load Diff

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_guadan_create : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,52 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="edit.aspx.cs" Inherits="admin_guadan_guadantime_edit" %>
<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 class="container mt-4">
<h3 class="mb-4">编辑挂单时间设置</h3>
<asp:Panel CssClass="card p-4 shadow-sm">
<!-- 最早入住时间 -->
<div class="mb-3 row align-items-center">
<label class="col-sm-3 col-form-label">最早入住时间:</label>
<div class="col-sm-3">
<asp:DropDownList ID="ddlEarliestCheckIn" runat="server" CssClass="form-select"></asp:DropDownList>
</div>
</div>
<!-- 最晚退房时间 -->
<div class="mb-3 row align-items-center">
<label class="col-sm-3 col-form-label">最晚退房时间:</label>
<div class="col-sm-3">
<asp:DropDownList ID="ddlLatestCheckOut" runat="server" CssClass="form-select"></asp:DropDownList>
</div>
</div>
<!-- 是否可用 -->
<div class="mb-3 row align-items-center">
<label class="col-sm-3 col-form-label">是否可用:</label>
<div class="col-sm-3">
<asp:CheckBox ID="chkIsActive" runat="server" CssClass="form-check-input" />
</div>
</div>
<!-- 按钮 -->
<div class="mt-4">
<asp:Button ID="btnSave" runat="server" Text="保存" CssClass="btn btn-primary me-2" OnClick="btnSave_Click" />
<asp:HyperLink ID="hlBack" runat="server" NavigateUrl="timeindex.aspx" Text="返回列表" CssClass="btn btn-secondary" />
</div>
<!-- 消息提示 -->
<div id="divMessage" runat="server" class="mt-3 text-success"></div>
</asp:Panel>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

View File

@@ -0,0 +1,100 @@
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_guadan_guadantime_edit : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
private Guid SettingId
{
get
{
if (Guid.TryParse(Request.QueryString["id"], out Guid id))
return id;
else
return Guid.Empty;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindTimeDropdowns();
if (SettingId != Guid.Empty)
{
LoadSetting();
}
else
{
divMessage.InnerText = "未指定要编辑的记录。";
btnSave.Enabled = false;
}
}
}
private void BindTimeDropdowns()
{
ddlEarliestCheckIn.Items.Clear();
ddlLatestCheckOut.Items.Clear();
for (int h = 0; h < 24; h++)
{
ddlEarliestCheckIn.Items.Add($"{h:D2}:00");
ddlEarliestCheckIn.Items.Add($"{h:D2}:30");
ddlLatestCheckOut.Items.Add($"{h:D2}:00");
ddlLatestCheckOut.Items.Add($"{h:D2}:30");
}
}
private void LoadSetting()
{
var setting = _db.GuadanTimeSetting.FirstOrDefault(x => x.Id == SettingId);
if (setting != null)
{
ddlEarliestCheckIn.SelectedValue = setting.EarliestCheckIn;
ddlLatestCheckOut.SelectedValue = setting.LatestCheckOut;
chkIsActive.Checked = setting.IsActive;
}
else
{
divMessage.InnerText = "找不到指定记录。";
btnSave.Enabled = false;
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
if (SettingId == Guid.Empty)
return;
try
{
{
var setting = _db.GuadanTimeSetting.FirstOrDefault(x => x.Id == SettingId);
if (setting != null)
{
setting.EarliestCheckIn = ddlEarliestCheckIn.SelectedValue;
setting.LatestCheckOut = ddlLatestCheckOut.SelectedValue;
setting.IsActive = chkIsActive.Checked;
setting.UpdatedAt = DateTime.Now;
_db.SaveChanges();
divMessage.InnerText = "保存成功!";
}
else
{
divMessage.InnerText = "记录不存在,保存失败。";
}
}
}
catch (Exception ex)
{
divMessage.InnerText = "保存失败:" + ex.Message;
}
}
}

View File

@@ -0,0 +1,40 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="timeindex.aspx.cs" Inherits="admin_guadan_guadantime_timeindex" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<a href="timeset.aspx" class="btn btn-primary">
新建时间参数
</a>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<h3>挂单时间设置列表</h3>
<div id="divMessage" runat="server" style="color:red; margin-top:10px;"></div>
<asp:GridView ID="gvTimeSettings" runat="server" AutoGenerateColumns="False"
CssClass="table table-bordered" OnRowCommand="gvTimeSettings_RowCommand">
<Columns>
<asp:BoundField DataField="EarliestCheckIn" HeaderText="最早入住时间" />
<asp:BoundField DataField="LatestCheckOut" HeaderText="最晚退房时间" />
<asp:BoundField DataField="IsActive" HeaderText="是否可用" />
<asp:BoundField DataField="CreatedAt" HeaderText="创建时间" DataFormatString="{0:yyyy-MM-dd HH:mm}" />
<asp:TemplateField HeaderText="操作">
<ItemTemplate>
<asp:HyperLink ID="hlEdit" runat="server" Text="编辑"
NavigateUrl='<%# "edit.aspx?id=" + Eval("Id") %>' CssClass="btn btn-sm btn-primary"></asp:HyperLink>
<asp:Button ID="btnDelete" runat="server" Text="删除" CommandName="DeleteItem"
CommandArgument='<%# Eval("Id") %>' CssClass="btn btn-sm btn-danger"
OnClientClick="return confirm('确定删除这条记录吗?');" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class admin_guadan_guadantime_timeindex : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindTimeSettings();
}
}
private void BindTimeSettings()
{
{
// 取得最近所有挂单时间设置
var list = _db.GuadanTimeSetting
.OrderByDescending(x => x.CreatedAt)
.ToList();
gvTimeSettings.DataSource = list;
gvTimeSettings.DataBind();
}
}
protected void gvTimeSettings_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "DeleteItem")
{
Guid id = Guid.Parse(e.CommandArgument.ToString());
try
{
{
var setting = _db.GuadanTimeSetting.FirstOrDefault(x => x.Id == id);
if (setting != null)
{
_db.GuadanTimeSetting.Remove(setting);
_db.SaveChanges();
}
}
// 重新绑定列表
BindTimeSettings();
}
catch (Exception ex)
{
// 显示错误信息
divMessage.InnerText = "删除失败:" + ex.Message;
}
}
}
}

View File

@@ -0,0 +1,36 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="timeset.aspx.cs" Inherits="admin_guadan_guadantime_timeset" %>
<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>
<h3>入住时间設定</h3>
<div>
<label>最早入住時間:</label>
<asp:DropDownList ID="ddlEarliestCheckIn" runat="server"></asp:DropDownList>
</div>
<div>
<label>最晚退房時間:</label>
<asp:DropDownList ID="ddlLatestCheckOut" runat="server"></asp:DropDownList>
</div>
<div style="margin-top:10px;">
<asp:Button ID="btnSave" runat="server" Text="保存" OnClick="btnSave_Click" />
</div>
<div id="divMessage" runat="server" style="color:green; margin-top:10px;"></div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

View File

@@ -0,0 +1,65 @@
using Model;
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_guadan_guadantime_timeset : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// 預設時間
// 初始化半小时间隔
for (int h = 0; h < 24; h++)
{
ddlEarliestCheckIn.Items.Add(new System.Web.UI.WebControls.ListItem($"{h:D2}:00"));
ddlEarliestCheckIn.Items.Add(new System.Web.UI.WebControls.ListItem($"{h:D2}:30"));
ddlLatestCheckOut.Items.Add(new System.Web.UI.WebControls.ListItem($"{h:D2}:00"));
ddlLatestCheckOut.Items.Add(new System.Web.UI.WebControls.ListItem($"{h:D2}:30"));
}
// 預設值
ddlEarliestCheckIn.SelectedValue = "08:00";
ddlLatestCheckOut.SelectedValue = "14:00";
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
try
{
if(_db.GuadanTimeSetting.Where( a => a.IsActive == true).Count() > 0)
{
divMessage.InnerText = "已经存在有效的时间设置";
return;
}
string earliest = ddlEarliestCheckIn.SelectedValue;
string latest = ddlLatestCheckOut.SelectedValue;
var setting = new GuadanTimeSetting
{
Id = Guid.NewGuid(),
EarliestCheckIn = earliest,
LatestCheckOut = latest,
IsActive = true,
CreatedAt = DateTime.Now
};
_db.GuadanTimeSetting.Add(setting);
_db.SaveChanges();
// TODO: 保存到資料庫
divMessage.InnerText = $"保存成功!最早入住:{earliest}, 最晚退房:{latest}";
}
catch (Exception ex)
{
divMessage.InnerText = "保存失败:" + ex.Message;
}
}
}

112
web/admin/guadan/index.aspx Normal file
View File

@@ -0,0 +1,112 @@
<%@ 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">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<a href="create.aspx" class="btn btn-primary" >新建掛單</a>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="mx-5">
<v-data-table
:items="items"
:headers="headers">
<template #item.actions="{item}">
<a :href="'create.aspx?orderId='+item.guaDanOrderNo" class="btn btn-secondary">編輯</a>
<a class="btn btn-outline-danger" @click="deleteGuadanOrder(item)">取消</a>
</template>
<template #item.room="{item}">
{{item.room.name}}
</template>
<template #item.bed="{item}">
{{item.bed.name}}
</template>
<template #item.status="{item}">
{{item.status}}
</template>
<template #item.start_date="{item}">
{{item.start_date | timeString('YYYY/MM/DD HH:mm')}}
</template>
<template #item.end_date="{item}">
{{item.end_date | timeString('YYYY/MM/DD HH:mm')}}
</template>
<template #item.created_at="{item}">
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
</template>
</v-data-table>
</div>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
<!-- 刪除確認彈出視窗 -->
<confirm-modal ref="confirmModal"></confirm-modal>
</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({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
items: [],
headers: [
{ text: '登记挂单莲友', value: 'bookerName'},
{ text: '起始日期', value: 'start_date', align: 'center' },
{ text: '結束日期', value: 'end_date', align: 'center' },
{ text: '掛單人數', value: 'guest_count' },
{ text: '狀態', value: 'statusName', align: 'center' },
{ text: '建立時間', value: 'created_at', align: 'center' },
{ text: '備註', value: 'notes', align: 'center' },
{ text: '操作', value: 'actions', align: 'center' }
],
}
},
methods: {
getGuadanOrder() {
axios.get('/api/guadan/list')
.then((res) => {
this.items = res.data;
}).catch((err) => {
console.log(err);
})
},
deleteGuadanOrder(order) {
this.$refs.confirmModal.open({
message: '確認取消掛單?',
onConfirm: () => {
axios.post('/api/guadan/delete', 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: '取消失敗'
})
})
}
});
},
},
watch: {
},
mounted() {
this.getGuadanOrder();
},
});
</script>
</asp:Content>

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_guadan_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,143 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="statistics.aspx.cs" Inherits="admin_guadan_statistics" %>
<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 class="container my-4">
<!-- 客房统计 -->
<div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-5 g-3">
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">🏠</div>
<div class="text-muted small mt-1">总房间数量</div>
<div class="fw-bold fs-5 mt-1">{{ roomStatistics.roomCount }}</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">🚪</div>
<div class="text-muted small mt-1">空房间数量</div>
<div class="fw-bold fs-5 mt-1">{{ roomStatistics.emptyRoomCount }}</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">🛏️</div>
<div class="text-muted small mt-1">总床位数量</div>
<div class="fw-bold fs-5 mt-1">
{{ roomStatistics.bedCount }} (男:{{ roomStatistics.maleBedCount }},女:{{ roomStatistics.femaleBedCount }}
</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">🛌</div>
<div class="text-muted small mt-1">可用空床</div>
<div class="fw-bold fs-5 mt-1">
{{ roomStatistics.emptyBedCount }} (男:{{ roomStatistics.emptyMaleBedCount }},女:{{ roomStatistics.emptyFemaleBedCount }}
</div>
</div>
</div>
</div>
<!-- 挂单统计 -->
<div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-5 g-3 mt-1">
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">📝</div>
<div class="text-muted small mt-1">总挂单次数</div>
<div class="fw-bold fs-5 mt-1">{{ guadanStatistics.guadanTotalCount }}</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">📋</div>
<div class="text-muted small mt-1">当前挂单数量</div>
<div class="fw-bold fs-5 mt-1">{{ guadanStatistics.guadanCurrentCount }}</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">👥</div>
<div class="text-muted small mt-1">总挂单人数</div>
<div class="fw-bold fs-5 mt-1">
{{ guadanStatistics.guadanPeopleTotal }} (男:{{ guadanStatistics.guadanPeopleMale }},女:{{ guadanStatistics.guadanPeopleFemale }}
</div>
</div>
</div>
<div class="col">
<div class="p-3 bg-light text-center rounded shadow" style="min-height: 180px;">
<div class="fs-2">👨👩</div>
<div class="text-muted small mt-1">当前挂单人数</div>
<div class="fw-bold fs-5 mt-1">
{{ guadanStatistics.guadanPeopleCurrent }} (男:{{ guadanStatistics.guadanPeopleCurrentMale }},女:{{ guadanStatistics.guadanPeopleCurrentFemale }}
</div>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
roomStatistics: {
roomCount: 0,
emptyRoomCount: 0,
bedCount: 0,
maleBedCount: 0,
femaleBedCount: 0,
emptyBedCount: 0,
emptyMaleBedCount: 0,
emptyFemaleBedCount: 0
},
guadanStatistics: {
guadanTotalCount: 0,
guadanCurrentCount: 0,
guadanPeopleTotal: 0,
guadanPeopleMale: 0,
guadanPeopleFemale: 0,
guadanPeopleCurrent: 0,
guadanPeopleCurrentMale: 0,
guadanPeopleCurrentFemale: 0
},
}
},
methods: {
GetGuadanStatistics() {
axios.get('/api/guadanStatistics/GetGuadanStatistics')
.then((res) => {
this.roomStatistics = res.data.roomStatistics;
this.guadanStatistics = res.data.guadanStatistics;
})
}
},
watch: {
},
mounted() {
this.GetGuadanStatistics();
// 每两分钟更新一次 (2 * 60 * 1000 毫秒)
setInterval(() => {
this.GetGuadanStatistics();
}, 1 * 60 * 1000);
},
})
</script>
</asp:Content>

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_guadan_statistics : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,13 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="update.aspx.cs" Inherits="admin_guadan_update" %>
<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">
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

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_guadan_update : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,48 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="create.aspx.cs" Inherits="admin_region_bed_bedstatus_create" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<div></div>
<div>
<a href="index.aspx" class="btn btn-primary">回列表</a>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="container">
<div class="card shadow-sm my-3">
<div class="card-header">
<asp:Literal ID="L_title" runat="server" Text="新增床位状态" />
</div>
<div class="card-body">
<asp:Literal ID="L_msg" runat="server" />
<asp:HiddenField ID="HF_Id" runat="server" />
<div class="form-group mb-3">
<label for="TB_Name">名稱</label>
<asp:TextBox ID="TB_Name" runat="server" CssClass="form-control" />
</div>
<div class="form-group mb-3">
<label for="TB_Code">代碼</label>
<asp:TextBox ID="TB_Code" runat="server" CssClass="form-control" />
</div>
<div>
<label for="TB_Category">状态所属分类(挂单,房间,床位)</label>
<asp:DropDownList ID="TB_Category" runat="server" CssClass="form-control"></asp:DropDownList>
</div>
<div class="form-group mb-3">
<label for="Description">說明</label>
<asp:TextBox ID="Description" runat="server" CssClass="form-control" TextMode="MultiLine" Rows="3" />
</div>
<asp:Button ID="BTN_Save" runat="server" Text="儲存" CssClass="btn btn-primary" OnClick="BTN_Save_Click" />
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

View File

@@ -0,0 +1,103 @@
using Model;
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_region_bed_bedstatus_create : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) // 加這行
{
if (Guid.TryParse(Request.QueryString["statusid"], out Guid id))
{
LoadData(id);
L_title.Text = "編輯區域類型";
}
var categoryList = RegionRoomBedStatus.GetCategoryList();
// 假设你的下拉控件ID叫 ddlCategory
TB_Category.DataSource = categoryList;
TB_Category.DataTextField = "Text"; // 显示文字
TB_Category.DataValueField = "Value"; // 选项值
TB_Category.DataBind();
TB_Category.Items.Insert(0, new ListItem("--请选择分类--", ""));
}
}
private void LoadData(Guid id)
{
var rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>找不到資料</div>";
BTN_Save.Enabled = false;
return;
}
HF_Id.Value = rt.Uuid.ToString();
TB_Name.Text = rt.Name;
TB_Code.Text = rt.Code;
TB_Category.SelectedValue = rt.Category?.ToString();
Description.Text = rt.Description;
}
protected void BTN_Save_Click(object sender, EventArgs e)
{
try
{
RegionRoomBedStatus rt;
if (Guid.TryParse(HF_Id.Value, out Guid id))
{
// 更新
rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>資料不存在</div>";
return;
}
}
else
{
// 新增
rt = new RegionRoomBedStatus();
rt.Uuid = Guid.NewGuid();
_db.RegionRoomBedStatus.Add(rt);
}
rt.Name = TB_Name.Text.Trim();
if (rt.Name.Length == 0)
{
L_msg.Text = "<div class='alert alert-danger'>名稱不能为空</div>";
return;
}
rt.Code = TB_Code.Text.Trim();
rt.Description = Description.Text.Trim();
if(int.TryParse(TB_Category.SelectedValue, out int category))
{
rt.Category = category;
}
else
{
rt.Category = null;
}
_db.SaveChanges();
L_msg.Text = "<div class='alert alert-success'>儲存成功</div>";
// 如果是新增,更新隱藏欄位並切換標題為編輯
if (HF_Id.Value == "")
{
HF_Id.Value = rt.Uuid.ToString();
L_title.Text = "編輯區域類型";
}
Response.Redirect("index.aspx");
}
catch (Exception ex)
{
L_msg.Text = $"<div class='alert alert-danger'>錯誤:{ex.Message}</div>";
}
}
}

View File

@@ -0,0 +1,99 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_region_bed_bedstatus_index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<a href="create.aspx" class="btn btn-primary">
<i class="mdi mdi-plus"></i>新增床位狀態
</a>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="container">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">床位和掛單狀態列表</h5>
</div>
<div class="card-body">
<v-data-table
:headers="headers"
:items="items"
:loading="loading"
>
<template #item.actions="{item}">
<a :href="'create.aspx?statusid='+item.uuid" class="btn btn-primary"><i class="mdi mdi-pencil"></i>修改</a>
<button
type="button"
class="btn btn-outline-danger"
@click="confirmDeleteStatus(item)"
>
<i class="mdi mdi-delete"></i> 刪除
</button>
</template>
</v-data-table>
</div>
</div>
</div>
<message-modal ref="messageModal"></message-modal>
<confirm-modal ref="confirmModal"></confirm-modal>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<script>
new Vue({
'el': '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
headers: [
{ text: 'Id', value: 'id' },
{ text: '狀態名稱', value: 'name' },
{ text: '狀態代碼', value: 'code' },
{ text: '描述', value: 'description' },
{ text: '状态分类', value: 'categoryName'},
{ text: '', value: 'actions' }
],
items: [],
loading: true, // 初始時顯示動畫
}
},
methods: {
getStatusList() {
axios.get('/api/region/bed/status/list')
.then((res) => {
this.items = res.data
this.loading = false;
});
},
confirmDeleteStatus(item) {
this.$refs.confirmModal.open({
message: '是否確認刪除給床位狀態',
onConfirm: () => {
this.deleteStatus(item);
}
})
},
deleteStatus(item) {
axios.post('/api/region/bed/status/delete', null, {
params: { id: item.uuid }
})
.then(() => {
this.items = this.items.filter(i => i.uuid != item.uuid);
this.$refs.messageModal.open({
title: '操作成功',
message: '刪除成功!',
status: 'success'
});
})
},
},
watch: {
},
mounted() {
this.getStatusList();
}
});
</script>
</asp:Content>

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_region_bed_bedstatus_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,391 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_region_bed_index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<div class="card mb-3 w-100">
<div class="card-body bg-light p-3 rounded" style="height: 150px;">
<div class="d-flex h-100 w-100">
<!-- 左側統計區塊 40% -->
<div class="d-flex gap-2 pe-3" style="flex: 0 0 40%; height: 100%; border-right: 2px solid #ccc;">
<!-- 總房間 -->
<div class="p-2 border rounded text-center flex-fill d-flex flex-column justify-content-center" style="background-color: #f0f8ff;">
<div><strong>總房間</strong></div>
<div>{{ summary?.totalRooms || 0 }}</div>
<div class="text-primary">男: {{ summary?.totalMaleRooms || 0 }}</div>
<div class="text-danger">女: {{ (summary?.totalRooms || 0) - (summary?.totalMaleRooms || 0) }}</div>
</div>
<!-- 總床位 -->
<div class="p-2 border rounded text-center flex-fill d-flex flex-column justify-content-center" style="background-color: #fff0f5;">
<div><strong>總床位</strong></div>
<div>{{ summary?.totalBeds || 0 }}</div>
<div class="text-primary">男: {{ summary?.totalMaleBeds || 0 }}</div>
<div class="text-danger">女: {{ (summary?.totalBeds || 0) - (summary?.totalMaleBeds || 0) }}</div>
</div>
<!-- 可用床位 -->
<div class="p-2 border rounded text-center flex-fill d-flex flex-column justify-content-center" style="background-color: #f5fff0;">
<!-- 標題 -->
<h6 class="mb-2">查詢結果</h6>
<!-- 內容 -->
<div><strong>可用床位</strong></div>
<div class="text-primary">男: {{ filteredStats.maleBeds }}|女: {{ filteredStats.femaleBeds }}</div>
</div>
</div>
<!-- 右側篩選區塊 60% -->
<div class="d-flex gap-3 ms-10" style="flex: 0 0 60%; height: 100%; align-items: flex-start;">
<!-- 分組 1: 區域 & 房間下拉框 -->
<div class="p-2 rounded shadow-sm" style="background-color: #fff; min-width: 160px;">
<h6 class="mb-2">區域與房間</h6>
<div class="d-flex flex-column gap-2">
<select class="form-select" v-model="filter.selectedArea">
<option :value=null>全部區域</option>
<option v-for="region in filter.areas" :value="region.uuid" :key="region.uuid">{{region.name}}</option>
</select>
<select class="form-select" v-model="filter.selectedRoom">
<option :value=null>房間</option>
<option v-for="room in filter.rooms" :value="room.uuid" :key="room.uuid">{{room.name}}</option>
</select>
</div>
</div>
<!-- 分組 2: 日期篩選 -->
<div class="p-2 rounded shadow-sm" style="background-color: #fff; min-width: 160px;">
<h6 class="mb-2">日期篩選</h6>
<div class="d-flex flex-column gap-2">
<input type="date" class="form-control" v-model="filter.startDate" :min="today" @change="onStartDateChange"/>
<input type="date" class="form-control" v-model="filter.endDate" :min="filter.startDate || today"/>
</div>
</div>
<!-- 分組 3: 占用床位 & 性別復選框 -->
<div class="p-2 rounded shadow-sm" style="background-color: #fff; min-width: 200px;">
<h6 class="mb-2">床位與性別</h6>
<!-- 上排 -->
<div class="d-flex gap-3 p-2 mb-2 border rounded shadow-sm" style="background-color: #f8f9fa;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="occupied" v-model="filter.occupied" @change="toggleOccupied('occupied')">
<label class="form-check-label" for="occupied">已佔用床位</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unoccupied" v-model="filter.unoccupied" @change="toggleOccupied('unoccupied')">
<label class="form-check-label" for="unoccupied">可用床位</label>
</div>
</div>
<!-- 下排 -->
<div class="d-flex gap-3 p-2 border rounded shadow-sm" style="background-color: #f8f9fa;">
<div class="form-check">
<input type="checkbox" id="male" :checked="filter.Gender === true" @change="toggleGender(true)">
<label class="form-check-label" for="male">男</label>
</div>
<div class="form-check">
<input type="checkbox" id="female" :checked="filter.Gender === false" @change="toggleGender(false)">
<label class="form-check-label" for="female">女</label>
</div>
</div>
</div>
<!-- 分組 4: 查詢與清空按鈕 -->
<div class="p-2 rounded shadow-sm" style="background-color: #fff; min-width: 120px;">
<h6 class="mb-2">操作</h6>
<div class="d-flex flex-column gap-2">
<button class="btn btn-success" type="button" @click="search">查詢</button>
<button class="btn btn-primary" type="button" @click="clearFilter">清空條件</button>
</div>
</div>
</div>
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div v-for="region in regions" :key="region.uuid" class="region-block mb-4">
<h2 class="region-title mb-3">{{ region.regionPath }}</h2>
<div class="row g-3 justify-content-start">
<div v-for="room in region.room" :key="room.ruid" class="col-12 col-md-6 col-lg-4">
<div class="card h-100 shadow-sm" style="min-height: 300px; max-height: 400px; overflow-y: auto; border-radius: 0.5rem;">
<!-- 上部:房間名稱 -->
<div class="card-header bg-primary text-white fw-bold">
<h5 class="mb-0">{{ room.name + ' (' + (room.gender ? '男' : '女') + ')' }}</h5>
</div>
<!-- 下部:左右兩列 -->
<div class="card-body p-2">
<div class="row">
<!-- 左列:房間資訊 -->
<div class="col-3 border-end pe-3">
<p class="mb-1"><strong>總床位:</strong>{{ room.stats.bedCount }}</p>
<p class="text-success mb-1"><strong>可用:</strong>{{ room.stats.availableCount }}</p>
<p class="text-danger mb-0"><strong>已占用:</strong>{{ room.stats.bedCount - room.stats.availableCount }}</p>
</div>
<!-- 右列:床位清單 -->
<div class="col-9 ps-3">
<h6 class="fw-bold mb-2">床位清單</h6>
<div style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-bordered mb-0">
<thead>
<tr>
<th scope="col">床位名稱</th>
<th scope="col">是否可用</th>
<th scope="col">使用明細</th>
</tr>
</thead>
<tbody>
<tr v-for="bed in room.beds" :key="bed.uuid"
:class="bed.canuse ? 'table-success' : 'table-danger'">
<td>{{ bed.name }}</td>
<td :class="!bed.canuse ? 'text-danger' : 'text-success'">
{{ bed.canuse ? '是' : '否' }}
</td>
<td>
<button type="button" class="btn btn-primary" @click="showBedSchedule(bed)">查看明細</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<v-dialog v-model="bedSchedule.dialogVisible" max-width="900">
<v-card>
<v-card-title>
<span class="text-h6">床位排程明細 - {{ bedSchedule.selectedBed?.name }}</span>
<v-spacer></v-spacer>
<v-btn icon @click="closeBedSchedule">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-data-table
:headers="bedSchedule.scheduleHeaders"
:items="bedSchedule.selectedBed?.schedules || []"
class="elevation-1"
dense
hide-default-footer
:items-per-page="5"
>
<template #item.scheduleDate="{item}">
{{item.scheduledate|timeString('YYYY-MM-DD')}}
</template>
<template #item.actions =" {item}">
<a :href="'/admin/guadan/create.aspx?orderId='+item.guaDanOrderNo" class="btn btn-primary">查看掛單</a>
</template>
</v-data-table>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text color="primary" @click="closeBedSchedule">關閉</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<style>
.region-block { background:#f8f9fa; padding:15px; margin-bottom:20px; border-radius:8px; }
.region-title { font-size:16px; font-weight:bold; margin-bottom:10px; }
.room-list { display:grid; grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); gap:10px; }
.room-card { background:#fff; border:1px solid #ddd; padding:10px; border-radius:6px; }
.text-green { color: green; }
.text-red { color: red; }
</style>
<script>
Vue.filter('timeString', function (value, myFormat) {
return value == null || value == "" ? "" : moment(value).format(myFormat || 'YYYY-MM-DD, HH:mm:ss');
});
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data: {
regions: [],
summary: {},
todayStr: new Date().toISOString().split('T')[0],
today: new Date().toISOString().split('T')[0],
filter: {
// 日期篩選
startDate: null,
endDate: null,
Gender: null,
// 區域與房間選擇
selectedArea: null,
selectedRoom: null,
// 佔用床位狀態(互斥)
occupied: false, // 已佔用
unoccupied: false, // 未佔用
areas: [
],
rooms: [
]
},
bedSchedule: {
dialogVisible: false,
selectedBed: null,
scheduleHeaders: [
{ text: '使用日期', value: 'scheduledate' },
{ text: '掛單單號', value: 'guaDanOrderNo' },
{ text: '標題', value: 'title' },
{ text: '查看掛單', value: 'actions' },
],
},
},
methods: {
showBedSchedule(bed) {
this.bedSchedule.selectedBed = bed;
this.bedSchedule.dialogVisible = true;
console.log(bed)
},
closeBedSchedule() {
this.bedSchedule.selectedBed = null;
this.bedSchedule.dialogVisible = false;
},
search() {
this.GetRegionList();
},
onStartDateChange() {
if (this.filter.endDate && this.filter.endDate < this.filter.startDate) {
this.filter.endDate = this.filter.startDate;
}
if (!this.filter.endDate) {
this.filter.endDate = this.filter.startDate;
}
},
resetFilter() {
this.filter.startDate = null;
this.filter.endDate = null;
this.filter.selectedArea = null;
this.filter.selectedRoom = null;
this.filter.occupied = false;
this.filter.unoccupied = false;
this.filter.Gender = null;
},
clearFilter() {
this.resetFilter();
this.GetRegionList();
},
GetRegionList() {
const payload = {
startDate: this.filter.startDate,
endDate: this.filter.endDate,
areaId: this.filter.selectedArea,
roomId: this.filter.selectedRoom,
occupied: this.filter.occupied,
unoccupied: this.filter.unoccupied,
gender: this.filter.Gender,
};
axios.post('/api/region/list', payload)
.then((res) => {
this.regions = res.data.regions;
this.summary = res.data.summary; // 保存後端統計
})
.catch((err) => {
console.error('API 錯誤', err);
});
},
toggleOccupied(type) {
if (type === "occupied" && this.filter.occupied) {
this.filter.unoccupied = false;
}
if (type === "unoccupied" && this.filter.unoccupied) {
this.filter.occupied = false;
}
},
toggleGender(value) {
// 如果當前選擇就是自己,則取消選擇
this.filter.Gender = this.filter.Gender === value ? null : value;
console.log(this.filter.Gender);
},
getRegionWithRoom() {
axios.get('/api/region/regionwithroom')
.then((res) => {
this.filter.areas = res.data;
})
},
async loadRooms() {
try {
let url = "/api/room/roomwithbed";
const params = {};
if (this.filter.selectedArea) {
params.RegionUuid = this.filter.selectedArea;
}
const res = await axios.get(url, { params });
this.filter.rooms = res.data; // axios 會自動解析 json
this.filter.selectedRoom = null; // 切換區域時,重設房間選擇
} catch (error) {
console.error("載入房間失敗:", error);
}
},
},
watch: {
'filter.selectedArea'() {
this.loadRooms();
}
},
mounted() {
this.GetRegionList();
this.getRegionWithRoom();
this.loadRooms(); // 預設載入所有房間
},
computed: {
filteredStats() {
if (!this.regions || this.regions.length === 0) {
return { maleBeds: 0, femaleBeds: 0 };
}
let maleBeds = 0;
let femaleBeds = 0;
this.regions.forEach(region => {
(region.room || []).forEach(room => {
(room.beds || []).forEach(bed => {
if (bed.canuse) {
if (bed.gender) maleBeds++;
else femaleBeds++;
}
});
});
});
return { maleBeds, femaleBeds };
}
}
})
</script>
</asp:Content>

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_region_bed_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

956
web/admin/region/index.aspx Normal file
View File

@@ -0,0 +1,956 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_region_index" %>
<%@ Register Src="~/admin/_uc/alert.ascx" TagPrefix="uc1" TagName="alert" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server"></asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav class="mb-2 ps-3">
<button class="btn btn-primary me-2" @click="newRegion" type="button">
<i class="mdi mdi-plus"></i> 新增主區域
</button>
<button class="btn btn-secondary me-2" @click="expandAll" type="button">
<i class="mdi mdi-arrow-expand-all"></i> 全部展開
</button>
<button class="btn btn-secondary" @click="collapseAll" type="button">
<i class="mdi mdi-arrow-collapse-all"></i> 全部收起
</button>
</nav>
<nav v-if="form && selectedType==null">
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
<i class="bi bi-save me-1"></i> 儲存區域資料
</button>
</nav>
<nav class="btn-group mb-2 ps-3 pe-3" role="group" v-if="form && selectedType=='region'">
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
<i class="bi bi-save me-1"></i> 儲存區域資料
</button>
<div v-if="selectedRegionId">
<button class="btn btn-success me-2" @click="createSubRegion" type="button" >
<i class="mdi mdi-arrow-down-right"></i> 新增下層區域
</button>
<button class="btn btn-success me-2" type="button" @click="room.showCreateRoomDialog=true" >
<i class="mdi mdi-arrow-down-right"></i> 新建客房
</button>
<button class="btn btn-outline-danger" @click="deleteRegion" type="button">
<i class="mdi mdi-delete"></i> 刪除
</button>
</div>
</nav>
<nav class="btn-group mb-2 ps-3 pe-3" role="group" v-if="currentSelectRoom && selectedType=='room'">
<button class="btn btn-primary me-2" type="button" @click="roomUpdate">
<i class="bi bi-save me-1"></i> 儲存客房資料
</button>
<button class="btn btn-outline-danger" @click="confirmRoomDelete" type="button" v-if="currentSelectRoom">
<i class="mdi mdi-delete"></i> 刪除客房
</button>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<uc1:alert runat="server" ID="L_msg" Text="" />
<div class="container-fluid">
<div class="row">
<div class="col-sm-4 col-lg-3">
<div class="card shadow-sm my-2">
<div class="card-header">區域列表</div>
<div class="card-body">
<ul class="tree">
<li v-for="region in regions" :key="region.uuid">
<region-item
:item="region"
:selected-id="selectedId"
:selected-type="selectedType"
@select-region="selectRegion"
@select-room="selectRoom"
:expand-all="expandAllFlag"
:collapse-all="collapseAllFlag"
@clear-expand-all="expandAllFlag = false"
@clear-collapse-all="collapseAllFlag = false"
/>
</li>
</ul>
</div>
</div>
</div>
<div class="col-sm-4 col-lg-6" v-if="!currentSelectRoom">
<div class="card shadow-sm my-2"style="position: sticky; top: 20px;">
<div class="card-header">
<span>区域</span>
<span class="fw-bold" v-if="currentSelectRegion">
{{ ' ' + currentSelectRegion.name + ' ' }}
</span>
<span>
资料
</span>
以下 * 欄位為必填欄位
</div>
<div class="card-body">
<div class="row mt-5">
<div class="form-group col-5">
<label>區域名稱*</label>
<input type="text" class="form-control" v-model="form.name" />
</div>
<div class="form-group col-5">
<label>上層區域</label>
<select class="form-control" v-model="form.parentUuid">
<option :value="null">無</option>
<option v-for="r in flatRegions"
:value="r.uuid"
:disabled="disabledParentOptions.includes(r.uuid)">{{ r.name }}</option>
</select>
</div>
</div>
<div class="row mt-5">
<div class="form-group col-5">
<label>排序</label>
<input type="number" class="form-control" v-model="form.sortOrder" />
</div>
<div class="form-group col-5">
<label>區域類型</label>
<select class="form-control" v-model="form.regionTypeUuid">
<option :value="null">無</option>
<option v-for="type in regionTypes" :value="type.uuid">
{{ type.name }}
</option>
</select>
</div>
</div>
<div class="row mt-5">
<div class="form-group col-5">
<label>客房數量</label>
<input type="number" class="form-control" v-model="form.roomCount" />
</div>
<div class="form-group col-5">
<label>描述</label>
<textarea class="form-control" v-model="form.description"></textarea>
</div>
</div>
<div class="row">
<div class="form-group form-check form-switch mb-3 mt-3 col-5">
<input type="checkbox" class="form-check-input" id="isActiveCheck" v-model="form.isActive" />
<label class="form-check-label" for="isActiveCheck">是否啟用</label>
</div>
</div>
</div>
</div>
</div>
<!-- 客房編輯區 -->
<div class="col-sm-4 col-lg-6" v-else>
<div style="position: sticky; top: 20px;" >
<div class="card shadow-sm my-2">
<div class="card-header">
<span>客房</span>
<span class="fw-bold" v-if="currentSelectRoom">
{{ ' ' + currentSelectRoom.name + ' ' }}
</span>
<span>资料</span>
以下 * 欄位為必填欄位
</div>
<div class="card-body shadow">
<div class="row mt-5">
<div class="form-group col-5">
<label>客房名稱*</label>
<input v-model="room.room_form.name" class="form-control"/>
</div>
<div class="form-group col-5">
<label>床位數量</label>
<input v-model="room.room_form.bedCount" class="form-control"/>
</div>
</div>
<div class="row mt-5">
<div class="form-group col-5">
<label>客房所属区域</label>
<select class="form-control" v-model="room.room_form.regionUuid">
<option v-for="r in flatRegions"
:value="r.uuid"
>{{ r.name }}</option>
</select>
</div>
<div class="form-group form-check form-switch mb-3 mt-3 col-5 ms-5">
<input type="checkbox" class="form-check-input" id="roomIsActive" v-model="room.room_form.isActive" />
<label class="form-check-label" for="roomIsActive">是否啟用</label>
</div>
</div>
<div class="row mt-5">
<div class="mb-3">
<div class="form-check form-check-inline">
<input class="form-check-input"
type="radio"
name="gender"
id="roomMale"
:value="true"
v-model="room.room_form.gender"
required>
<label class="form-check-label" for="roomMale">男眾客房</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input"
type="radio"
name="gender"
id="roomFemale"
:value="false"
v-model="room.room_form.gender"
required>
<label class="form-check-label" for="roomFemale">女眾客房</label>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm my-2" v-if="currentSelectRoom">
<div>如果有修改編輯區域的資料,請先儲存後再進行床位操作</div>
<div class="card-header d-flex align-items-center justify-content-between">
<span class="fw-bold">床位列表</span>
<button type="button" class="btn btn-primary" @click="newBed">新增床位</button>
</div>
<div class="card-body">
<v-data-table
:headers="room_bed.bed_headers"
:items ="room_bed.bed_items"
>
<template #item.isactive="{item}">
{{item.isactive ? '啟用' : '停用'}}
</template>
<template #item.statusuuid="{item}">
{{getBedStatusNameById(item.statusuuid)}}
</template>
<template #item.action ="{item}">
<button type="button" class="btn btn-primary" @click="editBed(item)">
編輯
</button>
<button type="button" class="btn btn-outline-danger" @click="deleteBed(item)">
刪除
</button>
</template>
</v-data-table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 床位編輯 Modal -->
<v-dialog v-model="room_bed.showBedModal" max-width="500px" transition="none">
<v-card>
<v-card-title>
<span class="headline">{{room_bed.mode == 'create' ? '新增床位' : '編輯床位'}}</span>
</v-card-title>
<v-card-text>
<div class="mb-3">
<label class="form-label">床位名稱<span class="text-danger">*</span></label>
<input type="text" class="form-control" v-model="room_bed.newBedForm.Name" required />
</div>
<div class="mb-3">
<label class="form-label">狀態</label>
<select class="form-control" v-model="room_bed.newBedForm.statusuuid">
<option v-for="status in room_bed.bed_status" :value="status.uuid">
{{status.name}}
</option>
</select>
</div>
<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="isActiveCheck" v-model="room_bed.newBedForm.IsActive" />
<label class="form-check-label" for="isActiveCheck">是否啟用</label>
</div>
<div class="mb-3">
<label class="form-label d-block">性別 <span class="text-danger">*</span></label>
<div class="form-check form-check-inline">
<input class="form-check-input"
type="radio"
name="bedGender"
id="bedMale"
:value="true"
v-model="room_bed.newBedForm.Gender"
required>
<label class="form-check-label" for="bedMale">男眾床位</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input"
type="radio"
name="bedGender"
id="bedFemale"
:value="false"
v-model="room_bed.newBedForm.Gender"
required>
<label class="form-check-label" for="bedFemale">女眾床位</label>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="room_bed.showBedModal = false">取消</v-btn>
<v-btn color="primary" @click="room_bed.mode === 'create' ? saveBed() : saveEditBed()">儲存</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!--
新建客房dialog
-->
<v-dialog width="50%" max-width="80%" transition="none" v-model="room.showCreateRoomDialog">
<v-card style="min-height: 500px; max-height: 80vh; overflow-y: auto;font-size: 30px;">
<v-card-title>
<span style=" font-size:30px;">
新建客房
</span>
</v-card-title>
<v-card-text>
<div class="mb-3" style=" font-size:30px;">
<label class="form-label">客房名稱<span class="text-danger">*</span></label>
<input type="text" class="form-control" v-model="room.room_form.name" required />
</div>
<div class="mb-3" style=" font-size:25px;">
<label>床位数量</label>
<input type="number" class="form-control" v-model="room.room_form.bedCount" />
</div>
<div class="form-check form-switch mb-3" style=" font-size:25px;">
<input type="checkbox" class="form-check-input" v-model="room.room_form.isActive" />
<label class="form-check-label">是否啟用</label>
</div>
<div class="mb-3" style=" font-size:25px;">
<label class="form-label d-block">性別 <span class="text-danger">*</span></label>
<div class="form-check form-check-inline">
<input class="form-check-input"
type="radio"
name="creatRoomGender"
v-model="room.room_form.gender"
:value="1"
required>
<label class="form-check-label" style=" font-size:25px;">男眾客房</label>
</div>
<div class="form-check form-check-inline" style=" font-size:25px;">
<input class="form-check-input"
type="radio"
name="creatRoomGender"
v-model="room.room_form.gender"
:value="0"
required>
<label class="form-check-label" style=" font-size:25px;">女眾客房</label>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="room.showCreateRoomDialog = false" style=" font-size:25px;">取消</v-btn>
<v-btn color="primary" @click="roomCreate" style=" font-size:25px;">儲存</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
<!-- 刪除確認彈出視窗 -->
<confirm-modal ref="confirmModal"></confirm-modal>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server"></asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<style>
.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>
<script>
Vue.component('region-item', {
props: ['item', 'selectedId', 'selectedType', 'expandAll', 'collapseAll'],
data() {
return {
expanded: false, // 預設全部收起
selectedRoomId: null,
}
},
watch: {
expandAll(newVal) {
if (newVal) {
this.expanded = true;
// 執行完後發事件通知父組件清除標誌
this.$nextTick(() => this.$emit('clear-expand-all'));
}
},
collapseAll(newVal) {
if (newVal) {
this.expanded = false;
this.$nextTick(() => this.$emit('clear-collapse-all'));
}
}
},
computed: {
hasChildren() {
return this.item.children && this.item.children.length > 0;
},
icon() {
// 無論有無子節點,皆可點擊展開/收起
return this.expanded ? '▼' : '▶';
},
isSelected() {
return this.selectedType === 'region' && this.item.uuid === this.selectedId;
}
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
select() {
this.$emit('select-region', this.item);
},
selectRoom(room) {
this.selectedRoomId = room.uuid;
this.$emit('select-room', room); // 可以發事件給父組件
}
},
template: `
<div>
<span class="toggle-icon" @click="toggle">{{ icon }}</span>
<span @click="select"
class="region-item-label"
:class="{ 'selected': isSelected }">
{{ item.rooms.length>0 ? (item.name + '(' + item.rooms.length + '房)'): item.name }}
</span>
<!-- 子區域列表 -->
<ul v-if="hasChildren && expanded">
<li v-for="child in item.children" :key="child.uuid">
<region-item
:item="child"
:selected-id="selectedId"
:selected-type="selectedType"
:expand-all="expandAll"
:collapse-all="collapseAll"
@select-region="$emit('select-region', $event)"
@select-room="$emit('select-room', $event)"
@clear-expand-all="$emit('clear-expand-all')"
@clear-collapse-all="$emit('clear-collapse-all')"
/>
</li>
</ul>
<!-- 客房列表:無論是否有子區域,只要展開就顯示 -->
<ul v-if="item.rooms && item.rooms.length > 0 && expanded">
<li v-for="room in item.rooms" :key="'room-' + room.uuid"
@click="selectRoom(room)"
:class="{ 'selected-room': selectedType === 'room' && selectedId === room.uuid }"
style="cursor: pointer;">
<span class="bed-label">🛏️ {{ room.name + '(' + room.beds.length +'床)'}}</span>
</li>
</ul>
</div>
`
});
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
selectedId: null, // 被選中項目ID
selectedType: null, // 'region' 或 'room'
expandAllFlag: false, // 控制全部展開
collapseAllFlag: false, // 控制全部收起
showModal: false,
showDeleteModal: false,
modalMessage: '',
deleteModalMessage: '',
selectedRegionId: null,
regions: [],
flatRegions: [],
disabledParentOptions: [],
regionTypes: [],
currentSelectRegion: null,
currentSelectRoom: null,
form: {
uuid: null,
name: '',
description: '',
sortOrder: 0,
parentUuid: null,
regionTypeUuid: null,
isActive: true,
roomCount: null,
},
room: {
//room相關的變數放在這裡
room_form: {
uuid: null,
name: '',
gender: null,
isActive: true,
regionUuid: null,
bedCount: null,
regionUuid: null,
},
showCreateRoomDialog: false, //显示新建客房对话框
},
room_bed: {
mode: 'create', // 'create' or 'edit'
bed_items: [],
bed_headers: [
{ text: '床位編號', value: 'uuid' },
{ text: '床位名稱', value: 'name' },
{ text: '床位狀態', value: 'statusuuid' },
{ text: '是否啟用', value: 'isactive' },
{ text: '', value: 'action' },
],
newBedForm: {
uuid: null,
RegionUuid: null,
Name: '',
statusuuid: null,
IsActive: true,
Gender: null,
},
showBedModal: false,
bed_status: [], //床位狀態列表
}
};
},
methods: {
expandAll() {
this.expandAllFlag = true;
this.collapseAllFlag = false;
},
collapseAll() {
this.collapseAllFlag = true;
this.expandAllFlag = false;
},
async loadRegions() {
const res = await axios.post('/api/region/getRegionList');
this.regions = res.data;
this.flatRegions = this.flatten(res.data);
if (this.currentSelectRoom) {
}
},
loadRegionType() {
axios.post('/api/region/getRegionType')
.then(res => {
this.regionTypes = res.data
});
},
flatten(data, list = []) {
data.forEach(item => {
list.push({ uuid: item.uuid, name: item.name });
if (item.children && item.children.length) {
this.flatten(item.children, list);
}
});
return list;
},
selectRegion(region) {
this.selectedId = region.uuid;
this.selectedType = 'region';
this.selectedRegionId = region.uuid;
this.currentSelectRegion = region;
this.currentSelectRoom = null;
this.resetRoomForm();
this.form = {
uuid: region.uuid,
name: region.name,
description: region.description,
sortOrder: region.sortOrder,
parentUuid: region.parentUuid,
regionTypeUuid: region.regionTypeUuid,
isActive: region.isActive !== false, // 若為 null 也視為啟用
roomCount: region.roomCount,
};
// 禁止選擇自己與所有子孫作為上層
const node = this.findRegionById(this.regions, region.uuid);
this.disabledParentOptions = this.getAllDescendants(node);
},
newRegion() {
this.selectedRegionId = null;
this.form = {
uuid: null,
name: '',
description: '',
sortOrder: 0,
parentUuid: null,
isActive: true
};
this.disabledParentOptions = [];
this.currentSelectRegion = null;
this.currentSelectRoom = null;
},
createSubRegion() {
if (!this.selectedRegionId) return;
this.form = {
uuid: null,
name: '',
description: '',
sortOrder: 0,
parentUuid: this.selectedRegionId,
isActive: true
};
},
saveRegion() {
if (!this.form.name || this.form.name.trim() === '') {
this.$refs.messageModal.open({
title: "錯誤",
message: "請輸入區域名稱",
});
return;
}
const url = this.form.uuid ? '/api/region/update' : '/api/region/create';
axios.post(url, this.form)
.then((res) => {
//alert('儲存成功');
this.loadRegions();
//this.newRegion();
this.form.uuid = res.data.uuid;
this.selectedRegionId = res.data.uuid;
this.currentSelectRegion = JSON.parse(JSON.stringify(this.form));
this.$refs.messageModal.open({
title: "更新",
message: "更新成功",
});
})
.catch((error) => {
this.$refs.messageModal.open({
title: '更新提示',
message: error.response?.data?.message || "儲存失敗,請稍後再試。",
});
});
},
deleteRegion() {
this.$refs.confirmModal.open({
titil: '刪除提示',
message: '確定要刪除該區域嗎?刪除區域會一併刪除該區域下的所有區域和床位',
onConfirm: () => {
this.confirmDeleteRegion();
}
});
},
confirmDeleteRegion() {
axios.post('/api/region/delete', { uuid: this.form.uuid })
.then(() => {
this.showDeleteModal = false;
this.$refs.messageModal.open({
title: "刪除",
message: "刪除成功",
});
this.room_bed.bed_items = [];
this.currentSelectRegion = null;
this.currentSelectRoom = null;
this.loadRegions();
this.newRegion();
});
},
getAllDescendants(node) {
//尋找某個區域的所有子區域
const ids = [];
const dfs = (n) => {
ids.push(n.uuid);
if (n.children) {
n.children.forEach(child => dfs(child));
}
};
dfs(node);
return ids;
},
findRegionById(list, uuid) {
for (const item of list) {
if (item.uuid === uuid) return item;
if (item.children) {
const found = this.findRegionById(item.children, uuid);
if (found) return found;
}
}
return null;
},
//下面是床位相關的操作函數
newBed() {
//添加床位
if (!this.currentSelectRoom) {
this.$refs.messageModal.open({
title: '提示',
message: '請先選擇一個客房'
})
return;
}
this.room_bed.mode = 'create';
this.room_bed.newBedForm = {
uuid: null,
RoomUuid: this.currentSelectRoom.uuid,
Name: '',
statusuuid: null,
IsActive: true,
Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇
};
this.room_bed.showBedModal = true;
},
async saveBed() {
if (!this.room_bed.newBedForm.Name || this.room_bed.newBedForm.Name.trim() === '') {
this.$refs.messageModal.open({
title: '錯誤',
message: '請輸入床位名稱'
});
return;
}
try {
var res = await axios.post('/api/region/bed/create', this.room_bed.newBedForm);
this.room_bed.showBedModal = false;
this.$refs.messageModal.open({
title: '成功',
message: '新增床位成功'
});
// 重新載入區域資料
await this.loadRegions();
this.room_bed.bed_items.push({
...res.data
})
} catch (err) {
this.$refs.messageModal.open({
title: '錯誤',
message: err.response?.data?.message || '新增失敗'
});
}
},
deleteBed(bed) {
this.$refs.confirmModal.open({
titil: '刪除提示',
message: '確定要刪除該床位嗎',
onConfirm: () => {
this.confirmDeleteBed(bed);
}
});
},
confirmDeleteBed(bed) {
axios.post('/api/region/bed/delete', null, {
params: { uuid: bed.uuid }
}) // 假設後端吃的是 id
.then(() => {
// 成功後從本地列表移除
this.room_bed.bed_items = this.room_bed.bed_items.filter(item => item.uuid !== bed.uuid);
this.$refs.messageModal.open({
title: '刪除成功',
message: `已刪除床位「${bed.name}」`
});
})
.catch(err => {
this.$refs.messageModal.open({
title: '錯誤',
message: err.response?.data?.message || '刪除失敗'
});
});
},
editBed(bed) {
if (!this.currentSelectRoom) {
this.$refs.messageModal.open({
title: '提示',
message: '請先選擇一個客房'
})
return;
}
this.room_bed.mode = 'edit';
this.room_bed.newBedForm = {
uuid: bed.uuid,
RegionUuid: bed.regionUuid,
RoomUuid: bed.roomUuid,
Name: bed.name,
statusuuid: bed.statusuuid,
IsActive: bed.isactive,
Gender: bed.gender,
};
this.room_bed.showBedModal = true;
},
async saveEditBed() {
try {
await axios.post('/api/region/bed/update', this.room_bed.newBedForm);
this.room_bed.showBedModal = false;
const updated = this.room_bed.newBedForm;
const index = this.room_bed.bed_items.findIndex(b => b.uuid === updated.uuid);
if (index !== -1) {
this.$set(this.room_bed.bed_items, index, {
...this.room_bed.bed_items[index], // 保留原本未更新欄位
roomUuid: updated.RoomUuid,
name: updated.Name,
statusuuid: updated.statusuuid,
isactive: updated.IsActive
});
}
this.$refs.messageModal.open({
title: '成功',
message: '更新床位成功'
});
await this.loadRegions();
this.room_bed.bed_items = this.currentSelectRoom.beds;
//this.selectRegion(this.findRegionById(this.regions, this.form.id));
} catch (err) {
console.log(err)
this.$refs.messageModal.open({
title: '錯誤',
message: err.response?.data?.message || '更新失敗'
});
}
},
getBedStatus() {
//獲取床位狀態
axios.get('/api/region/bed/status/list')
.then((res) => {
this.room_bed.bed_status = res.data;
})
},
getBedStatusNameById(id) {
console.log(id)
//傳入一個Id獲取該Id對應的名稱
const status = this.room_bed.bed_status.find(i => i.uuid == id);
if (status) {
return status.name;
}
return "";
},
//------------------------------------------------
//room相關的函數
roomGet() {
},
roomCreate() {
this.room.room_form['regionUuid'] = this.currentSelectRegion.uuid
if (this.room.room_form.gender === null) {
this.$refs.messageModal.open({
message: "請選擇性別"
});
return;
}
axios.post('/api/region/room/create', this.room.room_form)
.then((res) => {
this.room.showCreateRoomDialog = false;
this.currentSelectRegion.rooms.push(res.data);
this.$refs.messageModal.open({
title: '提示',
message: '客房新建成功',
});
this.resetRoomForm();
}).catch((error) => {
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
});
})
},
async roomUpdate() {
try {
const res = await axios.post('/api/region/room/update', this.room.room_form);
this.$refs.messageModal.open({
message: '客房資料更新成功'
});
this.loadRegions();
this.currentSelectRoom = {
...this.room.room_form
};
} catch (error) {
console.error('更新失敗', error);
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
});
}
},
roomDelete() {
axios.post('/api/region/room/delete', { uuid: this.currentSelectRoom.uuid })
.then((res) => {
const region = this.findRegionById(this.regions, this.currentSelectRoom.regionUuid)//當前room所在的region
if (region) {
const index = region.rooms.findIndex(item => item.uuid === this.currentSelectRoom.uuid)
if (index !== -1) {
region.rooms.splice(index, 1); // 直接修改原數組
}
}
this.currentSelectRoom = null;
this.room_bed.bed_items = [];
//清空 beds
});
},
confirmRoomDelete() {
this.$refs.confirmModal.open({
title: '確認刪除',
message: '確認刪除該房間嗎?刪除房間會一併刪除房間床位,刪除後不可恢復',
onConfirm: () => {
this.roomDelete();
}
})
},
selectRoom(room) {
this.selectedId = room.uuid;
this.selectedType = 'room';
this.currentSelectRoom = room;
this.currentSelectRegion = null;
this.room.room_form = {
...this.currentSelectRoom
}
this.room_bed.bed_items = this.currentSelectRoom.beds;
},
resetRoomForm() {
this.room.room_form = {
uuid: null,
name: '',
gender: null,
isActive: true,
regionUuid: null,
bedCount: null,
}
},
//結束room相關的函數
//------------------------------------------------
},
watch: {
},
mounted() {
this.loadRegions();
this.loadRegionType();
this.getBedStatus();
}
});
</script>
</asp:Content>

View File

@@ -0,0 +1,19 @@
using Model;
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_region_index : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
}
}
}

View File

@@ -0,0 +1,95 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_regiontype_index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<a href="reg.aspx" class="btn btn-primary">
<i class="mdi mdi-plus"></i>新增區域類型
</a>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="container">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">區域類型管理</h5>
</div>
<div class="card-body">
<v-data-table
:headers="headers"
:items="items"
>
<template #item.isactive =" {item} ">
<v-icon small :color="item.isactive ? 'success' : 'grey'">
{{ item.isactive ? 'mdi-check-circle' : 'mdi-close-circle' }}
</v-icon>
{{ item.isactive ? '已啟用' : '已停用' }}
</template>
<template #item.actions="{item}">
<a :href="'reg.aspx?regiontypeid='+item.uuid" class="btn btn-primary"><i class="mdi mdi-pencil"></i>修改</a>
<button
type="button"
class="btn btn-outline-danger"
@click="regiontypeDelete(item)"
>
<i class="mdi mdi-delete"></i> 刪除
</button>
</template>
</v-data-table>
</div>
</div>
</div>
<message-modal ref="messageModal"></message-modal>
<confirm-modal ref="confirmModal"></confirm-modal>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
headers: [
{ text: '區域類型名稱', value: 'name' },
{ text: '區域代碼', value: 'code' },
{ text: '啟用', value: 'isactive' },
{ text: '操作', value: 'actions', sortable: false }
],
items: []
}
},
methods: {
getRegionTypeList() {
axios.post('/api/regiontype/getreiontypelist')
.then((res) => {
this.items = res.data;
})
},
regiontypeDelete(item) {
this.$refs.confirmModal.open({
'title': '刪除提示',
'message': `確定要刪除 ${item.name} ?`,
onConfirm: () => {
axios.post('/api/regiontype/delete',null, {
params: { uuid: item.uuid }
})
.then(() => {
this.items = this.items.filter(i => i.uuid != item.uuid);
this.$refs.messageModal.open({
title: '操作成功',
message: '刪除成功!',
status: 'success'
});
})
}
});
}
},
mounted() {
this.getRegionTypeList();
}
})
</script>
</asp:Content>

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_regiontype_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,46 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="reg.aspx.cs" Inherits="admin_regiontype_reg" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<div></div>
<div>
<a href="index.aspx" class="btn btn-primary">回列表</a>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="container">
<div class="card shadow-sm my-3">
<div class="card-header">
<asp:Literal ID="L_title" runat="server" Text="新增區域類型" />
</div>
<div class="card-body">
<asp:Literal ID="L_msg" runat="server" />
<asp:HiddenField ID="HF_Id" runat="server" />
<div class="form-group mb-3">
<label for="TB_Name">名稱</label>
<asp:TextBox ID="TB_Name" runat="server" CssClass="form-control" />
</div>
<div class="form-group mb-3">
<label for="TB_Name">代碼</label>
<asp:TextBox ID="TB_Code" runat="server" CssClass="form-control" />
</div>
<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="CB_IsActive" name="CB_IsActive" checked runat="server"/>
<label class="form-check-label" for="CB_IsActive">是否啟用</label>
</div>
<asp:Button ID="BTN_Save" runat="server" Text="儲存" CssClass="btn btn-primary" OnClick="BTN_Save_Click" />
</div>
</div>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
</asp:Content>

View File

@@ -0,0 +1,87 @@
using Model;
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_regiontype_reg : MyWeb.config
{
private Model.ezEntities _db = new Model.ezEntities();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
if (Guid.TryParse(Request.QueryString["regiontypeid"], out Guid id))
{
LoadData(id);
L_title.Text = "編輯區域類型";
}
}
}
private void LoadData(Guid id)
{
var rt = _db.RegionType.FirstOrDefault(r => r.Uuid == id);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>找不到資料</div>";
BTN_Save.Enabled = false;
return;
}
HF_Id.Value = rt.Uuid.ToString();
TB_Name.Text = rt.Name;
TB_Code.Text = rt.Code;
CB_IsActive.Checked = rt.IsActive;
}
protected void BTN_Save_Click(object sender, EventArgs e)
{
try
{
RegionType rt;
if (Guid.TryParse(HF_Id.Value, out Guid id))
{
// 更新
rt = _db.RegionType.FirstOrDefault(r => r.Uuid == id);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>資料不存在</div>";
return;
}
}
else
{
// 新增
rt = new RegionType();
rt.Uuid = Guid.NewGuid();
_db.RegionType.Add(rt);
}
rt.Name = TB_Name.Text.Trim();
if(rt.Name.Length == 0)
{
L_msg.Text = "<div class='alert alert-danger'>名稱不能为空</div>";
return;
}
rt.Code = TB_Code.Text.Trim();
rt.IsActive = CB_IsActive.Checked;
_db.SaveChanges();
L_msg.Text = "<div class='alert alert-success'>儲存成功</div>";
// 如果是新增,更新隱藏欄位並切換標題為編輯
if (HF_Id.Value == "")
{
HF_Id.Value = rt.Uuid.ToString();
L_title.Text = "編輯區域類型";
}
}
catch (Exception ex)
{
L_msg.Text = $"<div class='alert alert-danger'>錯誤:{ex.Message}</div>";
}
}
}