30 Commits

Author SHA1 Message Date
b776b411b4 上传资料库结构 2025-10-29 13:54:27 +08:00
e9f17a5037 神祖牌位管理模組,掛單模組前端URL添加HTTP_HOST 2025-10-29 13:48:20 +08:00
7d36d6b0a6 掛單調整 2025-10-14 13:44:23 +08:00
6fc82510cc 在掛單資料頁面,增加判斷掛單是否超時 2025-10-09 16:49:32 +08:00
4a36ce9c1c 挂单资料增加挂单单号查询,增加显示挂单单号 2025-10-03 16:41:34 +08:00
63cab37c87 Merge pull request 'hkj0918' (#2) from hkj0918 into main
Reviewed-on: #2
2025-10-03 08:12:49 +00:00
cd1e5c2cd0 挂单综合状态调整 2025-10-03 16:04:49 +08:00
d7b0f29296 统计修改 2025-10-03 14:37:33 +08:00
79854a2618 Merge pull request 'hkj0918' (#1) from hkj0918 into main
Reviewed-on: #1
2025-09-30 08:38:54 +00:00
93aaffd3d8 处理新建区域的时候出现的问题 2025-09-30 16:37:05 +08:00
71490b1fac guadan 2025-09-25 15:18:34 +08:00
c6bd763485 Merge remote-tracking branch 'origin/guadan0905' 2025-09-17 23:23:01 +08:00
ad06b77fae 添加生成舒文异常处理 2025-09-17 17:40:44 +08:00
af5b1f71fb 添加生成舒文异常处理 2025-09-17 17:38:29 +08:00
104f95eaec 修改舒文,牌位预览列印 2025-09-17 17:31:34 +08:00
c38dc55dff 处理挂单测试问题 2025-09-16 17:53:38 +08:00
40da17b414 调整挂单统计,修改删除区域报错 2025-09-16 11:49:02 +08:00
f1e3f555e6 修改资料库结构 2025-09-15 16:23:38 +08:00
72db7ca928 挂单模块URL 2025-09-15 14:24:31 +08:00
ebad44be71 挂单查询 2025-09-15 14:16:12 +08:00
68f2733530 Update data/SQL/17168_db_schema.sql 2025-09-15 02:34:28 +00:00
d752a01cc7 更新SCHEMA SQL,
疏文下載改相對路徑
2025-09-15 01:22:45 +08:00
03e366c34a Merge branch 'guadan0905' 2025-09-15 00:49:38 +08:00
d4748e415a update 2025-09-13 10:30:40 +08:00
42e46b4d35 Merge branch 'yiming-250910' 2025-09-12 15:53:48 +08:00
69b9b1bbd1 资料库数据文件上传 2025-09-12 15:45:11 +08:00
4419dfff64 把资料库产色的几个文件提交,汇出挂单功能的数据,状态数据要有才能运行 2025-09-12 13:48:04 +08:00
636b22849c Merge remote-tracking branch 'origin/guadan0905' 2025-09-11 00:48:50 +08:00
0b0ddc82bd 上传挂单资料库 2025-09-09 16:28:31 +08:00
ded24a8446 修改挂单功能 2025-09-09 16:25:28 +08:00
79 changed files with 7920 additions and 996 deletions

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@
packages
obj/
*.user
*.log
*.log
**website.publishproj

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,11 @@
資料字典:
USE [17168erp_t]
GO
INSERT [dbo].[AncestralTabletStatus] ([StatusCode], [StatusName], [StatusType]) VALUES (N'available', N'可用', N'Position')
INSERT [dbo].[AncestralTabletStatus] ([StatusCode], [StatusName], [StatusType]) VALUES (N'maintenance', N'維護中', N'Position')
INSERT [dbo].[AncestralTabletStatus] ([StatusCode], [StatusName], [StatusType]) VALUES (N'used', N'已使用', N'Position')
GO
代碼中會用到上面表中的狀態,所以必須在該表中插入上面的數據
AncestralTabletStatus_script.sql文件中的表是神祖牌位功能模組需要用到的表再運行之前需要先執行該文件
執行AncestralTabletStatus_script.sql後要把在item表中新增的URL權限添加到相應的管理員帳號

Binary file not shown.

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
/// <summary>
/// GuaDanStatusCode 的摘要描述
/// </summary>
public static class GuaDanStatusCode
{
public static class Bed
{
public const string Empty = "101"; // 空床:床位可分配
public const string Occupied = "102"; // 占用中:床位已有人使用
public const string Repair = "103"; // 維修停用:床位維修或不可使用
}
public static class Room
{
public const string Empty = "301"; // 空房:房間所有床位皆為空
public const string Partly = "302"; // 部分入住:房間有人,但仍有空床
public const string Full = "303"; // 已滿:房間所有床位皆已入住
public const string Repair = "304"; // 維修停用:房間維修或不可使用
}
public static class Guadan
{
public const string Booked = "401"; // 預訂成功:默認就是預訂成功狀態
public const string CheckedIn = "402"; // 已入住:已辦理入住
public const string CheckedOut = "403"; // 已退房
public const string Cancelled = "404"; // 已取消:取消後的狀態,不是取消的動作
}
}

View File

@@ -1,9 +1,9 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
// 這個程式碼是由範本產生。
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// 對這個檔案進行手動變更可能導致您的應用程式產生未預期的行為。
// 如果重新產生程式碼,將會覆寫對這個檔案的手動變更。
// </auto-generated>
//------------------------------------------------------------------------------
@@ -74,7 +74,6 @@ namespace Model
public virtual DbSet<stock_reason> stock_reason { get; set; }
public virtual DbSet<supplier> suppliers { get; set; }
public virtual DbSet<supplier_kind> supplier_kind { get; set; }
public virtual DbSet<sysdiagram> sysdiagrams { get; set; }
public virtual DbSet<act_bom> act_bom { get; set; }
public virtual DbSet<family_members> family_members { get; set; }
public virtual DbSet<PostCity> PostCitiy { get; set; }
@@ -90,6 +89,11 @@ namespace Model
public virtual DbSet<RegionRoomBedStatus> RegionRoomBedStatus { get; set; }
public virtual DbSet<RegionType> RegionType { get; set; }
public virtual DbSet<Room> Room { get; set; }
public virtual DbSet<AncestralTabletArea> AncestralTabletArea { get; set; }
public virtual DbSet<AncestralTabletPosition> AncestralTabletPosition { get; set; }
public virtual DbSet<AncestralTabletPositionRecord> AncestralTabletPositionRecord { get; set; }
public virtual DbSet<AncestralTabletRegistrant> AncestralTabletRegistrant { get; set; }
public virtual DbSet<AncestralTabletStatus> AncestralTabletStatus { get; set; }
public virtual int pager_eztrust(Nullable<int> startRowIndex, Nullable<int> pageSize, string tableName, string columnName, string sqlWhere, string orderBy, ObjectParameter rowCount)
{

View File

@@ -1,10 +1,10 @@
// T4 code generation is enabled for model 'D:\dev\ez\17168erp\git_17888\web\App_Code\Model\Model.edmx'.
// To enable legacy code generation, change the value of the 'Code Generation Strategy' designer
// property to 'Legacy ObjectContext'. This property is available in the Properties Window when the model
// is open in the designer.
// 已啟用模型 'D:\17168erp_new_git\17168ERP\web\App_Code\Model\Model.edmx' 的 T4 程式碼產生。
// 若要啟用舊版程式碼產生,請將 [程式碼產生策略] 設計工具屬性的值
//變更為 [舊版 ObjectContext]。當模型在設計工具中開啟時,這個屬性便可
//以在 [屬性] 視窗中使用。
// If no context and entity classes have been generated, it may be because you created an empty model but
// have not yet chosen which version of Entity Framework to use. To generate a context class and entity
// classes for your model, open the model in the designer, right-click on the designer surface, and
// select 'Update Model from Database...', 'Generate Database from Model...', or 'Add Code Generation
// Item...'.
// 如果尚未產生任何內容和實體類型,可能是因為您建立了空的模型,但
//尚未選擇要使用的 Entity Framework 版本。若要為您的模型產生內容類別和
//實體類型,請在設計工具中開啟模型,以滑鼠右鍵按一下設計工具介面並
//選取 [從資料庫更新模型]、[由模型產生資料庫] 或 [加入程式碼產生
//項目]。

View File

@@ -1,9 +1,9 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
// 這個程式碼是由範本產生。
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// 對這個檔案進行手動變更可能導致您的應用程式產生未預期的行為。
// 如果重新產生程式碼,將會覆寫對這個檔案的手動變更。
// </auto-generated>
//------------------------------------------------------------------------------
@@ -441,7 +441,7 @@ namespace Model
{
this.members = new HashSet<member>();
this.news = new HashSet<news>();
this.GuaDanOrders = new HashSet<GuaDanOrder>();
this.GuaDanOrder = new HashSet<GuaDanOrder>();
}
public int num { get; set; }
@@ -477,7 +477,7 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<news> news { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrder> GuaDanOrders { get; set; }
public virtual ICollection<GuaDanOrder> GuaDanOrder { get; set; }
}
}
namespace Model
@@ -523,6 +523,139 @@ namespace Model
using System;
using System.Collections.Generic;
public partial class AncestralTabletArea
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public AncestralTabletArea()
{
this.AncestralTabletArea1 = new HashSet<AncestralTabletArea>();
this.AncestralTabletPosition = new HashSet<AncestralTabletPosition>();
}
public int AreaId { get; set; }
public string AreaName { get; set; }
public string AreaCode { get; set; }
public Nullable<int> ParentAreaId { get; set; }
public string AreaType { get; set; }
public Nullable<int> Price { get; set; }
public Nullable<int> SortOrder { get; set; }
public bool IsDisabled { get; set; }
public string Description { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AncestralTabletArea> AncestralTabletArea1 { get; set; }
public virtual AncestralTabletArea AncestralTabletArea2 { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AncestralTabletPosition> AncestralTabletPosition { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class AncestralTabletPosition
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public AncestralTabletPosition()
{
this.AncestralTabletRegistrant = new HashSet<AncestralTabletRegistrant>();
}
public int PositionId { get; set; }
public int AreaId { get; set; }
public string PositionCode { get; set; }
public string PositionName { get; set; }
public Nullable<int> Price { get; set; }
public string StatusCode { get; set; }
public string Description { get; set; }
public Nullable<int> RowNo { get; set; }
public Nullable<int> ColumnNo { get; set; }
public virtual AncestralTabletArea AncestralTabletArea { get; set; }
public virtual AncestralTabletStatus AncestralTabletStatus { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AncestralTabletRegistrant> AncestralTabletRegistrant { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class AncestralTabletPositionRecord
{
public int RecordId { get; set; }
public string RegistrantCode { get; set; }
public string NPTitle { get; set; }
public System.DateTime NPStandDate { get; set; }
public string NPYangShang { get; set; }
public string WPContent { get; set; }
public System.DateTime CreatedAt { get; set; }
public Nullable<System.DateTime> UpdatedAt { get; set; }
public virtual AncestralTabletRegistrant AncestralTabletRegistrant { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class AncestralTabletRegistrant
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public AncestralTabletRegistrant()
{
this.AncestralTabletPositionRecord = new HashSet<AncestralTabletPositionRecord>();
}
public string RegistrantCode { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public System.DateTime RegisterDate { get; set; }
public Nullable<int> Price { get; set; }
public Nullable<int> PositionId { get; set; }
public System.DateTime StartDate { get; set; }
public Nullable<System.DateTime> EndDate { get; set; }
public bool IsLongTerm { get; set; }
public bool IsActive { get; set; }
public System.DateTime CreatedAt { get; set; }
public Nullable<System.DateTime> UpdatedAt { get; set; }
public bool IsEnd { get; set; }
public virtual AncestralTabletPosition AncestralTabletPosition { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AncestralTabletPositionRecord> AncestralTabletPositionRecord { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class AncestralTabletStatus
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public AncestralTabletStatus()
{
this.AncestralTabletPosition = new HashSet<AncestralTabletPosition>();
}
public string StatusCode { get; set; }
public string StatusName { get; set; }
public string StatusType { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<AncestralTabletPosition> AncestralTabletPosition { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class appellation
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
@@ -768,8 +901,8 @@ namespace Model
this.family_members = new HashSet<family_members>();
this.transfer_register = new HashSet<transfer_register>();
this.transfer_register1 = new HashSet<transfer_register>();
this.GuaDanOrders = new HashSet<GuaDanOrder>();
this.GuaDanOrderGuests = new HashSet<GuaDanOrderGuest>();
this.GuaDanOrder = new HashSet<GuaDanOrder>();
this.GuaDanOrderGuest = new HashSet<GuaDanOrderGuest>();
}
public int num { get; set; }
@@ -830,9 +963,9 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<transfer_register> transfer_register1 { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrder> GuaDanOrders { get; set; }
public virtual ICollection<GuaDanOrder> GuaDanOrder { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuests { get; set; }
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuest { get; set; }
}
}
namespace Model
@@ -857,6 +990,12 @@ namespace Model
public partial class GuaDanOrder
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public GuaDanOrder()
{
this.GuaDanOrderGuest = new HashSet<GuaDanOrderGuest>();
}
public Nullable<System.DateTime> StartDate { get; set; }
public Nullable<System.DateTime> EndDate { get; set; }
public Nullable<int> CreateUser { get; set; }
@@ -869,9 +1008,13 @@ namespace Model
public string BookerPhone { get; set; }
public bool IsDeleted { get; set; }
public System.Guid Uuid { get; set; }
public Nullable<int> ActivityNum { get; set; }
public bool IsCancel { get; set; }
public virtual admin admin { get; set; }
public virtual follower follower { get; set; }
public virtual follower followers { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuest { get; set; }
}
}
namespace Model
@@ -881,6 +1024,12 @@ namespace Model
public partial class GuaDanOrderGuest
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public GuaDanOrderGuest()
{
this.RegionAndRoomAndBedSchedule = new HashSet<RegionAndRoomAndBedSchedule>();
}
public string GuaDanOrderNo { get; set; }
public Nullable<int> FollowerNum { get; set; }
public bool IsDeleted { get; set; }
@@ -889,12 +1038,16 @@ namespace Model
public Nullable<System.Guid> BedUuid { get; set; }
public Nullable<System.DateTime> CheckInAt { get; set; }
public Nullable<System.DateTime> CheckOutAt { get; set; }
public Nullable<System.Guid> statusUuid { get; set; }
public string StatusCode { get; set; }
public Nullable<System.Guid> OrderUuid { get; set; }
public virtual follower follower { get; set; }
public virtual follower followers { get; set; }
public virtual RegionRoomBed RegionRoomBed { get; set; }
public virtual Room Room { get; set; }
public virtual RegionRoomBedStatus RegionRoomBedStatus { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<RegionAndRoomAndBedSchedule> RegionAndRoomAndBedSchedule { get; set; }
public virtual GuaDanOrder GuaDanOrder { get; set; }
}
}
namespace Model
@@ -1393,7 +1546,10 @@ namespace Model
public Nullable<System.Guid> TargetUuid { get; set; }
public string GuaDanOrderNo { get; set; }
public Nullable<System.DateTime> ScheduleDate { get; set; }
public bool IsActive { get; set; }
public bool IsCancel { get; set; }
public Nullable<System.Guid> GuaDanOrderGuestUuid { get; set; }
public virtual GuaDanOrderGuest GuaDanOrderGuest { get; set; }
}
}
namespace Model
@@ -1406,7 +1562,7 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public RegionRoomBed()
{
this.GuaDanOrderGuests = new HashSet<GuaDanOrderGuest>();
this.GuaDanOrderGuest = new HashSet<GuaDanOrderGuest>();
}
public string Name { get; set; }
@@ -1415,12 +1571,12 @@ namespace Model
public bool IsDeleted { get; set; }
public System.Guid Uuid { get; set; }
public System.Guid RoomUuid { get; set; }
public Nullable<System.Guid> StatusUuid { get; set; }
public string StatusCode { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuests { get; set; }
public virtual RegionRoomBedStatus RegionRoomBedStatus { get; set; }
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuest { get; set; }
public virtual Room Room { get; set; }
public virtual RegionRoomBedStatus RegionRoomBedStatus { get; set; }
}
}
namespace Model
@@ -1433,8 +1589,8 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public RegionRoomBedStatus()
{
this.GuaDanOrderGuests = new HashSet<GuaDanOrderGuest>();
this.RegionRoomBeds = new HashSet<RegionRoomBed>();
this.GuaDanOrderGuest = new HashSet<GuaDanOrderGuest>();
this.RegionRoomBed = new HashSet<RegionRoomBed>();
}
public string Code { get; set; }
@@ -1442,12 +1598,11 @@ namespace Model
public string Description { get; set; }
public Nullable<int> Category { get; set; }
public bool IsDeleted { get; set; }
public System.Guid Uuid { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuests { get; set; }
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuest { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<RegionRoomBed> RegionRoomBeds { get; set; }
public virtual ICollection<RegionRoomBed> RegionRoomBed { get; set; }
}
}
namespace Model
@@ -1460,7 +1615,7 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public RegionType()
{
this.Regions = new HashSet<Region>();
this.Region = new HashSet<Region>();
}
public string Code { get; set; }
@@ -1471,7 +1626,7 @@ namespace Model
public System.Guid Uuid { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Region> Regions { get; set; }
public virtual ICollection<Region> Region { get; set; }
}
}
namespace Model
@@ -1484,7 +1639,7 @@ namespace Model
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Room()
{
this.GuaDanOrderGuests = new HashSet<GuaDanOrderGuest>();
this.GuaDanOrderGuest = new HashSet<GuaDanOrderGuest>();
this.RegionRoomBed = new HashSet<RegionRoomBed>();
}
@@ -1499,7 +1654,7 @@ namespace Model
public System.Guid RegionUuid { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuests { get; set; }
public virtual ICollection<GuaDanOrderGuest> GuaDanOrderGuest { get; set; }
public virtual Region Region { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<RegionRoomBed> RegionRoomBed { get; set; }
@@ -1685,20 +1840,6 @@ namespace Model
using System;
using System.Collections.Generic;
public partial class sysdiagram
{
public string name { get; set; }
public int principal_id { get; set; }
public int diagram_id { get; set; }
public Nullable<int> version { get; set; }
public byte[] definition { get; set; }
}
}
namespace Model
{
using System;
using System.Collections.Generic;
public partial class transfer_register
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,6 @@
<EntityTypeShape EntityType="Model.stock_reason" Width="1.5" PointX="14.25" PointY="33.625" IsExpanded="true" />
<EntityTypeShape EntityType="Model.supplier" Width="1.5" PointX="14.25" PointY="36.5" IsExpanded="true" />
<EntityTypeShape EntityType="Model.supplier_kind" Width="1.5" PointX="12" PointY="37.5" IsExpanded="true" />
<EntityTypeShape EntityType="Model.sysdiagram" Width="1.5" PointX="0.75" PointY="6" IsExpanded="true" />
<AssociationConnector Association="Model.FK_accounting_accounting_kind" ManuallyRouted="false" />
<AssociationConnector Association="Model.FK_accounting_accounting_kind2" ManuallyRouted="false" />
<AssociationConnector Association="Model.FK_accounting_activity" ManuallyRouted="false" />
@@ -131,26 +130,38 @@
<AssociationConnector Association="Model.FK_transfer_register_actItem" />
<AssociationConnector Association="Model.FK_transfer_register_pro_order_detail" />
<AssociationConnector Association="Model.FK_pro_order_record_transfer_register" />
<EntityTypeShape EntityType="Model.GuaDanOrder" Width="1.5" PointX="13.375" PointY="21.25" />
<EntityTypeShape EntityType="Model.GuaDanOrderGuest" Width="2.125" PointX="30.375" PointY="13.75" />
<EntityTypeShape EntityType="Model.GuadanTimeSetting" Width="1.5" PointX="21.375" PointY="9.125" />
<EntityTypeShape EntityType="Model.Region" Width="1.5" PointX="23.625" PointY="3.875" />
<EntityTypeShape EntityType="Model.RegionAndRoomAndBedSchedule" Width="1.5" PointX="23.375" PointY="9.125" />
<EntityTypeShape EntityType="Model.RegionRoomBed" Width="2.125" PointX="28.125" PointY="4.625" />
<EntityTypeShape EntityType="Model.RegionRoomBedStatus" Width="1.5" PointX="25.875" PointY="0.75" />
<EntityTypeShape EntityType="Model.RegionType" Width="1.5" PointX="21.375" PointY="4.875" />
<EntityTypeShape EntityType="Model.Room" Width="1.5" PointX="25.875" PointY="4.375" />
<EntityTypeShape EntityType="Model.GuaDanOrder" Width="1.5" PointX="13.25" PointY="20.25" />
<EntityTypeShape EntityType="Model.GuaDanOrderGuest" Width="1.5" PointX="25.875" PointY="12.5" />
<EntityTypeShape EntityType="Model.GuadanTimeSetting" Width="1.5" PointX="21.375" PointY="7.75" />
<EntityTypeShape EntityType="Model.Region" Width="1.5" PointX="19.125" PointY="30.75" />
<EntityTypeShape EntityType="Model.RegionAndRoomAndBedSchedule" Width="1.5" PointX="28.125" PointY="12.625" />
<EntityTypeShape EntityType="Model.RegionRoomBed" Width="1.5" PointX="23.625" PointY="3.375" />
<EntityTypeShape EntityType="Model.RegionRoomBedStatus" Width="1.5" PointX="21.375" PointY="3.625" />
<EntityTypeShape EntityType="Model.RegionType" Width="1.5" PointX="16.875" PointY="31.625" />
<EntityTypeShape EntityType="Model.Room" Width="1.5" PointX="21.375" PointY="31.125" />
<AssociationConnector Association="Model.FK_GuaDanOrder_Admin_CreateUser" />
<AssociationConnector Association="Model.FK_GuaDanOrder_Followers" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_FOLLOWERS" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_BedUuid" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_RoomUuid" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_Status" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_StatusCode" />
<AssociationConnector Association="Model.FK_Schedule_GuaDanOrderGuest" />
<AssociationConnector Association="Model.FK_Region_ParentUuid" />
<AssociationConnector Association="Model.FK_Region_RegionTypeUuid" />
<AssociationConnector Association="Model.FK_Room_Region" />
<AssociationConnector Association="Model.FK_RegionRoomBed_RegionRoomBedStatus" />
<AssociationConnector Association="Model.FK_RegionRoomBed_RoomUuid" />
<AssociationConnector Association="Model.FK_RegionRoomBed_StatusCode" />
<EntityTypeShape EntityType="Model.AncestralTabletArea" Width="1.5" PointX="25.375" PointY="22.375" />
<EntityTypeShape EntityType="Model.AncestralTabletPosition" Width="1.5" PointX="27.625" PointY="18.375" />
<EntityTypeShape EntityType="Model.AncestralTabletPositionRecord" Width="1.5" PointX="32.125" PointY="18.625" />
<EntityTypeShape EntityType="Model.AncestralTabletRegistrant" Width="1.5" PointX="29.875" PointY="18" />
<EntityTypeShape EntityType="Model.AncestralTabletStatus" Width="1.5" PointX="25.375" PointY="19.125" />
<AssociationConnector Association="Model.FK_AncestralTabletArea_Parent" />
<AssociationConnector Association="Model.FK_Position_Area" />
<AssociationConnector Association="Model.FK_Position_Status" />
<AssociationConnector Association="Model.FK_Registrant_Position" />
<AssociationConnector Association="Model.FK__Ancestral__Regis__5A1A5A11" />
<AssociationConnector Association="Model.FK_GuaDanOrderGuest_Order" />
</Diagram>
</edmx:Diagrams>
</edmx:Designer>

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
/// <summary>
/// GuaDanOrderGuest 的摘要描述
/// </summary>
namespace Model
{
public partial class GuaDanOrderGuest
{
// 状态常量定义
public const string STATUS_BOOKED = "401"; // 预订成功
public const string STATUS_CHECKED_IN = "402"; // 已入住
public const string STATUS_CHECKED_OUT = "403"; // 已退房
public const string STATUS_CANCELLED = "404"; // 已取消
public static bool IsStatusTransitionValid(ezEntities db, string targetStatus, Guid guestId)
{
// 获取当前客人对象
GuaDanOrderGuest currentGuest = db.GuaDanOrderGuest.Find(guestId);
if (currentGuest == null)
{
// 如果没有客人对象,只能创建新的预订
return targetStatus == STATUS_BOOKED;
}
// 安全获取当前状态处理null情况
string currentStatus = currentGuest.RegionRoomBedStatus?.Code;
// 如果当前状态为空,只能进入预订成功状态
if (string.IsNullOrEmpty(currentStatus))
{
return targetStatus == STATUS_BOOKED;
}
// 定义有效的状态转换规则
var validTransitions = new Dictionary<string, List<string>>
{
{ STATUS_BOOKED, new List<string> { STATUS_CHECKED_IN, STATUS_CANCELLED } },
{ STATUS_CHECKED_IN, new List<string> { STATUS_CHECKED_OUT } },
{ STATUS_CHECKED_OUT, new List<string> { } }, // 终态,不能再转换
{ STATUS_CANCELLED, new List<string> { } } // 终态,不能再转换
};
// 检查转换是否有效
if (validTransitions.ContainsKey(currentStatus))
{
return validTransitions[currentStatus].Contains(targetStatus);
}
return false; // 未知的当前状态
}
}
}

View File

@@ -58,7 +58,7 @@ namespace Model
// 找出在日期範圍內被占用的床位 Uuid包括長期占用 ScheduleDate = null
var busyBedUuidsQuery = db.RegionAndRoomAndBedSchedule
.Where(s => s.IsDeleted == false && s.IsActive
.Where(s => s.IsDeleted == false && !s.IsCancel
&& (s.ScheduleDate == null
|| (end.HasValue
&& s.ScheduleDate >= start
@@ -79,25 +79,28 @@ namespace Model
public static async Task<bool> IsBedAvailableAsync(ezEntities db, Guid targetUuid, DateTime start, DateTime? end)
{
// 如果 end 為 null表示長期占用直接判斷是否已有長期占用
//不包含结束时间那一天
if (end == null)
{
var hasLongTerm = await db.RegionAndRoomAndBedSchedule
.AnyAsync(s => s.IsDeleted == false
&& s.IsActive
&& !s.IsCancel
&& s.TargetUuid == targetUuid
&& s.ScheduleDate == null);
return !hasLongTerm;
}
// 短期占用,查詢每日排程中有無衝突
var totalDays = (end.Value.Date - start.Date).Days + 1;
var totalDays = (end.Value.Date - start.Date).Days;
for (int i = 0; i < totalDays; i++)
{
var date = start.Date.AddDays(i);
var conflict = await db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderGuest.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
.Where(s => s.GuaDanOrderGuest.StatusCode != GuaDanOrderGuest.STATUS_CHECKED_OUT)
.AnyAsync(s => s.IsDeleted == false
&& s.IsActive
&& !s.IsCancel
&& s.TargetUuid == targetUuid
&& s.ScheduleDate == date);
@@ -114,7 +117,10 @@ namespace Model
// 找出所有在日期範圍內被占用的床位
var busyBedUuids = await db.RegionAndRoomAndBedSchedule
.Where(s => s.IsDeleted == false && s.IsActive
.Where(s => s.GuaDanOrderGuest.StatusCode != "403")
.Where(s => s.GuaDanOrderGuest.StatusCode != "404")
.Where(a => a.IsCancel == false)
.Where(s => s.IsDeleted == false
&& (s.ScheduleDate == null // 長期占用
|| (s.ScheduleDate >= start && s.ScheduleDate <= end)))
.Select(s => s.TargetUuid)

View File

@@ -11,6 +11,9 @@ namespace Model
{
public partial class RegionRoomBed
{
public const string STATUS_BED_FREE = "101"; // 空閒,可使用
public const string STATUS_BED_OCCUPIED = "102"; // 已佔用
public const string STATUS_BED_MAINTENANCE = "103"; // 維護中,不可使用
public bool IsAvailable()
{
//判断床位是否可用:自身是否启用
@@ -34,7 +37,9 @@ namespace Model
// 如果資料庫 ScheduleDate 是 date 型別,本身沒有時間部分,可以直接比較
var conflict = _db.RegionAndRoomAndBedSchedule.Any(s =>
s.TargetUuid == this.Uuid &&
s.IsActive &&
s.GuaDanOrderGuest.StatusCode != "403" &&
s.GuaDanOrderGuest.StatusCode != "404" &&
!s.IsCancel &&
!s.IsDeleted &&
(
s.ScheduleDate == null || // 長期占用
@@ -46,5 +51,6 @@ namespace Model
return !conflict;
}
}
}

View File

@@ -19,5 +19,6 @@ public class GuaDanOrderView
public string bookerName { get; set; }
public string bookerPhone { get; set; }
public int? bookerFollowerNum { get; set; }
public int? activityNum { get; set; }
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace Model
{
public static class StatusTransitionManager
{
private static readonly Dictionary<string, List<string>> transitions =
new Dictionary<string, List<string>>
{
// 掛單狀態
{ "401", new List<string> { "402", "404" } },
{ "402", new List<string> { "403" } },
{ "403", new List<string>() },
{ "404", new List<string>() },
// 床位狀態
{ "101", new List<string> {"101", "102","103"} },
{ "102", new List<string> { "101" } },
{ "103", new List<string> { "101" } },
};
public static bool CanTransition(string currentCode, string targetCode)
{
if (string.IsNullOrEmpty(currentCode))
{
return targetCode == "401" || targetCode == "402" || targetCode == "101";
}
if(string.IsNullOrEmpty(targetCode))
{ return false; }
return transitions.ContainsKey(currentCode) &&
transitions[currentCode].Contains(targetCode);
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
/// <summary>
/// ActivityStatisticsController 的摘要描述
/// </summary>
public class ActivityStatisticsController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[Route("api/activity/statistics/summary")]
public IHttpActionResult GetStatisticsSummary([FromUri] int? activity_num =null)
{
if (activity_num == null)
{
return BadRequest("活动Id不能为空");
}
var now = DateTime.Now;
var pre_order_query = _db.pro_order.Where(a => a.activity_num == activity_num);
var totalApplicants = pre_order_query.Count();
var maleApplicants = pre_order_query.Where(a => a.follower.sex == "男眾").Count();
var pro_order_detail_query = _db.pro_order_detail
.Where(d => d.pro_order.activity_num == activity_num);
var result = new
{
reportDate = now.ToString("yyyy/MM/dd HH:mm:ss"),
totalApplicants = totalApplicants, //报名总人数
maleApplicants = maleApplicants, //男
femaleApplicants = totalApplicants - maleApplicants,//女
donation = new
{
total = 158000,//总功德金
received = 150000,//已收功德金
unreceived = 8000//未收功德金
},
items = new[]//功德项目,这个的功德项目要根据活动预设的功德项目来做统计,就是这个活动有那些可报名的功德项目
{
new { name = "總功德主", count = "10人" },
new { name = "利益主", count = "15人" },
new { name = "個人大牌", count = "50人" },
new { name = "供僧", count = "45人" }
},
plaques = new[]//牌位
{
new { name = "總牌位數", count = pro_order_detail_query.Count() },
new { name = "消災-大牌位", count = 101 },
new { name = "超冤-大牌位", count =10 },
new { name = "消災-個人大牌", count =10 },
new { name = "超冤-個人大牌", count = 10 },
new { name = "超薦-個人大牌", count = 10 },
new { name = "消災-個人中牌", count = 10 },
new { name = "超冤-個人中牌", count = 10 },
new { name = "超薦-個人中牌", count = 10 },
new { name = "消災-隨喜牌位", count = 10 },
new { name = "超冤-隨喜牌位", count = 10 },
new { name = "超薦-隨喜牌位", count = 10 },
new { name = "消災-常年牌位", count = 10 },
new { name = "超冤-常年牌位", count = 10 },
new { name = "超薦-常年牌位", count = 10 },
new { name = "消災-急立牌位", count = 10 },
new { name = "超薦-急立牌位", count = 10 },
new { name = "超冤-急立牌位", count = 101 },
}
};
return Ok(result);
}
}

View File

@@ -0,0 +1,180 @@
using Model;
using PagedList;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using static AncestralTabletPositionController;
using static regionController;
/// <summary>
/// AncestralTabletController 的摘要描述
/// </summary>
public class AncestralTabletAreaController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public class AreaViewModel
{
public int? AreaId { get; set; }
public string AreaName { get; set; }
[Required(ErrorMessage = "區域編號為必填")]
public string AreaCode { get; set; }
public int? ParentAreaId { get; set; }
public string AreaType { get; set; }
public int? Price { get; set; }
public int? SortOrder { get; set; }
public bool IsDisabled { get; set; } = false;
public string Description { get; set; }
}
public class RegionDto
{
public int AreaId { get; set; }
public string AreaName { get; set; }
public string AreaCode { get; set; }
public int? SortOrder { get; set; }
public int? ParentAreaId { get; set; }
public string AreaType { get; set; }
public bool IsDisabled { get; set; }
public string Description { get; set; }
public int? Price { set; get; }
public List<RegionDto> Children { get; set; } = new List<RegionDto>();
}
[HttpGet]
[Route("api/ancestraltablet/area/getlist")]
public IHttpActionResult GetList()
{
var allArea = _db.AncestralTabletArea.ToList();
var rootRegions = allArea
.Where(r => r.ParentAreaId == null)
.OrderBy(r => r.SortOrder)
.ToList();
var tree = rootRegions
.Select(r => BuildRegionDto(r, allArea))
.ToList();
return Ok(tree);
}
[HttpGet]
[Route("api/ancestraltablet/area/getereawithposition")]
public IHttpActionResult GetEreaWithPosition()
{
//获取有神位位置的区域
var allArea = _db.AncestralTabletArea
.Where(a => a.AncestralTabletPosition.Count()>0)
.Select(a => new
{
a.AreaId,
a.AreaName,
})
.ToList();
return Ok(allArea);
}
private RegionDto BuildRegionDto(AncestralTabletArea area, List<AncestralTabletArea> allArea)
{
return new RegionDto
{
AreaId = area.AreaId,
AreaName = area.AreaName,
AreaCode = area.AreaCode,
SortOrder = area.SortOrder,
ParentAreaId = area.ParentAreaId,
AreaType = area.AreaType,
IsDisabled = area.IsDisabled,
Description = area.Description,
Price = area.Price,
Children = allArea
.Where(r => r.ParentAreaId == area.AreaId)
.OrderBy(r => r.SortOrder)
.Select(child => BuildRegionDto(child, allArea))
.ToList(),
};
}
[HttpPost]
[Route("api/ancestraltablet/area/create")]
public async Task<IHttpActionResult> CreateArea([FromBody] AreaViewModel tabletArea)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
try
{
var area = new AncestralTabletArea
{
AreaName = tabletArea.AreaName,
AreaCode = tabletArea.AreaCode,
ParentAreaId = tabletArea.ParentAreaId,
AreaType = tabletArea.AreaType,
Price = tabletArea.Price,
SortOrder = tabletArea.SortOrder,
IsDisabled = tabletArea.IsDisabled,
Description = tabletArea.Description
};
_db.AncestralTabletArea.Add(area);
await _db.SaveChangesAsync();
tabletArea.AreaId = area.AreaId;
return Ok(new { message = "區域建立成功", area = tabletArea });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpPost]
[Route("api/ancestraltablet/area/edit")]
public async Task<IHttpActionResult> EditArea([FromBody] AreaViewModel tabletArea)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
try
{
var oldArea = _db.AncestralTabletArea.Find(tabletArea.AreaId);
if (oldArea == null)
{
return NotFound();
}
oldArea.AreaName = tabletArea.AreaName;
oldArea.AreaCode = tabletArea.AreaCode;
oldArea.ParentAreaId = tabletArea.ParentAreaId;
oldArea.Price = tabletArea.Price;
oldArea.SortOrder = tabletArea.SortOrder;
oldArea.Description = tabletArea.Description;
oldArea.IsDisabled = tabletArea.IsDisabled;
await _db.SaveChangesAsync();
return Ok(new { message = "區域修改成功", area = tabletArea });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpGet]
[Route("api/ancestraltablet/area/position/getlist")]
public IHttpActionResult GetPositionList([FromUri] int areaId)
{
var positions = _db.AncestralTabletPosition
.Where(p => p.AreaId == areaId)
.Select(p => new AncestralTabletPositionDto
{
PositionId = p.PositionId,
AreaId = p.AreaId,
PositionCode = p.PositionCode,
PositionName = p.PositionName,
Price = p.Price,
StatusCode = p.StatusCode,
Description = p.Description,
RowNo = p.RowNo,
ColumnNo = p.ColumnNo
})
.ToList();
return Ok(positions);
}
}

View File

@@ -0,0 +1,184 @@
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Http;
/// <summary>
/// AncestralTabletPositionController 的摘要描述
/// </summary>
public class AncestralTabletPositionController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[Route("api/ancestraltablet/position/getlist")]
public IHttpActionResult GetList([FromUri] int areaId)
{
var positions = _db.AncestralTabletPosition
.Where(p => p.AreaId == areaId)
.Select(p => new
{
PositionId = p.PositionId,
AreaId = p.AreaId,
PositionCode = p.PositionCode,
PositionName = p.PositionName,
Price = p.Price,
StatusCode = p.StatusCode,
Description = p.Description,
RowNo = p.RowNo,
ColumnNo = p.ColumnNo,
AncestralTabletRegistrant = _db.AncestralTabletRegistrant
.Where(r => r.PositionId == p.PositionId && r.IsActive == true)
.Select(r => new
{
r.RegistrantCode,
r.Name,
r.Phone,
r.Address,
r.RegisterDate,
r.Price,
r.StartDate,
r.EndDate,
r.IsLongTerm,
r.IsActive,
// 嵌套查询牌位记录PositionRecord
TabletRecord = r.AncestralTabletPositionRecord
.Select(pr => new
{
pr.RecordId,
pr.RegistrantCode,
pr.NPTitle,
pr.NPStandDate,
pr.NPYangShang,
pr.WPContent
})
.FirstOrDefault()
})
.FirstOrDefault()
})
.ToList();
return Ok(positions);
}
[HttpGet]
[Route("api/ancestraltablet/position/shortlist")]
public IHttpActionResult GetListWithShort([FromUri] int areaId)
{
//获取位置列表,简单信息
var positions = _db.AncestralTabletPosition
.Where(p => p.AreaId == areaId)
.Select(p => new
{
PositionId = p.PositionId,
AreaId = p.AreaId,
PositionCode = p.PositionCode,
PositionName = p.PositionName,
Price = p.Price,
StatusCode = p.StatusCode,
Description = p.Description,
RowNo = p.RowNo,
ColumnNo = p.ColumnNo,
isCanUse = p.StatusCode == "available" ? true : false,
})
.ToList();
return Ok(positions);
}
[HttpPost]
[Route("api/ancestraltablet/position/batchcreate")]
public IHttpActionResult BatchCreatePosition([FromBody] List<AncestralTabletPositionDto> positions)
{
if (positions == null || positions.Count == 0)
return BadRequest("未接收到任何位置数据");
try
{
foreach (var dto in positions)
{
var entity = new AncestralTabletPosition
{
AreaId = dto.AreaId,
PositionCode = dto.PositionCode,
PositionName = dto.PositionName,
Price = dto.Price,
StatusCode = dto.StatusCode,
Description = dto.Description,
RowNo = dto.RowNo,
ColumnNo = dto.ColumnNo
};
_db.AncestralTabletPosition.Add(entity);
}
_db.SaveChanges();
return Ok(new { message = "批量新增成功", count = positions.Count });
}
catch (Exception ex)
{
string message = ex.InnerException?.InnerException?.Message ?? ex.Message;
return Content(HttpStatusCode.InternalServerError, new
{
message = "批量新增失败",
exceptionMessage = message
});
}
}
[HttpPost]
[Route("api/ancestraltablet/position/edit")]
public IHttpActionResult EditPosition([FromBody] AncestralTabletPositionDto pos)
{
var oldPos = _db.AncestralTabletPosition
.FirstOrDefault(p => p.AreaId == pos.AreaId && p.PositionCode == pos.PositionCode);
if(oldPos == null) return NotFound();
try
{
oldPos.PositionName = pos.PositionName;
oldPos.Price = pos.Price;
oldPos.StatusCode = pos.StatusCode;
// 保存到数据库
_db.SaveChanges();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
return Ok(new { message="更新成功", code=200});
}
[HttpDelete]
[Route("api/ancestraltablet/position/delete/{positionId}")]
public IHttpActionResult DeletePosition(int positionId)
{
var pos = _db.AncestralTabletPosition.FirstOrDefault(p => p.PositionId == positionId);
if (pos == null)
{
return NotFound();
}
_db.AncestralTabletPosition.Remove(pos);
_db.SaveChanges();
return Ok("删除成功");
}
public class AncestralTabletPositionDto
{
public int PositionId { get; set; }
public int AreaId { get; set; }
public string PositionCode { get; set; }
public string PositionName { get; set; }
public int? Price { get; set; }
public string StatusCode { get; set; }
public string Description { get; set; }
public int? RowNo { get; set; }
public int? ColumnNo { get; set; }
}
}

View File

@@ -0,0 +1,361 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.UI.WebControls;
/// <summary>
/// AncestralTabletRecordController 的摘要描述
/// </summary>
public class AncestralTabletRecordController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[Route("api/ancestraltablet/registrant/getlist")]
public IHttpActionResult GetRegistrantList()
{
//获取登记人列表
var data = _db.AncestralTabletRegistrant.Select(x => new
{
x.RegistrantCode,
x.Name,
x.Phone,
x.Address,
x.RegisterDate,
x.Price,
x.PositionId,
x.StartDate,
x.EndDate,
x.IsLongTerm,
x.IsActive,
x.CreatedAt,
x.UpdatedAt
})
.Take(1000) // 取前1000条
.ToList(); ;
return Ok(data);
}
[HttpPost]
[Route("api/ancestraltablet/registrant/getlistbypage")]
public IHttpActionResult GetRegistrantListByPage([FromBody] RegistrantSearchDto searchDto)
{
//获取登记人列表
var query = _db.AncestralTabletRegistrant.AsQueryable();
if( !string.IsNullOrEmpty(searchDto.searchName))
{
query = query.Where(r => r.Name == searchDto.searchName);
}
var tatol = query.Count();
var data = query.Select(x => new
{
x.RegistrantCode,
x.Name,
x.Phone,
x.Address,
x.RegisterDate,
x.Price,
x.PositionId,
x.StartDate,
x.EndDate,
x.IsLongTerm,
x.IsActive,
x.CreatedAt,
x.UpdatedAt
})
.OrderByDescending(a => a.CreatedAt)
.Skip((searchDto.page - 1) * searchDto.pageSize)
.Take(searchDto.pageSize) // 取前1000条
.ToList();
return Ok(new
{
data = data,
total = tatol
});
}
[HttpGet]
[Route("api/ancestraltablet/registrant/getbycode")]
public IHttpActionResult GetRegistrantByCode([FromUri] string registrantCode)
{
var r = _db.AncestralTabletRegistrant.Find(registrantCode);
if (r == null)
{
return NotFound();
}
var rDto = new
{
r.RegistrantCode,
r.Name,
r.Phone,
r.Address,
r.RegisterDate,
r.Price,
r.StartDate,
r.EndDate,
r.IsLongTerm,
r.IsActive,
r.PositionId,
positionName = r.AncestralTabletPosition?.PositionName,
// 嵌套查询牌位记录PositionRecord
TabletRecord = r.AncestralTabletPositionRecord
.Select(pr => new
{
pr.RecordId,
pr.RegistrantCode,
pr.NPTitle,
pr.NPStandDate,
pr.NPYangShang,
pr.WPContent,
})
.FirstOrDefault()
};
return Ok(rDto);
}
[HttpPost]
[Route("api/ancestraltablet/registrant/create")]
public IHttpActionResult CreateRegistrant([FromBody] AncestralTabletRegistrantDto dto)
{
//新增登记人api
if (dto == null)
{
return BadRequest("请求体不能为空");
}
if(!string.IsNullOrEmpty(dto.RegistrantCode))
{
return BadRequest("RegistrantCode 应传递空");
}
try
{
dto.RegistrantCode = GenerateRegistrantCode();
// 设置默认创建时间
dto.CreatedAt = DateTime.Now;
var entity = new Model.AncestralTabletRegistrant
{
RegistrantCode = dto.RegistrantCode,
Name = dto.Name,
Phone = dto.Phone,
Address = dto.Address,
RegisterDate = dto.RegisterDate,
Price = dto.Price,
PositionId = dto.PositionId,
StartDate = dto.startDate,
EndDate = dto.endDate,
IsLongTerm = dto.isLongTerm,
IsActive = dto.isActive,
CreatedAt = dto.CreatedAt,
UpdatedAt = dto.UpdatedAt
};
// 假设你有一个 EF DbContext如 _db
_db.AncestralTabletRegistrant.Add(entity);
if (dto.PositionId != null)
{
var position = _db.AncestralTabletPosition
.FirstOrDefault(p => p.PositionId == dto.PositionId);
if (position != null)
{
position.StatusCode = "used"; // 或者根据你的枚举/字段设置
}
}
_db.SaveChanges();
return Ok(new { message = "登记成功", registrantCode = entity.RegistrantCode });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpPost]
[Route("api/ancestraltablet/registrant/update")]
public IHttpActionResult UpdateRegistrant([FromBody] AncestralTabletRegistrantDto dto)
{
if (dto == null)
{
return BadRequest("请求体不能为空");
}
if (string.IsNullOrWhiteSpace(dto.RegistrantCode))
{
return BadRequest("缺少 RegistrantCode无法进行更新操作");
}
try
{
// 查找原始资料
var entity = _db.AncestralTabletRegistrant
.FirstOrDefault(r => r.RegistrantCode == dto.RegistrantCode);
if (entity == null)
{
return NotFound(); // 没有对应记录
}
// ===== 处理安位状态 =====
if (entity.PositionId != dto.PositionId)
{
// 1. 原来的安位设置为可用
if (entity.PositionId != null)
{
var oldPosition = _db.AncestralTabletPosition
.FirstOrDefault(p => p.PositionId == entity.PositionId);
if (oldPosition != null)
{
oldPosition.StatusCode = "available"; // 可用
}
}
// 2. 新的安位设置为已使用
if (dto.PositionId != null)
{
var newPosition = _db.AncestralTabletPosition
.FirstOrDefault(p => p.PositionId == dto.PositionId);
if (newPosition != null)
{
newPosition.StatusCode = "used"; // 已使用
}
}
// 更新登记人安位
entity.PositionId = dto.PositionId;
}
// 更新其它字段
entity.Name = dto.Name;
entity.Phone = dto.Phone;
entity.Address = dto.Address;
entity.RegisterDate = dto.RegisterDate;
entity.Price = dto.Price;
entity.StartDate = dto.startDate;
entity.EndDate = dto.endDate;
entity.IsLongTerm = dto.isLongTerm;
entity.IsActive = dto.isActive;
entity.UpdatedAt = DateTime.Now;
_db.SaveChanges();
return Ok(new { message = "登记人更新成功", registrantCode = entity.RegistrantCode });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpPost]
[Route("api/ancestraltablet/pw/create")]
public IHttpActionResult CreatePW([FromBody] AncestralTabletPositionRecordDto dto)
{
if (dto == null)
return BadRequest("请求体不能为空");
if (string.IsNullOrEmpty(dto.RegistrantCode))
return BadRequest("登记人编号RegistrantCode不能为空");
try
{
// 映射到数据库实体
var entity = new Model.AncestralTabletPositionRecord
{
RegistrantCode = dto.RegistrantCode,
NPTitle = dto.NPTitle,
NPStandDate = dto.NPStandDate,
NPYangShang = dto.NPYangShang,
WPContent = dto.WPContent,
CreatedAt = DateTime.Now,
UpdatedAt = null
};
_db.AncestralTabletPositionRecord.Add(entity);
_db.SaveChanges();
return Ok(new { message = "牌位登记成功", recordId = entity.RecordId });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
[HttpPost]
[Route("api/ancestraltablet/pw/update")]
public IHttpActionResult UpdatePW([FromBody] AncestralTabletPositionRecordDto dto)
{
if (dto == null)
return BadRequest("请求体不能为空");
try
{
var entity = _db.AncestralTabletPositionRecord.Find(dto.RecordId);
// 映射到数据库实体
if (entity == null)
return BadRequest("牌位不存在,更新失败");
entity.RegistrantCode = dto.RegistrantCode;
entity.NPTitle = dto.NPTitle;
entity.NPStandDate = dto.NPStandDate;
entity.NPYangShang = dto.NPYangShang;
entity.WPContent = dto.WPContent;
entity.UpdatedAt = DateTime.Now;
_db.SaveChanges();
return Ok(new { message = "牌位更新成功", recordId = entity.RecordId });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
public string GenerateRegistrantCode(string prefix = "REG", int randomLength = 6)
{
string datePart = DateTime.Now.ToString("yyyyMMdd");
string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
var suffix = new string(Enumerable.Repeat(chars, randomLength)
.Select(s => s[random.Next(s.Length)]).ToArray());
return $"{prefix}{datePart}{suffix}";
}
public class AncestralTabletRegistrantDto
{
public string RegistrantCode { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DateTime RegisterDate { get; set; }
public int? Price { get; set; }
public int? PositionId { get; set; }
public DateTime startDate { get; set; }
public DateTime? endDate { get; set; }
public bool isLongTerm { get; set; } = false;
public bool isActive { get; set; } = true;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
public class AncestralTabletPositionRecordDto
{
public int? RecordId { get; set; }
public string RegistrantCode { get; set; }
public string NPTitle { get; set; }
public DateTime NPStandDate { get; set; }
public string NPYangShang { get; set; }
public string WPContent { get; set; }
}
public class RegistrantSearchDto
{
public int page { get; set; } = 1;
public int pageSize { get; set; } = 10;
public string searchName { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
/// <summary>
/// AncestralTabletStatisticsController 的摘要描述
/// </summary>
public class AncestralTabletStatisticsController:ApiController
{
private Model.ezEntities db = new Model.ezEntities();
[HttpGet]
[Route("api/ancestraltablet/statistics/positions/availablepositions")]
public IHttpActionResult GetAvailablePositions()
{
var query =
from a in db.AncestralTabletArea // 区域表
join p in db.AncestralTabletPosition
on a.AreaId equals p.AreaId into ap
from p in ap.DefaultIfEmpty()
join r in db.AncestralTabletRegistrant
on p.PositionId equals r.PositionId into pr
from r in pr.DefaultIfEmpty()
group new { a, p, r } by new { a.AreaId, a.AreaName } into g
select new
{
AreaId = g.Key.AreaId,
AreaName = g.Key.AreaName,
TotalPositions = g.Count(x => x.p != null), // 总位置数
AvailableCount = g.Count(x => x.p != null && x.r == null) // 可用位置数(未登记)
};
var result = query.ToList();
return Ok(result);
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
/// <summary>
/// AncestralTabletStatusController 的摘要描述
/// </summary>
public class AncestralTabletStatusController:ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[Route("api/ancestraltablet/status/list")]
public IHttpActionResult GetStatusList()
{
var statusList = _db.AncestralTabletStatus
.Select(s => new
{
s.StatusCode,
s.StatusName,
s.StatusType
})
.ToList();
return Ok(statusList);
}
}

View File

@@ -0,0 +1,184 @@
using Model;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Results;
using static GuaDanStatusCode;
/// <summary>
/// HandleBedInUsedController 的摘要描述
/// </summary>
public class HandleBedInUsedController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public HandleBedInUsedController()
{
//
// TODO: 在這裡新增建構函式邏輯
//
}
[HttpPost]
[Route("api/bed/inuse/list")]
public IHttpActionResult Get([FromBody] UuidModel uuidModel)
{
//獲取已被預約或者正在入住的床位,如果有指定就會查詢指定條件,如果沒有指定就會返回所有
var query = _db.GuaDanOrderGuest
.Where(gd => gd.StatusCode == GuaDanStatusCode.Guadan.CheckedIn
|| gd.StatusCode == GuaDanStatusCode.Guadan.Booked).ToList();
if (uuidModel.bedUuid.HasValue)
{
// 優先按床位查詢
query = query.Where(g => g.BedUuid == uuidModel.bedUuid.Value).ToList();
}
else if (uuidModel.roomUuid.HasValue)
{
// 如果沒有 bed但有 room
query = query.Where(g => g.RoomUuid == uuidModel.roomUuid.Value).ToList();
}
else if (uuidModel.regionUuid.HasValue)
{
// 如果只有 region
//query = query.Where(g => g.Room.RegionUuid == uuidModel.regionUuid.Value);
query = query.Where(g => IsRegionOrAncestor(g.Room, uuidModel.regionUuid.Value)).ToList();
}
var data = query.Select(g => new
{
g.BedUuid,
g.RoomUuid,
g.Room.RegionUuid,
g.GuaDanOrderNo,
g.RegionRoomBed.Name,
fullName = GetFullBedName(g.BedUuid.Value),
g.followers.u_name,
guadan_during = new { g.CheckInAt, g.CheckOutAt },
status = new { g.StatusCode, g.RegionRoomBedStatus.Name }
});
return Ok(data.ToList());
}
[HttpPost]
[Route("api/bed/inuse/cancel/singlebed/booking")]
public IHttpActionResult CancelSingleBedBooking([FromBody] UuidModel uuidModel)
{
if (uuidModel?.bedUuid == null)
return BadRequest("床位ID不能為空");
using (var transaction = _db.Database.BeginTransaction())
{
try
{
// 查詢符合條件的訂單
var orders = _db.GuaDanOrderGuest
.Where(g => g.BedUuid == uuidModel.bedUuid)
.Where(g => g.StatusCode == GuaDanStatusCode.Guadan.Booked || g.StatusCode == GuaDanStatusCode.Guadan.CheckedIn)
.ToList();
if (!orders.Any())
return NotFound();
// 更新狀態
foreach (var order in orders)
{
if (!StatusTransitionManager.CanTransition(order.StatusCode, GuaDanStatusCode.Guadan.Cancelled))
{
return BadRequest("當前狀態不能被取消");
}
order.StatusCode = GuaDanStatusCode.Guadan.Cancelled; // 假設Cancelled是取消狀態
}
var schedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.TargetUuid == uuidModel.bedUuid)
.Where(s => s.GuaDanOrderGuest.StatusCode == GuaDanStatusCode.Guadan.Booked
|| s.GuaDanOrderGuest.StatusCode == GuaDanStatusCode.Guadan.CheckedIn)
.ToList();
foreach (var schedule in schedules)
{
schedule.IsCancel = true;
}
_db.SaveChanges();
transaction.Commit();
return Ok(new { message = "取消成功", cancelledCount = orders.Count });
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
}
[HttpGet]
[Route("api/bed/inuse/region/list")]
public IHttpActionResult GetRegionList()
{
var regions = _db.Region
.Select(r => new
{
r.Uuid,
r.Name,
})
.ToList();
return Ok(regions);
}
[HttpGet]
[Route("api/bed/inuse/room/list")]
public IHttpActionResult GetRoomList([FromUri] Guid? regionUuid = null)
{
var room = _db.Room.Where(r => !r.IsDeleted && r.IsActive.Value).ToList();
if (regionUuid != null)
{
room = room.Where(r => IsRegionOrAncestor(r, regionUuid.Value)).ToList();
}
var data = room.Select(r => new
{
r.Uuid,
r.Name,
fullName = r.Region.Name + "/" + r.Name,
}).ToList();
return Ok(data);
}
public string GetFullBedName(Guid bedUuid)
{
var bed = _db.RegionRoomBed.Find(bedUuid);
if (bed == null)
return "";
var name = bed.Name;
var room = bed.Room;
if (room == null)
return name;
name = room.Name + "/" + name;
var region = room?.Region;
while (region != null)
{
name = region.Name + "/" + name;
region = region.Region2; // 遞迴向上
}
return name;
}
bool IsRegionOrAncestor(Model.Room room, Guid regionUuid)
{
//判斷傳入的regionuuid是否是room的祖先
if (room.RegionUuid == regionUuid)
return true;
var region = room.Region;
while (region != null)
{
if (region.Uuid == regionUuid) return true;
region = region.Region2;
}
return false;
}
public class UuidModel
{
public Guid? regionUuid = null;
public Guid? roomUuid = null;
public Guid? bedUuid = null;
}
}

View File

@@ -74,9 +74,11 @@ public class ShuWenController : ApiController
{
shuwen.ShuWenList = ProcessDesserts2(_db.pro_order_detail.Where(a => a.pro_order.activity_num == activitynum.Value).ToList());
}
catch
catch (Exception ex)
{
shuwen.IsGenerating = false;
_db.SaveChanges();
return BadRequest("生成舒文失败:" + ex.Message);
}
shuwen.IsGenerating = false;
shuwen.UpdateTime = DateTime.Now;
@@ -222,34 +224,42 @@ public class ShuWenController : ApiController
[Route("api/shuwen/download")]
public HttpResponseMessage DownloadShuWenWord(int? activitynum)
{
var data = _db.ShuWen.Where(a => a.ActivityNum == activitynum).FirstOrDefault();
if (data == null)
try
{
//return;
}
string json = data.ShuWenList;
string ActivityName = _db.activities.Where(a => a.num == data.ActivityNum).FirstOrDefault().subject;
if (json == null)
{
//return;
}
string fileName = $"疏文名單_{DateTime.Now:yyyyMMddHHmmss}.docx";
var stream = new MemoryStream();
GenerateShuWenWord_OpenXml(json, stream, ActivityName);
stream.Position = 0;
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
response.Content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
var data = _db.ShuWen.Where(a => a.ActivityNum == activitynum).FirstOrDefault();
if (data == null)
{
FileName = fileName
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "活動編號不能為空");
}
string json = data.ShuWenList;
string ActivityName = _db.activities.Where(a => a.num == data.ActivityNum).FirstOrDefault().subject;
if (json == null)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "疏文列表为空,无法生成 Word");
}
string fileName = $"疏文名單_{DateTime.Now:yyyyMMddHHmmss}.docx";
var stream = new MemoryStream();
GenerateShuWenWord_OpenXml(json, stream, ActivityName);
stream.Position = 0;
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
return response;
response.Content.Headers.ContentType =
new System.Net.Http.Headers.MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = fileName
};
return response;
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
public void GenerateShuWenWord_OpenXml(string json, Stream outputStream, string ActivityName ="")

View File

@@ -0,0 +1,140 @@
using Model;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
/// <summary>
/// guadanGuestQueryController 的摘要描述
/// </summary>
public class guadanGuestQueryController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public guadanGuestQueryController()
{
//
// TODO: 在這裡新增建構函式邏輯
//
}
[HttpPost]
[Route("api/guadan/guest/query/list")]
public async Task<IHttpActionResult> GetList([FromBody] SearchGuestModel search)
{
var query = _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode != "404");
if(search.SearchName != null)
{
query = query.Where(guest => guest.followers.u_name.Contains(search.SearchName));
}
if(search.searchCheckInDate != null)
{
query = query.Where(guest => guest.CheckInAt == search.searchCheckInDate);
}
if(search.searchCheckOutDate != null)
{
query = query.Where(guest => guest.CheckOutAt == search.searchCheckOutDate);
}
if(search.searchCheckInDateStart != null)
{
query = query.Where(guest => guest.CheckInAt >= search.searchCheckInDateStart);
}
if (search.searchCheckInDateEnd != null)
{
query = query.Where(guest => guest.CheckInAt <= search.searchCheckInDateEnd);
}
if (search.searchCheckOutDateStart != null)
{
query = query.Where(guest => guest.CheckOutAt >= search.searchCheckOutDateStart);
}
if (search.searchCheckOutDateEnd != null)
{
query = query.Where(guest => guest.CheckOutAt <= search.searchCheckOutDateEnd);
}
var totalCount = await query.CountAsync();
var pagedData = await query
.OrderByDescending(a => a.CheckInAt) // 可根据需要排序
.Skip((search.Page - 1) * search.PageSize)
.Take(search.PageSize)
.ToListAsync();
var data1 = pagedData.Select(a => new
{
name = a.followers != null ? a.followers.u_name : null,
checkindate = a.CheckInAt,
checkoutdate = a.CheckOutAt,
guadanorderno = a.GuaDanOrderNo,
roomName = GetRoomAndBedString(a.RegionRoomBed),
statusName = a.RegionRoomBedStatus.Name
}).ToList();
return Ok(new
{
items = data1,
total = totalCount,
});
}
[HttpGet]
[Route("api/guadan/guest/checkin/list")]
public async Task<IHttpActionResult> GetCheckInGuest([FromUri] DateTime date)
{
var today = DateTime.Now.Date;
var data = await _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode == "402" || guest.StatusCode == "403")
.Where(guest => guest.RegionAndRoomAndBedSchedule
.Any(s => s.ScheduleDate == date.Date && s.ScheduleDate <= today) == true)
.Select(guest => new
{
name = guest.followers.u_name,
gender = guest.followers.sex,
})
.ToListAsync();
return Ok(data);
}
[HttpGet]
[Route("api/guadan/guest/booking/list")]
public async Task<IHttpActionResult> GetBookingGuest([FromUri] DateTime date)
{
var data = await _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode == "402" || guest.StatusCode == "401" || guest.StatusCode == "403")
.Where(guest => guest.RegionAndRoomAndBedSchedule.Any(s => s.ScheduleDate == date.Date) == true)
.Select(guest => new
{
name = guest.followers.u_name,
gender = guest.followers.sex,
})
.ToListAsync();
return Ok(data);
}
public string GetRoomAndBedString(RegionRoomBed bed)
{
if (bed == null || bed.Room == null) return "";
var room = bed.Room;
var region = room.Region;
var name = room.Name + "/" + bed.Name;
if(region != null)
{
name = region.Name + "/" + name;
}
var parentRegion = region.Region2;
while (parentRegion != null)
{
name = parentRegion.Name + "/" + name;
parentRegion = parentRegion.Region2;
}
return name;
}
public class SearchGuestModel
{
public string SearchName = null;
public int Page = 1;
public int PageSize = 10;
public DateTime? searchCheckInDateStart = null;//入住日期的开始
public DateTime? searchCheckInDateEnd = null;//入住日期的结束
public DateTime? searchCheckOutDateStart = null;//退房日期的开始
public DateTime? searchCheckOutDateEnd = null;//退房日期的结束
public DateTime? searchCheckInDate = null;
public DateTime? searchCheckOutDate = null;
}
}

View File

@@ -1,4 +1,5 @@
using Model;
using PagedList;
using System;
using System.Collections.Generic;
using System.Data.Entity;
@@ -6,20 +7,54 @@ using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using static regionController;
/// <summary>
/// guadanOderController 的摘要描述
/// </summary>
[ezAuthorize]
public class guadanOrderController: ApiController
public class guadanOrderController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
[HttpPost]
[Route("api/guadan/list")]
public async Task<IHttpActionResult> getGuadanList()
public async Task<IHttpActionResult> getGuadanList([FromBody] guadan_order_search_dto search)
{
var data = await _db.GuaDanOrder.OrderByDescending(b => b.CreatedAt)
var lastCheckoutTime = _db.GuadanTimeSetting.FirstOrDefault();
string lastCheckoutTimeStr = null;
if (lastCheckoutTime != null)
{
lastCheckoutTimeStr = lastCheckoutTime.LatestCheckOut;
}
var query = _db.GuaDanOrder
.Where(a => a.IsCancel == false)
.Where(a => a.IsDeleted == false);
if(!string.IsNullOrEmpty(search.guaDanOrderNo))
{
query = query.Where(order => order.GuaDanOrderNo == search.guaDanOrderNo);
}
if (search.guadanUser != null)
{
query = query.Where(order => order.BookerName == search.guadanUser);
}
if (search.startDate != null && search.endDate != null)
{
query = query.Where(order => order.StartDate >= search.startDate)
.Where(order => order.EndDate <= search.endDate);
}
else
{
if (search.startDate != null)
{
query = query.Where(order => order.StartDate == search.startDate);
}
else if (search.endDate != null)
{
query = query.Where(order => order.EndDate == search.endDate);
}
}
var total = query.Count();
var data1 = await query.ToListAsync();
var data = data1
.OrderByDescending(b => b.CreatedAt)
.Select(a => new
{
uuid = a.Uuid,
@@ -29,24 +64,65 @@ public class guadanOrderController: ApiController
created_at = a.CreatedAt,
updated_at = a.UpdatedAt,
notes = a.Notes,
is_timeout = !string.IsNullOrEmpty(lastCheckoutTimeStr) &&
_db.GuaDanOrderGuest
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && !g.IsDeleted && g.StatusCode == GuaDanOrderGuest.STATUS_CHECKED_IN)
.ToList()
.Any(g =>
g.CheckOutAt.HasValue &&
DateTime.Parse(g.CheckOutAt.Value.ToString("yyyy-MM-dd") + " " + lastCheckoutTimeStr) < DateTime.Now
),
activity = _db.activities
.Where(act => act.num == a.ActivityNum)
.Select(act => new
{
subject = act.subject
})
.FirstOrDefault(),
bookerName = a.BookerName,
guest_count = _db.GuaDanOrderGuest.Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo).Count(),
}).ToListAsync();
return Ok(data);
guest_count = _db.GuaDanOrderGuest
.Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo && c.IsDeleted == false)
.Where(c => c.RegionRoomBedStatus.Code != GuaDanOrderGuest.STATUS_CANCELLED)
.Count(),
guadan_status = _db.GuaDanOrderGuest
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
.All(g => g.StatusCode == "401") ? new { code=501, name="預約" }:
_db.GuaDanOrderGuest
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
.All(g => g.StatusCode == "403") ? new { code = 502, name = "全部退房" } :
_db.GuaDanOrderGuest
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
.Any(g => g.StatusCode == "402" && a.IsCancel == false) ? new { code = 503, name = "正在入住" } :
new { code = 504, name = "部分退房" }
})
.Skip((search.page - 1) * search.pageSize)
.Take(search.pageSize)
.ToList();
return Ok(new
{
total,
data
});
}
[HttpGet]
[Route("api/guadan/getorderbyid")]
public async Task<IHttpActionResult> getGuadanOrderById(string orderId)
{
var order = await _db.GuaDanOrder.Where(a => a.GuaDanOrderNo == orderId).FirstOrDefaultAsync();
var order = await _db.GuaDanOrder
.Where(a => a.GuaDanOrderNo == orderId)
.Where(a => a.IsCancel == false && a.IsDeleted == false)
.FirstOrDefaultAsync();
if (order == null)
{
return BadRequest("未找到对应订单");
return BadRequest("未找到對應訂單");
}
var result = new
{
order.admin,
order.follower,
order.followers,
StartDate = order.StartDate?.ToString("yyyy-MM-dd"),
EndDate = order.EndDate?.ToString("yyyy-MM-dd"),
order.CreateUser,
@@ -59,6 +135,8 @@ public class guadanOrderController: ApiController
order.BookerPhone,
order.IsDeleted,
order.Uuid,
order.ActivityNum,
};
return Ok(result);
@@ -71,9 +149,9 @@ public class guadanOrderController: ApiController
{
return BadRequest("掛單資料不可為空");
}
if(model.Uuid.HasValue)
if (model.Uuid.HasValue)
{
return BadRequest("已存在对应挂单资料");
return BadRequest("已存在對應掛單資料");
}
try
{
@@ -99,6 +177,7 @@ public class guadanOrderController: ApiController
BookerName = model.bookerName,
BookerPhone = model.bookerPhone,
Uuid = Guid.NewGuid(),
ActivityNum = model.activityNum,
};
_db.GuaDanOrder.Add(guadanorder);
await _db.SaveChangesAsync();
@@ -131,26 +210,30 @@ public class guadanOrderController: ApiController
var order = await _db.GuaDanOrder.FindAsync(model.Uuid.Value);
if (order == null)
{
return BadRequest("未找到对应挂单资料");
return BadRequest("未找到對應掛單資料");
}
order.StartDate = model.startdate;
order.EndDate = model.enddate;
order.Notes = model.note;
order.BookerName = model.bookerName;
order.BookerPhone = model.bookerPhone;
order.ActivityNum = model.activityNum;
await _db.SaveChangesAsync();
return Ok(model);
}
[HttpPost]
[Route("api/guadan/delete")]
public async Task<IHttpActionResult> deleteGuadanOrder([FromUri] Guid uuid)
[Route("api/guadan/cancel")]
public async Task<IHttpActionResult> CancelGuadanOrder([FromUri] Guid uuid)
{
var guadan = await _db.GuaDanOrder.FindAsync(uuid);
if (guadan == null)
{
return NotFound();
}
if (_db.GuaDanOrderGuest.Any(a => (a.GuaDanOrderNo == guadan.GuaDanOrderNo) && a.StatusCode != "404"))
{
return BadRequest($"該掛單已經存在掛單蓮友,不能取消!");
}
using (var transaction = _db.Database.BeginTransaction())
{
try
@@ -158,31 +241,27 @@ public class guadanOrderController: ApiController
var guadanGuests = await _db.GuaDanOrderGuest
.Where(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo)
.ToListAsync();
var scheduleIds = _db.RegionAndRoomAndBedSchedule
.Where(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo)
.Where( b => b.IsActive == true)
.Select(c => c.GuaDanOrderNo)
.ToList();
if (guadanGuests.Any())
{
_db.GuaDanOrderGuest.RemoveRange(guadanGuests);
foreach (var guest in guadanGuests)
{
guest.StatusCode = "404";
// 取消所有相關的排程
if (guest.RegionAndRoomAndBedSchedule != null && guest.RegionAndRoomAndBedSchedule.Any())
{
foreach (var schedule in guest.RegionAndRoomAndBedSchedule)
{
schedule.IsCancel = true;
}
}
}
await _db.SaveChangesAsync();
}
if (scheduleIds.Any())
{
var schedules = await _db.RegionAndRoomAndBedSchedule
.Where(a => scheduleIds.Contains(a.GuaDanOrderNo))
.ToListAsync();
if (schedules.Any())
_db.RegionAndRoomAndBedSchedule.RemoveRange(schedules);
}
_db.GuaDanOrder.Remove(guadan);
guadan.IsCancel = true;
await _db.SaveChangesAsync();
transaction.Commit();
return Ok(new { message = "删除成功" });
return Ok(new { message = "取消成功" });
}
catch (Exception ex)
{
@@ -200,7 +279,6 @@ public class guadanOrderController: ApiController
public int guest_id { get; set; }
public DateTime start_date { get; set; }
public DateTime? end_date { get; set; }
public Guid? statusUuid { get; set; }
public int? create_user { get; set; }
public DateTime created_at { get; set; }
public DateTime updated_at { get; set; }
@@ -211,4 +289,14 @@ public class guadanOrderController: ApiController
public RegionRoomBed bed { get; set; } = null;
}
public class guadan_order_search_dto
{
public DateTime? startDate { get; set; }
public DateTime? endDate { get; set; }
public string guadanUser { get; set; }
public int page { get; set; } = 1;
public int pageSize { get; set; } = 10;
public string guaDanOrderNo { get; set; } = null;
}
}

View File

@@ -1,8 +1,12 @@
using Model;
using DocumentFormat.OpenXml.Drawing;
using Model;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
@@ -11,7 +15,7 @@ using System.Web.Http;
/// guadanOrderGuest 的摘要描述
/// </summary>
[ezAuthorize]
public class guadanOrderGuestController: ApiController
public class guadanOrderGuestController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
[HttpGet]
@@ -25,12 +29,12 @@ public class guadanOrderGuestController: ApiController
[Route("api/guadanorderguest/getbyorderno")]
public async Task<IHttpActionResult> getByOrderNo(string orderNo)
{
// 先查数据库,不做格式化
// 先查資料庫,不做格式化
var qry = await _db.GuaDanOrderGuest
.Where(a => a.GuaDanOrderNo == orderNo && a.IsDeleted == false)
.Where(a => a.GuaDanOrderNo == orderNo && a.IsDeleted == false && a.RegionRoomBedStatus.Code != "404")
.ToListAsync();
// 拉到内存后再处理日期
// 拉到記憶體後再處理日期
var data = qry.Select(a => new guadan_order_guest_display_dto
{
Uuid = a.Uuid,
@@ -42,27 +46,50 @@ public class guadanOrderGuestController: ApiController
checkoutat = a.CheckOutAt.HasValue ? a.CheckOutAt.Value.ToString("yyyy-MM-dd") : null,
phone = null,
roomName = a.Room.Name,
bedName = a.RegionRoomBed.Name,
bedName = GetBedString(a.RegionRoomBed),
orderNo = a.GuaDanOrderNo,
follower = a.follower,
statusUuid = a.statusUuid,
follower = a.followers == null ? null : new FollowerDto
{
num = a.followers.num,
u_name = a.followers.u_name,
sex = a.followers.sex
},
statuscode = a.StatusCode,
statusName = a.RegionRoomBedStatus?.Name,
}).ToList();
return Ok(data);
}
public string GetBedString(RegionRoomBed bed)
{
if (bed == null)
return "";
var room = bed.Room;
var name = room.Name + "/" + bed.Name;
var region = room.Region;
name = region.Name + "/" + name;
var parentRegion = region.Region2;
while(parentRegion != null)
{
name = parentRegion.Name + "/" + name;
parentRegion = parentRegion.Region2;
}
return name;
}
[HttpPost]
[Route("api/guadanorderguest/create")]
public async Task<IHttpActionResult> create([FromBody] guadan_order_guest_dto model)
{
if (model == null)
return BadRequest("");
/*if(model.statuscode == null)
{
return BadRequest("狀態不能為空");
}*/
// 驗證床位與蓮友
var bed = _db.RegionRoomBed.Find(model.bedUuid.Value);
if (model.followerNum.HasValue && model.bedUuid.HasValue)
{
var bed = _db.RegionRoomBed.Find(model.bedUuid.Value);
var follower = _db.followers.Find(model.followerNum.Value);
if (bed == null || follower == null)
@@ -81,16 +108,16 @@ public class guadanOrderGuestController: ApiController
}
if (!model.bedUuid.HasValue)
return BadRequest("床位 UUID 不能空");
return BadRequest("床位 UUID 不能空");
if (!model.checkInAt.HasValue)
return BadRequest("入住时间不能空");
return BadRequest("入住時間不能空");
// 長期占用處理checkOutAt 可為 null
DateTime? checkOut = model.checkOutAt.Value.Date;
if (checkOut.HasValue && model.checkInAt > checkOut)
return BadRequest("掛單結束時間不能再開始時間之前");
if(model.checkInAt == model.checkOutAt)
if (model.checkInAt == model.checkOutAt)
{
return BadRequest("掛單結束時間和開始時間不能是同一天");
}
@@ -103,8 +130,22 @@ public class guadanOrderGuestController: ApiController
);
if (!bedIsCanUse)
return BadRequest("床位在該時間段內已被占用");
if (model.followerNum.HasValue)
{
if (_db.GuaDanOrderGuest.Any(a => a.FollowerNum == model.followerNum
&& a.GuaDanOrderNo == model.orderNo
&& a.StatusCode != "404"
))
return BadRequest("該蓮友已經在該掛單中");
}
//建立訂單的的時候狀態只能是401或者402
var targetStatus = _db.RegionRoomBedStatus.Where(a => a.Code == GuaDanOrderGuest.STATUS_BOOKED).FirstOrDefault();
if (targetStatus == null)
{
return Content(HttpStatusCode.PreconditionFailed, "找不到目標狀態,請先建立對應狀態");
}
// 建立掛單
var guest = new GuaDanOrderGuest
{
GuaDanOrderNo = model.orderNo,
@@ -114,14 +155,9 @@ public class guadanOrderGuestController: ApiController
CheckInAt = model.checkInAt?.Date,
CheckOutAt = checkOut,
Uuid = Guid.NewGuid(),
statusUuid = model.statusUuid,
StatusCode = GuaDanOrderGuest.STATUS_BOOKED,
};
if (model.followerNum.HasValue)
{
if (_db.GuaDanOrderGuest.Any(a => a.FollowerNum == model.followerNum && a.GuaDanOrderNo == model.orderNo))
return BadRequest("該蓮友已經在該掛單中");
}
_db.GuaDanOrderGuest.Add(guest);
await _db.SaveChangesAsync();
@@ -140,12 +176,13 @@ public class guadanOrderGuestController: ApiController
Description = "床位掛單",
ScheduleDate = scheduleDate,
IsDeleted = false,
IsActive = true,
IsCancel = false,
TargetUuid = guest.BedUuid,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo,
Uuid = Guid.NewGuid()
Uuid = Guid.NewGuid(),
GuaDanOrderGuestUuid = guest.Uuid,
};
_db.RegionAndRoomAndBedSchedule.Add(schedul);
}
@@ -159,19 +196,17 @@ public class guadanOrderGuestController: ApiController
Description = "床位掛單(長期占用)",
ScheduleDate = null,
IsDeleted = false,
IsActive = true,
IsCancel = false,
TargetUuid = guest.BedUuid,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo,
Uuid = Guid.NewGuid()
Uuid = Guid.NewGuid(),
GuaDanOrderGuestUuid = guest.Uuid
};
_db.RegionAndRoomAndBedSchedule.Add(schedul);
}
await _db.SaveChangesAsync();
await _db.SaveChangesAsync();
return Ok();
}
@@ -201,10 +236,10 @@ public class guadanOrderGuestController: ApiController
}
if (!model.bedUuid.HasValue)
return BadRequest("床位 UUID 不能空");
return BadRequest("床位 UUID 不能空");
if (!model.checkInAt.HasValue)
return BadRequest("入住时间不能空");
return BadRequest("入住時間不能空");
// 長期占用處理
DateTime? checkOut = model.checkOutAt?.Date;
@@ -234,18 +269,27 @@ public class guadanOrderGuestController: ApiController
.AnyAsync();
if (exists) return BadRequest("該蓮友已經在該掛單中");
}
/*var targetStatus = _db.RegionRoomBedStatus.Find(model.statuscode);
if (targetStatus == null)
{
return BadRequest("目標狀態不存在");
}
if(!StatusTransitionManager.CanTransition(guest.StatusCode,targetStatus.Code))
{
return BadRequest("狀態的變化不合法");
}*/
// 更新掛單基本資料
guest.FollowerNum = model.followerNum;
guest.RoomUuid = model.roomUuid;
guest.BedUuid = model.bedUuid;
guest.CheckInAt = model.checkInAt?.Date;
guest.CheckOutAt = checkOut;
guest.statusUuid = model.statusUuid;
//guest.StatusCode = model.statuscode;
//更新的時候不能更新狀態狀態都用單獨的操作api控制
// 刪除原有每日排程
var oldSchedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo)
.Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo && s.TargetUuid == guest.BedUuid)
.ToList();
_db.RegionAndRoomAndBedSchedule.RemoveRange(oldSchedules);
@@ -262,12 +306,13 @@ public class guadanOrderGuestController: ApiController
Description = "床位掛單",
ScheduleDate = date,
IsDeleted = false,
IsActive = true,
IsCancel = false,
TargetUuid = guest.BedUuid,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo,
Uuid = Guid.NewGuid()
Uuid = Guid.NewGuid(),
GuaDanOrderGuestUuid = guest.Uuid,
};
_db.RegionAndRoomAndBedSchedule.Add(schedul);
}
@@ -281,12 +326,13 @@ public class guadanOrderGuestController: ApiController
Description = "床位掛單(長期占用)",
ScheduleDate = null,
IsDeleted = false,
IsActive = true,
IsCancel = false,
TargetUuid = guest.BedUuid,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo,
Uuid = Guid.NewGuid()
Uuid = Guid.NewGuid(),
GuaDanOrderGuestUuid = guest.Uuid,
};
_db.RegionAndRoomAndBedSchedule.Add(schedul);
}
@@ -296,27 +342,293 @@ public class guadanOrderGuestController: ApiController
await _db.SaveChangesAsync();
return Ok();
}
[HttpPost]
[Route("api/guadanorderguest/delete")]
public async Task<IHttpActionResult> deleteGuadanGuest([FromUri] Guid uuid)
[Route("api/guadanorderguest/xuzhu")]
public async Task<IHttpActionResult> ExtendStay([FromBody] XuZhuModel model)
{
//續住方法
if (model == null)
return BadRequest("請求數據為空");
if (model.GuestUuid == Guid.Empty || model.GuestBedUuid == Guid.Empty)
return BadRequest("GuestUuid 或 GuestBedUuid 無效");
var guest = await _db.GuaDanOrderGuest.FindAsync(model.GuestUuid);
if (guest == null)
{
return BadRequest("掛單不存在");
}
if (guest.BedUuid != model.GuestBedUuid)
{
return BadRequest("床位不正確");
}
var bedIsCanUse = await RegionAndRoomAndBedSchedule.IsBedAvailableAsync(_db, model.GuestBedUuid, model.CurrentCheckoutDate, model.NewCheckoutDate);
if (!bedIsCanUse)
{
return BadRequest("該床位在續住時間段內被預定,無法續住");
}
var newStartDate = model.CurrentCheckoutDate.Date;
var newEndDate = model.NewCheckoutDate.Date.AddDays(-1);
if (newEndDate < newStartDate)
return BadRequest("續住日期區間無效");
for (var date = newStartDate; date <= newEndDate; date = date.AddDays(1))
{
var newSchedule = new RegionAndRoomAndBedSchedule
{
GuaDanOrderNo = guest.GuaDanOrderNo,
Uuid = Guid.NewGuid(),
TargetUuid = model.GuestBedUuid,
GuaDanOrderGuestUuid = model.GuestUuid,
ScheduleDate = date,
Title = "續住掛單", // 一天一條,開始和結束是同一天
Description = "續住掛單",
UseType = 30,
CreatedAt = DateTime.UtcNow
};
_db.RegionAndRoomAndBedSchedule.Add(newSchedule);
}
guest.CheckOutAt = model.NewCheckoutDate.Date;
await _db.SaveChangesAsync(); // 保存資料庫操作
return Ok(new { message = "續住成功" });
}
[HttpPost]
[Route("api/guadanorderguest/cancel")]
public async Task<IHttpActionResult> CancelGuadanGuest([FromUri] Guid uuid)
{
var guest = await _db.GuaDanOrderGuest.FindAsync(uuid);
if (guest == null)
return BadRequest("未找到指定挂单资料");
return NotFound();
// 删除所有与该 guest 相关的排程(每日排程或長期占用)
var schedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderNo == guest.GuaDanOrderNo)
.ToList();
if (guest.StatusCode == "404")
return BadRequest("該掛單已取消,無需再取消");
if (schedules.Any())
_db.RegionAndRoomAndBedSchedule.RemoveRange(schedules);
if (!StatusTransitionManager.CanTransition(guest.StatusCode, "404"))
{
return BadRequest("當前狀態不能取消");
}
var cancelStatus = await _db.RegionRoomBedStatus
.FirstOrDefaultAsync(a => a.Code == "404");
if (cancelStatus == null)
return Content(HttpStatusCode.PreconditionFailed, "找不到取消狀態Code=404請先建立對應狀態");
//把狀態設置為取消
using (var tx = _db.Database.BeginTransaction())
{
try
{
// 1) 更新 guest
guest.StatusCode = "404";
_db.GuaDanOrderGuest.Remove(guest);
await _db.SaveChangesAsync();
// 2) 取消相關排程(常見做法:只取消未來&未取消的)
var today = DateTime.Today;
return Ok(new { message = "删除成功" });
var schedules = await _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderGuestUuid == guest.Uuid
&& s.IsCancel == false
//&& s.ScheduleDate >= today
)
// ✅ 只取消今天與未來的,能取消就代表未入住,
// 未入住就要全部取消,如果是入住後提前退房,
// 就要取消未來的,取消未來的時候要注意今日是否包含的問題
.ToListAsync();
foreach (var s in schedules)
{
s.IsCancel = true;
}
// 3) 釋放占用資源(若有床位/房間占用紀錄)
if (guest.BedUuid != null)
{
// 先抓到目標狀態
var freeStatus = await _db.RegionRoomBedStatus
.FirstOrDefaultAsync(a => a.Code == "101");
if (freeStatus == null)
return Content(HttpStatusCode.PreconditionFailed, "找不到床位狀態 Code=101");
if (guest.RegionRoomBed != null)
{
guest.RegionRoomBed.StatusCode = freeStatus.Code;
}
}
await _db.SaveChangesAsync();
tx.Commit();
return Ok(new
{
message = "取消成功",
guestUuid = guest.Uuid,
canceledSchedules = schedules.Count
});
}
catch (DbUpdateConcurrencyException)
{
tx.Rollback();
return StatusCode(HttpStatusCode.PreconditionFailed); // 或自訂訊息
}
catch (Exception ex)
{
tx.Rollback();
return InternalServerError(ex);
}
}
}
[HttpPost]
[Route("api/guadanorderguest/checkout")]
public IHttpActionResult CheckoutGuadanOrderGuest(Guid uuid)
{
DbContextTransaction transaction = null;
try
{
transaction = _db.Database.BeginTransaction(); // 開啟事務
// 1⃣ 取得該筆掛單
var guest = _db.GuaDanOrderGuest
.Where(a => a.Uuid == uuid)
.Where(a => !a.IsDeleted && a.StatusCode != "404")
.FirstOrDefault();
if (guest == null)
return NotFound();
// 2⃣ 標記為已退房
var targetStatus = _db.RegionRoomBedStatus
.Where(a => a.Code == "403")
.FirstOrDefault();
if (targetStatus == null)
return Content(HttpStatusCode.PreconditionFailed, "找不到退房狀態Code=403請先建立對應狀態");
if (!StatusTransitionManager.CanTransition(guest.StatusCode, targetStatus.Code))
return BadRequest("掛單狀態轉換不對");
if (!StatusTransitionManager.CanTransition(guest.RegionRoomBed.StatusCode, "101"))
return BadRequest("床位掛單狀態轉換不對");
guest.StatusCode = targetStatus.Code;
guest.RegionRoomBed.StatusCode = "101";
//更新未來排程為取消
var latestCheckoutStr = _db.GuadanTimeSetting
.Select(a => a.LatestCheckOut) // 字串 "HH:mm"
.FirstOrDefault();
TimeSpan? latestCheckoutTime = null;
if (!string.IsNullOrEmpty(latestCheckoutStr))
{
// 嘗試解析字串
if (TimeSpan.TryParse(latestCheckoutStr, out var ts))
{
latestCheckoutTime = ts;
}
}
var futureSchedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderGuestUuid == guest.Uuid);
if (latestCheckoutTime == null || DateTime.Now.TimeOfDay > latestCheckoutTime)
{
// 包含今天
futureSchedules = futureSchedules.Where(s => s.ScheduleDate > DateTime.Today);
}
else
{
// 不包含今天
futureSchedules = futureSchedules.Where(s => s.ScheduleDate >= DateTime.Today);
}
foreach (var schedule in futureSchedules)
{
schedule.IsCancel = true;
}
// 4⃣ 保存所有變更
_db.SaveChanges();
// 5⃣ 提交事務
transaction.Commit();
return Ok(new { message = "退房完成", guestUuid = guest.Uuid });
}
catch (Exception ex)
{
if (transaction != null)
transaction.Rollback(); // 回滾事務
return InternalServerError(ex);
}
finally
{
if (transaction != null)
transaction.Dispose();
}
}
[HttpPost]
[Route("api/guadanorderguest/checkin")]
public IHttpActionResult CheckinGuadanGuest([FromUri] Guid uuid)
{
if (uuid == Guid.Empty)
return BadRequest("uuid不能為空");
// 獲取掛單客人
var guest = _db.GuaDanOrderGuest
.Include(g => g.RegionRoomBedStatus) // 包含導航屬性
.FirstOrDefault(g => g.Uuid == uuid);
if (guest == null)
return NotFound();
string currentStatus = guest.StatusCode;
// 判斷狀態流轉是否合法
if (!StatusTransitionManager.CanTransition(currentStatus, GuaDanOrderGuest.STATUS_CHECKED_IN))
{
return BadRequest("當前狀態不允許入住");
}
// ---------- 新增:檢查今天是否在排程表 ----------
var today = DateTime.Today;
bool hasScheduleToday = _db.RegionAndRoomAndBedSchedule
.Any(s => s.GuaDanOrderGuestUuid == guest.Uuid && s.ScheduleDate == today);
if (!hasScheduleToday)
{
return BadRequest("不在入住時間段內,無法入住");
}
try
{
// 更新狀態
guest.StatusCode = GuaDanOrderGuest.STATUS_CHECKED_IN;
// 如果需要,更新床位狀態,比如變為占用
if (guest.BedUuid != null)
{
var bed = _db.RegionRoomBed.FirstOrDefault(b => b.Uuid == guest.BedUuid);
if (bed == null)
{
return BadRequest("入住床位不存在");
}
if (StatusTransitionManager.CanTransition(bed.StatusCode, "102")) // 102 = 占用
{
bed.StatusCode = "102";
}
else
{
return BadRequest($"當前床位狀態:{bed.RegionRoomBedStatus.Name} 不能入住");
}
}
else if (guest.BedUuid == null)
{
return BadRequest("入住床位不存在");
}
_db.SaveChanges();
return Ok(new { message = "入住成功", statusCode = guest.StatusCode });
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
public class guadan_order_guest_dto
@@ -328,7 +640,7 @@ public class guadanOrderGuestController: ApiController
public Guid? bedUuid { get; set; }
public DateTime? checkInAt { get; set; }
public DateTime? checkOutAt { get; set; }
public Guid? statusUuid { get; set; }
public string statuscode { get; set; }
}
public class guadan_order_guest_display_dto
{
@@ -337,17 +649,31 @@ public class guadanOrderGuestController: ApiController
public string orderNo { get; set; }
public string name { get; set; }
public Guid? roomUuid { get; set; }
public Guid? bedUuid { get;set; }
public string checkinat { get;set; }
public string checkoutat { get;set; }
public int? gender { get; set; }
public Guid? statusUuid { get; set; }
public Guid? bedUuid { get; set; }
public string checkinat { get; set; }
public string checkoutat { get; set; }
public int? gender { get; set; }
public string statuscode { get; set; }
public string statusName { get; set; }
public string phone { get; set; }
public string note { get; set; }
public string roomName { get; set; }
public string bedName { get; set; }
public follower follower { get; set; }
public bool iscancel { get; set; }
public FollowerDto follower { get; set; }
}
public class FollowerDto
{
public int num { get; set; }
public string u_name { get; set; }
public string sex { get; set; }
}
public class XuZhuModel
{
public Guid GuestUuid { get; set; } // 不可為空
public Guid GuestBedUuid { get; set; } // 不可為空
public DateTime CurrentCheckoutDate { get; set; } // 當前退房時間
public DateTime NewCheckoutDate { get; set; } // 新退房時間
}
}

View File

@@ -21,39 +21,49 @@ public class guadanStatisticsController: ApiController
//挂单统计:房间,床位,挂单笔数,挂单人数的统计
var now = DateTime.Now;
var roomCount = await _db.Room.Where(a => a.IsDeleted == false).CountAsync();
var rooms = await _db.Room.Include(r => r.RegionRoomBed).ToListAsync();
var emptyRoomCount = rooms
.Where(r => r.RegionRoomBed.All(b => b.IsAvailableDuring(now, now, _db))) // 這裡就能用方法
.Count();
var bedCount = await _db.RegionRoomBed.Where(a => a.IsDeleted == false).CountAsync();
var maleBedCount = await _db.RegionRoomBed.Where(a => a.IsDeleted == false && a.Gender == true).CountAsync();
var femaleBedCount = await _db.RegionRoomBed.Where(a => a.IsDeleted == false && a.Gender == false).CountAsync();
var guadanTotalCount = await _db.GuaDanOrder.Where(a => a.IsDeleted == false).CountAsync();
var guadanPeopleTotal = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false).CountAsync();
var guadanPeopleMale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.follower.sex == "男眾").CountAsync();
var guadanPeopleFemale = await _db.GuaDanOrderGuest.Where(a => a.IsDeleted == false && a.follower.sex == "女眾").CountAsync();
dynamic bedCounts = await RegionAndRoomAndBedSchedule.GetAvailableBedCountsAsync(_db, DateTime.Now, DateTime.Now);
var guadanCurrentCount = await _db.GuaDanOrder.Where(a => now < a.EndDate).CountAsync();
var guadanPeopleCurrent = await _db.GuaDanOrderGuest.Where( a => a.CheckOutAt > now).CountAsync();
var guadanPeopleCurrentMale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.follower.sex == "男眾").CountAsync();
var guadanPeopleCurrentFemale = await _db.GuaDanOrderGuest.Where(a => a.CheckOutAt > now && a.follower.sex == "女眾").CountAsync();
var guadanTotalCount = await _db.GuaDanOrder
.Where(a => a.IsDeleted == false)
.Where(a => a.IsCancel == false)
.CountAsync();
var guadanCurrentCount = await _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode != "403")
.Where(guest => guest.StatusCode != "404")
.Select(guest => guest.GuaDanOrderNo)
.Distinct()
.CountAsync();
var guadanPeopleTotal = await _db.GuaDanOrderGuest
.Where(a => a.IsDeleted == false)
.Where(guest => guest.StatusCode != "404")
.CountAsync();
var guadanPeopleMale = await _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode != "404")
.Where(a => a.IsDeleted == false && a.followers.sex == "男眾")
.CountAsync();
var guadanPeopleFemale = await _db.GuaDanOrderGuest
.Where(guest => guest.StatusCode != "404")
.Where(a => a.IsDeleted == false && a.followers.sex == "女眾")
.CountAsync();
var guadanPeopleCurrent = await _db.GuaDanOrderGuest
.Where(a => a.IsDeleted == false)
.Where(guest => guest.StatusCode != "404")
.Where(guest => guest.StatusCode != "403")
.Where( a => a.CheckOutAt >= now.Date)
.CountAsync();
var guadanPeopleCurrentMale = await _db.GuaDanOrderGuest
.Where(a => a.IsDeleted == false)
.Where(guest => guest.StatusCode != "404")
.Where(guest => guest.StatusCode != "403")
.Where(a => a.CheckOutAt >= now.Date && a.followers.sex == "男眾")
.CountAsync();
var guadanPeopleCurrentFemale = await _db.GuaDanOrderGuest
.Where(a => a.IsDeleted == false)
.Where(guest => guest.StatusCode != "404")
.Where(guest => guest.StatusCode != "403")
.Where(a => a.CheckOutAt >= now.Date && a.followers.sex == "女眾")
.CountAsync();
var result = new
{
roomStatistics = new
{
roomCount = roomCount,
emptyRoomCount = emptyRoomCount,
bedCount = bedCount,
maleBedCount = maleBedCount,
femaleBedCount = femaleBedCount,
emptyBedCount = bedCounts.male + bedCounts.female,
emptyMaleBedCount = bedCounts.male,
emptyFemaleBedCount = bedCounts.female
},
guadanStatistics = new
{
guadanTotalCount = guadanTotalCount, // 总挂单次数
@@ -61,7 +71,7 @@ public class guadanStatisticsController: ApiController
guadanPeopleTotal = guadanPeopleTotal, // 总挂单人数
guadanPeopleMale = guadanPeopleMale,
guadanPeopleFemale = guadanPeopleFemale,
guadanPeopleCurrent = guadanPeopleCurrent, // 当前挂单人数
guadanPeopleCurrent = guadanPeopleCurrent, // 已預約掛單人數
guadanPeopleCurrentMale = guadanPeopleCurrentMale,
guadanPeopleCurrentFemale = guadanPeopleCurrentFemale
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
/// <summary>
/// guadanStatisticsTable 的摘要描述
/// </summary>
///
[ezAuthorize]
public class guadanStatisticsTableController: ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public guadanStatisticsTableController()
{
//
// TODO: 在這裡新增建構函式邏輯
//
}
[HttpGet]
[Route("api/guadan/guadanstatisticstable/list")]
public IHttpActionResult Get([FromUri] DateTime? start, [FromUri] DateTime? end)
{
// 如果兩個都為空,設定 start = 今天end = 一個月後
if (!start.HasValue && !end.HasValue)
{
start = DateTime.Today;
end = DateTime.Today.AddMonths(1);
}
// 如果有 start 沒有 end就給 end 預設一個月後
if (start.HasValue && !end.HasValue)
{
end = start.Value.Date.AddMonths(1);
}
// 如果有 end 沒有 start就給 start 預設 end 的前一個月
if (!start.HasValue && end.HasValue)
{
start = end.Value.Date.AddMonths(-1);
}
// 最後確保只取 date 部分
var startDate = start.Value.Date;
var endDate = end.Value.Date;
// 查詢資料庫時就用 date 型別對應
var statistics = _db.RegionAndRoomAndBedSchedule
.Where(s => s.IsCancel == false)
.Where(s => s.ScheduleDate >= startDate && s.ScheduleDate <= endDate)
.GroupBy(s => s.ScheduleDate)
.Select(g => new
{
date = g.Key,
todaytotalbookers = g.Count(), // 该日期的总记录数
checkin = g.Key <= DateTime.Today
? g.Count(x => x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "402"
|| x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "403")
: 0,
checkinfemale = g.Key <= DateTime.Today
? g.Count(x => (x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "402"
|| x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "403")
&& x.GuaDanOrderGuest.followers.sex == "女眾")
: 0,
checkinmale = g.Key <= DateTime.Today
? g.Count(x => (x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "402"
|| x.GuaDanOrderGuest.RegionRoomBedStatus.Code == "403")
&& x.GuaDanOrderGuest.followers.sex == "男眾")
: 0,
bookfemale = g.Count(x => x.GuaDanOrderGuest.followers.sex == "女眾"),
bookmale = g.Count(x => x.GuaDanOrderGuest.followers.sex == "男眾")
})
.OrderBy(x => x.date)
.ToList();
var todayDate = DateTime.Today;
var bedcount = _db.RegionRoomBed
.Where(a => a.IsDeleted == false)
.Count();
var roomcount = _db.Room.Count();
var result = new
{
bedcount,
roomcount,
statistics,
};
return Ok(result);
}
}

View File

@@ -11,7 +11,7 @@ using System.Web.Http;
/// lianyouController 的摘要描述
/// </summary>
[ezAuthorize]
public class lianyouController: ApiController
public class lianyouController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
@@ -19,13 +19,34 @@ public class lianyouController: ApiController
[Route("api/lianyou/getfollowers")]
public async Task<IHttpActionResult> GetGuadanFollowers(int page, int pageSize, string searchName = null)
{
var qry = _db.followers.AsEnumerable();
if(searchName != null)
// IQueryable 可讓 EF 在資料庫層面執行過濾和投影
var qry = _db.followers.AsQueryable();
if (!string.IsNullOrEmpty(searchName))
{
qry = qry.Where(a => (a.u_name ?? "").Contains(searchName) || (a.phone ?? "").Contains(searchName));
}
var count = qry.Count();
var data = qry.OrderBy(a => a.f_number).ToPagedList(page, pageSize);
return Ok(new {data = data, count = count});
var count = await qry.CountAsync();
// 投影到只需要的欄位
var projected = qry
.OrderBy(a => a.f_number)
.Select(a => new
{
num = a.num,
u_name = a.u_name,
phone = a.phone,
sex = a.sex,
});
// 分頁
var data = projected
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList(); // 如果使用 EF Core 可用 ToListAsync()
return Ok(new { data = data, count = count });
}
}

View File

@@ -22,94 +22,14 @@ public class orderdetailController:ApiController
public IHttpActionResult GetList([FromBody] Model.ViewModel.pro_order q, int page, int pageSize = 10, string sortBy = "", bool sortDesc = false)
{
int activity_num = Convert.ToInt32(q.activity_num);
//現在的牌位預覽只會出現功德主,修改為所有人都會出現
//var aIDt = _db.actItems.AsEnumerable().Where(f => f.subject.Contains(q.actItemTxt.Trim())).Select(f => f.num);//品項
var OrderList = _db.pro_order.Where(u => u.activity_num == activity_num).Select(j => j.order_no).ToList();
var gdzOrderList = _db.pro_order_detail.Where(o => OrderList.Contains(o.order_no) && o.print_id.Contains("主")).Select(o => o.order_no).Distinct().ToList();
var qry = _db.pro_order.Where(u => gdzOrderList.Contains(u.order_no)).AsEnumerable();
if (!string.IsNullOrEmpty(q.order_no))
qry = qry.Where(o => o.order_no.Contains(q.order_no.Trim()));
if (!string.IsNullOrEmpty(q.keyin1))
qry = qry.Where(o => o.keyin1.Contains(q.keyin1));
if (q.f_num.HasValue && q.f_num > 0)
qry = qry.Where(o => o.f_num == q.f_num);
if (q.activity_num.HasValue && q.activity_num > 0)
qry = qry.Where(o => o.activity_num == q.activity_num);
if (q.up_time1.HasValue)
qry = qry.Where(o => o.up_time >= q.up_time1.Value);
if (q.up_time2.HasValue)
qry = qry.Where(o => o.up_time < Convert.ToDateTime(q.up_time2.Value).AddDays(1));
if (!string.IsNullOrEmpty(q.address))
qry = qry.Where(o => o.address.Contains(q.address.Trim()));
if (!string.IsNullOrEmpty(q.subject))
qry = qry.Where(o => o.activity_num.HasValue && o.activity.subject.Contains(q.subject?.Trim()));
if (!string.IsNullOrEmpty(q.u_name))
qry = qry.Where(o => o.f_num.HasValue && o.follower.u_name.Contains(q.u_name?.Trim()));
if (!string.IsNullOrEmpty(q.introducerTxt))
qry = qry.Where(o => o.introducer.HasValue && o.follower1.u_name.Contains(q.introducerTxt?.Trim()));
if (!string.IsNullOrEmpty(q.actItemTxt))
{
//qry = qry.Where(o => o.pro_order_detail.Where(f2 => f2.order_no == o.order_no && aIDt.ToArray().Contains(f2.actItem_num?.ToString())).Count() > 0);
// qry = qry.Where(o => o.pro_order_detail.Where(f2 => f2.order_no == o.order_no && aIDt.Any(x => x == f2.actItem_num)).Count() > 0);
qry = qry.Where(o => o.pro_order_detail.Where(f2 => f2.actItem_num.HasValue && f2.actItem.subject.Contains(q.actItemTxt?.Trim())).Count() > 0);
}
if (!string.IsNullOrEmpty(q.country))
qry = qry.Where(o => o.f_num != null && o.follower?.country == q.country);
if (!string.IsNullOrEmpty(q.country2))
{
if (q.country2 == "1")
{
qry = qry.Where(o => o.f_num != null && o.follower?.country == "158");
}
else if (q.country2 == "2")
{
qry = qry.Where(o => o.f_num != null && o.follower?.country != "158");
}
}
if (sortBy.Equals("order_no"))
{
if (sortDesc)
qry = qry.OrderByDescending(o => o.order_no);
else
qry = qry.OrderBy(o => o.order_no);
}
else if (sortBy.Equals("keyin1_txt"))
{
if (sortDesc)
qry = qry.OrderByDescending(o => o.keyin1);
else
qry = qry.OrderBy(o => o.keyin1);
}
else if (sortBy.Equals("up_time"))
{
if (sortDesc)
qry = qry.OrderByDescending(o => o.up_time);
else
qry = qry.OrderBy(o => o.up_time);
}
else if (sortBy.Equals("u_name"))
{
if (sortDesc)
qry = qry.OrderByDescending(o => o.follower?.u_name);
else
qry = qry.OrderBy(o => o.follower?.u_name);
}
else if (sortBy.Equals("subject"))
{
if (sortDesc)
qry = qry.OrderByDescending(o => o.activity?.subject);
else
qry = qry.OrderBy(o => o.activity?.subject);
}
else
qry = qry.OrderByDescending(o => o.reg_time);
//var OrderList = _db.pro_order.Where(u => u.activity_num == activity_num).Select(j => j.order_no).ToList();
//var gdzOrderList = _db.pro_order_detail.Where(o => OrderList.Contains(o.order_no) && o.print_id.Contains("主")).Select(o => o.order_no).Distinct().ToList();
//var qry = _db.pro_order.Where(u => gdzOrderList.Contains(u.order_no)).AsEnumerable();
var qry = _db.pro_order.Where( u => u.activity_num == activity_num).AsEnumerable();
qry = qry.OrderByDescending(o => o.reg_time);
var count = qry.Count(); //pageSize = count;//一次取回??
var ret = new
@@ -125,7 +45,11 @@ public class orderdetailController:ApiController
up_time = x.up_time,
keyin1_txt = Model.pro_order.keyin1_value_to_text(x.keyin1),
//detail = x.pro_order_detail.Where(u => u.printed_files != null)
detail = new { count = x.pro_order_detail.Where(u => u.actItem.act_bom.Count() == 0).Count(),
detail = new { count = x.pro_order_detail
.Where(u => (u.parent_num != null)
|| u.actItem.subject.Contains("牌")
|| !string.IsNullOrEmpty(u.f_num_tablet))
.Count(),
actItem = x.pro_order_detail.Where(u => u.printed_files != null).FirstOrDefault()?.print_id }
}),
count = count

View File

@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Http;
using System.Web.Routing;
@@ -11,7 +12,7 @@ using System.Web.Routing;
/// regionController 的摘要描述
/// </summary>
[ezAuthorize]
public class regionController: ApiController
public class regionController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public regionController()
@@ -39,8 +40,9 @@ public class regionController: ApiController
var startDate = filter.StartDate.Date;
var endDate = filter.EndDate.Date;
var query = _db.Region
var query = _db.Region//區域狀態是否啟用這裡只設置了過濾了有客房的區域,是否要過濾所有
.Where(r => !r.IsDeleted)
.Where(r => r.IsActive)
.Where(r => r.Room.Any());
if (filter.Gender != null)
@@ -67,6 +69,7 @@ public class regionController: ApiController
r.Uuid,
r.Name,
regionPath = r.Name,
isStop = !IsRegionAvailable(r.Uuid),
Room = r.Room
.Where(room => filter.Gender == null || room.Gender == filter.Gender)
.Where(room =>
@@ -95,17 +98,19 @@ public class regionController: ApiController
bed.Name,
bed.Gender,
bed.RoomUuid,
bed.StatusUuid,
bed.StatusCode,
bed.IsActive,
bed.IsDeleted,
canuse = bed.IsAvailableDuring(startDate, endDate, _db),
statusname = bed.RegionRoomBedStatus.Name,
schedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.IsCancel == false)
.Where(s => s.TargetUuid == bed.Uuid
&& s.IsDeleted == false
&& s.IsActive
&& (s.ScheduleDate == null
|| (s.ScheduleDate >= startDate && s.ScheduleDate <= endDate)))
|| (s.ScheduleDate >= startDate)))
.Where(s => s.GuaDanOrderGuest.StatusCode != "403" && s.GuaDanOrderGuest.StatusCode != "404")
.OrderBy(a => a.ScheduleDate)
.Select(s => new
{
s.Uuid,
@@ -113,7 +118,15 @@ public class regionController: ApiController
s.UseType,
s.Title,
s.Description,
s.GuaDanOrderNo
s.GuaDanOrderNo,
s.TargetUuid,
usename = _db.GuaDanOrderGuest
.Where(guest => guest.GuaDanOrderNo == s.GuaDanOrderNo)
.Where(guest => guest.BedUuid == s.TargetUuid)
.Select(guest => guest.followers.u_name)
.FirstOrDefault()
})
.ToList()
}),
@@ -138,6 +151,30 @@ public class regionController: ApiController
Summary = summary,
});
}
public bool IsRegionAvailable(Guid regionUuid)
{
var current = _db.Region.FirstOrDefault(r => r.Uuid == regionUuid);
while (current != null)
{
// 當前區域不可用就直接返回 false
if (!current.IsActive || current.IsDeleted)
{
return false;
}
// 沒有父區域了,說明一路上都可用
if (!current.ParentUuid.HasValue)
{
return true;
}
// 繼續往父區域走
current = _db.Region.FirstOrDefault(r => r.Uuid == current.ParentUuid.Value);
}
// 沒查到(極端情況,比如資料庫被改了)
return false;
}
/// <summary>
/// 遞迴生成區域完整路徑
@@ -183,7 +220,8 @@ public class regionController: ApiController
IsActive = region.IsActive,
RoomCount = region.RoomCount,
BedDto = new List<BedDto>(),
Rooms = region.Room.Select(a => new RoomDto {
Rooms = region.Room.Select(a => new RoomDto
{
Uuid = a.Uuid,
Name = a.Name,
RegionUuid = a.RegionUuid,
@@ -194,13 +232,13 @@ public class regionController: ApiController
{
Uuid = c.Uuid,
name = c.Name,
roomUuid = c.RoomUuid,
roomUuid = c.RoomUuid,
isactive = c.IsActive,
statusuuid = c.StatusUuid,
statuscode = c.StatusCode,
Gender = c.Gender,
}).ToList(),
}).ToList(),
Children = allRegions
.Where(r => r.ParentUuid == region.Uuid)
@@ -216,25 +254,25 @@ public class regionController: ApiController
{
var allRegions = _db.Region.ToList();
// 根
// 根
var rootRegions = allRegions
.Where(r => r.ParentUuid == null)
.OrderBy(r => r.SortOrder)
.ToList();
// 生成树并按性别过滤
// 生成樹並按性別過濾
var tree = rootRegions
.Select(r => BuildRegionDtoByGender(r, allRegions, request.IsMale))
.Where(r => r != null) // 去掉有房间的区
.Where(r => r != null) // 去掉有房間的區
.ToList();
return Ok(tree);
}
// 根据性别过滤房间的 BuildRegionDto
// 根據性別過濾房間的 BuildRegionDto
private RegionDto BuildRegionDtoByGender(Region region, List<Region> allRegions, bool? gender)
{
// 过滤房间按性
// 過濾房間按性
var rooms = region.Room?
.Where(a => !gender.HasValue || a.Gender == gender.Value)
.Select(a => new RoomDto
@@ -251,19 +289,19 @@ public class regionController: ApiController
name = c.Name,
roomUuid = c.RoomUuid,
isactive = c.IsActive,
statusuuid = c.StatusUuid
statuscode = c.StatusCode,
}).ToList()
})
.ToList();
// 递归生成子
// 遞迴生成子
var children = allRegions
.Where(r => r.ParentUuid == region.Uuid)
.Select(child => BuildRegionDtoByGender(child, allRegions, gender))
.Where(c => c != null) // 去掉有房的子
.Where(c => c != null) // 去掉有房的子
.ToList();
// 如果这个区域既有房间也没有子域,返回 null
// 如果這個區域既有房間也沒有子域,返回 null
if (!rooms.Any() && !children.Any())
return null;
@@ -281,14 +319,14 @@ public class regionController: ApiController
BedDto = new List<BedDto>(),
Children = children,
Gender = region.Gender,
};
}
// 求模型
// 求模型
public class GenderRequest
{
public bool? IsMale { get; set; } // true = 男, false = 女, null = 不过滤
public bool? IsMale { get; set; } // true = 男, false = 女, null = 不過濾
}
public class RoomDto
@@ -303,11 +341,12 @@ public class regionController: ApiController
public Nullable<System.DateTime> UpdatedAt { get; set; }
public List<BedDto> beds { get; set; }
}
public class BedDto {
public class BedDto
{
public Guid Uuid { get; set; }
public Guid? roomUuid { get; set; }
public string name { get; set; }
public Guid? statusuuid { get; set; }
public string statuscode { get; set; }
public bool isactive { get; set; }
public bool Gender { get; set; }
}
@@ -324,13 +363,13 @@ public class regionController: ApiController
public bool IsActive { get; set; } = true;
public int? RoomCount { get; set; }
public List<RoomDto> Rooms { get; set; }
public List<BedDto> BedDto { get; set; }
public bool? Gender { get; set; }
public List<BedDto> BedDto { get; set; }
public bool? Gender { get; set; }
}
[HttpPost]
[Route("api/region/create")]
public IHttpActionResult createRegion([FromBody] RegionDto dto)
public IHttpActionResult createRegion([FromBody] RegionDto dto)
{
if (string.IsNullOrWhiteSpace(dto.Name))
return BadRequest("區域名稱為必填");
@@ -361,6 +400,28 @@ public class regionController: ApiController
var region = _db.Region.FirstOrDefault(r => r.Uuid == dto.Uuid);
if (region == null)
return NotFound();
if (dto.RoomCount < region.Room.Count())
{
return BadRequest("客房數量小於已存在的客房數量");
}
if (dto.IsActive == false)
{
var regionIds = GetAllRegionIds(region.Uuid);
var hasPendingBeds = _db.RegionRoomBed
.Where(b => regionIds.Contains(b.Room.RegionUuid))
.SelectMany(b => b.GuaDanOrderGuest)
.Any(g => !g.IsDeleted &&
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 預約中
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN)); // 已入住
if (hasPendingBeds)
{
return Content(HttpStatusCode.BadRequest, new
{
code = "BED_IS_USED",
message = "該區域有床位正在掛單中,請先處理"
});
}
}
region.Name = dto.Name;
region.Description = dto.Description;
region.SortOrder = dto.SortOrder;
@@ -399,6 +460,25 @@ public class regionController: ApiController
return Ok(new { message = "刪除成功" });
}
public List<Guid> GetAllRegionIds(Guid regionUuid)
{
var regionIds = new List<Guid> { regionUuid };
var children = _db.Region
.Where(r => r.ParentUuid == regionUuid)
.Select(r => r.Uuid)
.ToList();
foreach (var childId in children)
{
regionIds.AddRange(GetAllRegionIds(childId));
}
return regionIds;
}
// 遞迴刪除子節點
private void DeleteRegionRecursive(Region region)
{
@@ -412,7 +492,7 @@ public class regionController: ApiController
[HttpPost]
[Route("api/region/getRegionType")]
public IHttpActionResult getRegionType()
{
{
var data = _db.RegionType.Where(a => a.IsActive == true).ToList();
return Ok(data);
}
@@ -421,7 +501,7 @@ public class regionController: ApiController
[Route("api/region/regionwithroom")]
public IHttpActionResult GetRegionWithRoom()
{
//返回有房的region
//返回有房的region
var data = _db.Region.Where(a => a.Room.Count() > 0)
.Select(r => new
{
@@ -431,7 +511,9 @@ public class regionController: ApiController
r.Gender,
rooms = r.Room.Select(room => new
{
room.Uuid, room.Name, room.RegionUuid
room.Uuid,
room.Name,
room.RegionUuid
}).ToList()
}).ToList();
return Ok(data);
@@ -440,7 +522,7 @@ public class regionController: ApiController
[Route("api/room/roomwithbed")]
public IHttpActionResult GetRoomWithBed(Guid? RegionUuid = null)
{
//取所有有床位的房
//取所有有床位的房
var query = _db.Room
.Select(r => new
{

View File

@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
@@ -40,24 +41,23 @@ public class regionRoomBedController : ApiController
.Where(a => a.RoomUuid == roomUuid)
.ToList();
// 取出所有相关排程
var schedules = _db.RegionAndRoomAndBedSchedule
.Where(b => b.IsDeleted == false && b.IsActive)
.ToList();
StartTime = StartTime.Date;
EndTime = EndTime?.Date;
var data = beds.Select(a =>
{
// 在内存中处理日期比较
var bedSchedules = schedules
var bedSchedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderGuest.StatusCode != "403")
.Where(s => s.GuaDanOrderGuest.StatusCode != "404")
.Where(b => b.TargetUuid == a.Uuid
&& (b.ScheduleDate == null // 长期占用
|| (b.ScheduleDate >= StartTime.Date && b.ScheduleDate <= EndTime.Value.Date)))
&& (b.ScheduleDate == null
|| (b.ScheduleDate >= StartTime && b.ScheduleDate < EndTime)))
.ToList()
.Select(c => new
{
c.Uuid,
c.Description,
c.IsDeleted,
c.IsActive,
c.GuaDanOrderNo,
c.UseType,
c.Title,
@@ -67,22 +67,51 @@ public class regionRoomBedController : ApiController
.ToList();
bool canUsed = !bedSchedules.Any();
bool bedIsStop = IsBedStopped(a);
return new
{
a.Uuid,
a.Name,
a.Gender,
a.IsActive,
a.StatusUuid,
a.StatusCode,
a.RoomUuid,
canUsed,
bedIsStop,
schedule = bedSchedules
};
});
return Ok(data);
}
public bool IsBedStopped(RegionRoomBed bed)
{
// 1⃣ 床位本身不可用
if (!bed.IsActive || bed.IsDeleted)
return true;
// 2⃣ 所属房间不可用
var room = bed.Room;
if (room == null || !room.IsActive.Value || room.IsDeleted)
return true;
// 3⃣ 所属区域不可用
var region = room.Region;
while (region != null)
{
if (!region.IsActive || region.IsDeleted)
return true; // 有任意一级区域不可用就返回 true
if (!region.ParentUuid.HasValue)
break; // 到顶层了
region = _db.Region.FirstOrDefault(r => r.Uuid == region.ParentUuid.Value);
}
// 4⃣ 全部检查通过 → 床位可用
return false;
}
[HttpPost]
[Route("api/region/bed/create")]
@@ -93,7 +122,7 @@ public class regionRoomBedController : ApiController
{
return BadRequest("當前客房不存在");
}
if(room.Gender != bed.Gender)
if (room.Gender != bed.Gender)
{
return BadRequest("床為性別和房間性別必須一致");
}
@@ -105,7 +134,7 @@ public class regionRoomBedController : ApiController
{
Name = bed.Name,
RoomUuid = bed.RoomUuid,
StatusUuid = bed.StatusUuid,
StatusCode = bed.StatusCode,
IsActive = bed.IsActive,
Gender = bed.Gender,
Uuid = Guid.NewGuid(),
@@ -114,10 +143,11 @@ public class regionRoomBedController : ApiController
_db.SaveChanges();
//創建床位
return Ok(new {
return Ok(new
{
uuid = regionBed.Uuid,
roomUuid = regionBed.RoomUuid,
statusuuid = regionBed.StatusUuid,
statuscode = regionBed.StatusCode,
isactive = regionBed.IsActive,
gender = regionBed.Gender,
name = regionBed.Name,
@@ -146,7 +176,21 @@ public class regionRoomBedController : ApiController
{
return BadRequest("床為性別和房間性別必須一致");
}
oldBed.StatusUuid = bed.StatusUuid;
if (bed.IsActive == false)
{
var hasPendingBeds = oldBed.GuaDanOrderGuest.Any(g => !g.IsDeleted &&
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 预约中
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN));
if (hasPendingBeds)
{
return Content(HttpStatusCode.BadRequest, new
{
code = "BED_IS_USED",
message = "該床位正在掛單中,請先處理"
});
}
}
oldBed.StatusCode = bed.StatusCode;
oldBed.IsActive = bed.IsActive;
oldBed.Name = bed.Name;
oldBed.Gender = bed.Gender;
@@ -157,25 +201,75 @@ public class regionRoomBedController : ApiController
}
[HttpPost]
[Route("api/region/bed/delete")]
public IHttpActionResult delete([FromUri] Guid uuid)
public IHttpActionResult Delete([FromUri] Guid uuid)
{
var bed = _db.RegionRoomBed.Find(uuid);
if (bed == null)
{
return BadRequest("未找到床位");
}
_db.RegionRoomBed.Remove(bed);
_db.SaveChanges();
return Ok(new { message = "刪除成功" });
try
{
_db.RegionRoomBed.Remove(bed);
_db.SaveChanges();
return Ok(new { message = "刪除成功" });
}
catch (System.Data.Entity.Infrastructure.DbUpdateException ex)
{
// 判斷是否為外鍵關聯錯誤
if (ex.InnerException?.InnerException is System.Data.SqlClient.SqlException sqlEx &&
(sqlEx.Number == 547)) // 547 = SQL Server 外鍵違反錯誤碼
{
return BadRequest("刪除失敗:該床位已被使用或存在關聯資料,無法刪除。");
}
return InternalServerError(ex); // 其他資料庫錯誤
}
catch (Exception ex)
{
return InternalServerError(ex); // 其他未預期錯誤
}
}
[HttpGet]
[Route("api/region/bed/getavailablebedcountbytime")]
public async Task<IHttpActionResult> GetCanUseBedCountByTime(DateTime startTime, DateTime endTime)
{
//获取某个时间段内可用床位数量
var counts = await RegionAndRoomAndBedSchedule.GetAvailableBedCountsAsync(_db, startTime, endTime);
return Ok(counts);
var start = startTime.Date;
var end = endTime.Date;
// 找出所有在日期範圍內被占用的床位
var busyBedUuids = await _db.RegionAndRoomAndBedSchedule
.Where(s => s.GuaDanOrderGuest.StatusCode != "403")
.Where(s => s.GuaDanOrderGuest.StatusCode != "404")
.Where(a => a.IsCancel == false)
.Where(s => s.IsDeleted == false
&& (s.ScheduleDate == null // 長期占用
|| (s.ScheduleDate >= start && s.ScheduleDate < end)))
.Select(s => s.TargetUuid)
.Distinct()
.ToListAsync();
// 可用床位 = 所有床位 - 忙碌床位
var availableBeds = _db.RegionRoomBed
.Where(b => b.IsActive)
.Where(b => !busyBedUuids.Contains(b.Uuid));
var result = await availableBeds
.GroupBy(b => b.Gender)
.Select(g => new
{
Gender = g.Key,
Count = g.Count()
})
.ToListAsync();
var male = result.Where(r => r.Gender == true).Select(r => r.Count).FirstOrDefault();
var female = result.Where(r => r.Gender == false).Select(r => r.Count).FirstOrDefault();
return Ok(new {male, female});
}
[HttpPost]
@@ -260,7 +354,7 @@ public class regionRoomBedController : ApiController
}
}
if(index < followers.Count)
if (index < followers.Count)
{
isAllallocation = false;
return;
@@ -313,7 +407,7 @@ public class regionRoomBedController : ApiController
{
// 先拉出床位相關排程到內存,避免 EF 不支援 .Date
var schedules = _db.RegionAndRoomAndBedSchedule
.Where(s => s.IsDeleted == false && s.IsActive && s.TargetUuid == req.bedUuid)
.Where(s => s.IsDeleted == false && !s.IsCancel && s.TargetUuid == req.bedUuid)
.ToList();
bool conflictExists = schedules.Any(s =>
@@ -349,13 +443,14 @@ public class regionRoomBedController : ApiController
RoomUuid = roomUuid,
CheckInAt = allocationStart,
CheckOutAt = allocationEnd,
StatusCode = "401",
};
_db.GuaDanOrderGuest.Add(guest);
// 新增每日排程
if (allocationEnd.HasValue)
{
for (var date = allocationStart; date <= allocationEnd.Value; date = date.AddDays(1))
for (var date = allocationStart; date < allocationEnd.Value; date = date.AddDays(1))
{
var newSchedule = new RegionAndRoomAndBedSchedule
{
@@ -364,10 +459,11 @@ public class regionRoomBedController : ApiController
ScheduleDate = date,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
IsDeleted = false,
IsActive = true,
CreatedBy = "系统自动分配",
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo
GuaDanOrderNo = guest.GuaDanOrderNo,
Title = "掛單",
GuaDanOrderGuestUuid = guest.Uuid,
};
_db.RegionAndRoomAndBedSchedule.Add(newSchedule);
}
@@ -382,10 +478,11 @@ public class regionRoomBedController : ApiController
ScheduleDate = null,
UseType = (int)RegionAndRoomAndBedSchedule.SchedulePurpose.Bed_Reservation,
IsDeleted = false,
IsActive = true,
CreatedBy = "系统自动分配",
CreatedAt = DateTime.Now,
GuaDanOrderNo = guest.GuaDanOrderNo
GuaDanOrderNo = guest.GuaDanOrderNo,
Title = "掛單",
GuaDanOrderGuestUuid = guest.Uuid,
};
_db.RegionAndRoomAndBedSchedule.Add(newSchedule);
}
@@ -406,7 +503,7 @@ public class regionRoomBedController : ApiController
private bool BedIsCanUsed(RegionRoomBed bed, DateTime? StartTime, DateTime? EndTime)
{
if(!bed.IsActive)
if (!bed.IsActive)
{
return false;
}

View File

@@ -9,7 +9,7 @@ using System.Web.Http;
/// regionRoomBedStatusController 的摘要描述
/// </summary>
[ezAuthorize]
public class regionRoomBedStatusController: ApiController
public class regionRoomBedStatusController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public regionRoomBedStatusController()
@@ -34,7 +34,6 @@ public class regionRoomBedStatusController: ApiController
s.Code,
s.Name,
s.Description,
s.Uuid,
s.CategoryName
})
.ToList();
@@ -53,9 +52,9 @@ public class regionRoomBedStatusController: ApiController
}
[HttpPost]
[Route("api/region/bed/status/delete")]
public IHttpActionResult DeleteBedStatus([FromUri]Guid id)
public IHttpActionResult DeleteBedStatus([FromUri] string code)
{
var rt = _db.RegionRoomBedStatus.Find(id);
var rt = _db.RegionRoomBedStatus.Find(code);
if (rt == null)
{
return NotFound();

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Data.Entity;
using System.Drawing;
using System.Linq;
using System.Net;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using System.Web;
@@ -14,7 +15,7 @@ using static regionController;
/// regionRoomController 的摘要描述
/// </summary>
[ezAuthorize]
public class regionRoomController: ApiController
public class regionRoomController : ApiController
{
private Model.ezEntities _db = new Model.ezEntities();
public regionRoomController()
@@ -38,6 +39,15 @@ public class regionRoomController: ApiController
{
return BadRequest("請輸入床位數量");
}
var region = _db.Region.Find(room.RegionUuid);
if(region == null)
{
return BadRequest("未找到客房所屬的區域");
}
if(region.Room.Count() >= region.RoomCount)
{
return BadRequest("當前區域客房數量已經達到上限");
}
var newRoom = new Room();
newRoom.Name = room.Name;
newRoom.RegionUuid = room.RegionUuid;
@@ -62,7 +72,7 @@ public class regionRoomController: ApiController
name = c.Name,
roomUuid = c.RoomUuid,
isactive = c.IsActive,
statusuuid = c.StatusUuid,
statuscode = c.StatusCode,
}).ToList(),
};
@@ -86,10 +96,27 @@ public class regionRoomController: ApiController
// 如果有不符合性別的床位,不能繼續操作
return BadRequest("房間中已有與房間性別不符的床位,無法操作");
}
if(!room.BedCount.HasValue)
if (!room.BedCount.HasValue)
{
return BadRequest("請輸入床位數量");
}
if (room.IsActive == false)
{
var hasPendingBeds = oldRoom.RegionRoomBed
.SelectMany(b => b.GuaDanOrderGuest)
.Any(g => !g.IsDeleted &&
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 预约中
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN)); // 已入住
if (hasPendingBeds)
{
return Content(HttpStatusCode.BadRequest, new
{
code = "BED_IS_USED",
message = "該房间有床位正在掛單中,請先處理"
});
}
}
oldRoom.Name = room.Name;
oldRoom.BedCount = room.BedCount;
oldRoom.Gender = room.Gender;
@@ -97,21 +124,49 @@ public class regionRoomController: ApiController
oldRoom.IsActive = room.IsActive;
oldRoom.RegionUuid = room.RegionUuid;
await _db.SaveChangesAsync();
return Ok(new { message = "更新成功"});
return Ok(new { message = "更新成功" });
}
[HttpPost]
[Route("api/region/room/delete")]
public async Task<IHttpActionResult> deleteRoom([FromBody] Room rm)
{
var room = await _db.Room.FindAsync(rm.Uuid);
if (room == null) return BadRequest("房間不存在");
using (var transaction = _db.Database.BeginTransaction())
{
try
{
var room = await _db.Room.FindAsync(rm.Uuid);
if (room == null) return BadRequest("房間不存在");
var beds = _db.RegionRoomBed.Where(b => b.RoomUuid == room.Uuid);
_db.RegionRoomBed.RemoveRange(beds);
var beds = _db.RegionRoomBed.Where(b => b.RoomUuid == room.Uuid);
_db.RegionRoomBed.RemoveRange(beds);
_db.Room.Remove(room);
await _db.SaveChangesAsync();
return Ok(new { message = "刪除成功" });
_db.Room.Remove(room);
await _db.SaveChangesAsync();
transaction.Commit();
return Ok(new { message = "刪除成功" });
}
catch (System.Data.Entity.Infrastructure.DbUpdateException ex)
{
transaction.Rollback();
// 判断是否为外键约束错误
if (ex.InnerException?.InnerException is System.Data.SqlClient.SqlException sqlEx &&
sqlEx.Number == 547) // 547 = 外键冲突
{
return BadRequest("房間或床位正在被使用,不能刪除");
}
return InternalServerError(ex);
}
catch (Exception ex)
{
transaction.Rollback();
return InternalServerError(ex);
}
}
}
}

View File

@@ -28,7 +28,7 @@
:server-items-length="activity_statistics.totalItems"
:items="activity_statistics.items">
<template #item.detail_btn="{item}">
<a :href="'activity.aspx?num='+item.id" class="btn btn-outline-secondary btn-sm" target="_blank">詳細統計</a>
<a :href="'statistics.aspx?num='+item.id + '&activity_name=' + encodeURIComponent(item.activity_name)" class="btn btn-outline-secondary btn-sm" target="_blank">詳細統計</a>
</template>
<template #item.duetime="{item}">
{{item.startdate|timeString("YYYY/MM/DD")}}-{{item.enddate|timeString("YYYY/MM/DD")}}

View File

@@ -0,0 +1,186 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="statistics.aspx.cs" Inherits="admin_activity_statistics_statistics" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<div class="ms-5">
<span>
活動名稱: {{ activity_name }}
</span>
</div>
<div class="me-5">
<button class="btn btn-primary" type="button">匯出表格資料</button>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div id="content" class="container-fluid">
<div class="card shadow-sm my-2" id="sec2">
<div class="card-header py-0">
<nav class="navbar py-0">
<div class="nav nav-tabs">
<button class="nav-link active" id="sec2-tab1" data-bs-toggle="tab" data-bs-target="#sec2-page1"
type="button" role="tab" aria-controls="home" aria-selected="true">
活動統計總表</button>
<button class="nav-link" id="sec2-tab2" data-bs-toggle="tab" data-bs-target="#sec2-page2"
type="button" role="tab" aria-controls="profile" aria-selected="false">
活動統計數量明細 </button>
<button class="nav-link" id="sec2-tab3" data-bs-toggle="tab" data-bs-target="#sec2-page3"
type="button" role="tab" aria-controls="profile" aria-selected="false">
活動統計報名人明細 </button>
<button class="nav-link" id="sec2-tab4" data-bs-toggle="tab" data-bs-target="#sec2-page4"
type="button" role="tab" aria-controls="profile" aria-selected="false">
活動牌位明細</button>
</div>
</nav>
</div>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active noedit p-4" id="sec2-page1" role="tabpanel" aria-labelledby="sec2-tab1">
<h5 class="mb-4"> {{ activity_name }}(截至 {{summaryStats?.reportDate }}</h5>
<!-- 基本資訊 + 功德金 統計 -->
<table class="table table-bordered table-sm w-auto mb-4">
<tbody>
<!-- 基本資訊 -->
<tr class="table-primary">
<th colspan="4">基本資訊</th>
</tr>
<tr>
<th>類別</th>
<td>報名人數</td>
<td>男眾人數</td>
<td>女眾人數</td>
</tr>
<tr>
<th>數量</th>
<td>{{ summaryStats?.totalApplicants }}</td>
<td>{{ summaryStats?.maleApplicants }}</td>
<td>{{ summaryStats?.femaleApplicants }}</td>
</tr>
</tbody>
</table>
<table class="table table-bordered table-sm w-auto mb-4">
<tbody>
<!-- 功德金 -->
<tr class="table-primary">
<th colspan="4">功德金統計</th>
</tr>
<tr>
<th>類別</th>
<td>總功德金</td>
<td>已收</td>
<td>未收</td>
</tr>
<tr>
<th>金額</th>
<td>{{ summaryStats?.donation.total }}</td>
<td>{{ summaryStats?.donation.received }}</td>
<td>{{ summaryStats?.donation.unreceived }}</td>
</tr>
</tbody>
</table>
<!-- 功德項目 -->
<table class="table table-bordered table-sm w-auto mb-4">
<tbody>
<tr class="table-primary">
<th colspan="100%">功德項目</th>
</tr>
<tr>
<th>功德項目</th>
<td v-for="(item, index) in summaryStats?.items" :key="'name-' + index">
{{ item.name }}
</td>
</tr>
<tr>
<th>人數</th>
<td v-for="(item, index) in summaryStats?.items" :key="'count-' + index">
{{ item.count }}
</td>
</tr>
</tbody>
</table>
<!-- 牌位數量 -->
<table class="table table-bordered table-sm w-auto">
<tbody>
<tr class="table-primary">
<th colspan="100%">牌位數量</th>
</tr>
<tr>
<th>牌位名稱</th>
<td v-for="(plaque, index) in summaryStats?.plaques" :key="'plaque-name-' + index">
{{ plaque.name }}
</td>
</tr>
<tr>
<th>數量</th>
<td v-for="(plaque, index) in summaryStats?.plaques" :key="'plaque-count-' + index">
{{ plaque.count }}
</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="sec2-page2" role="tabpanel" aria-labelledby="sec2-tab2">
sec2-page2
</div>
<div class="tab-pane fade" id="sec2-page3" role="tabpanel" aria-labelledby="sec2-tab3">
sec2-page3
</div>
<div class="tab-pane fade" id="sec2-page4" role="tabpanel" aria-labelledby="sec2-tab4">
sec2-page4
</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 {
activity_num: '<%= Request["num"] %>',
activity_name: '<%= Request["activity_name"] %>',
summaryStats: null, //匯總統計(總覽)
quantityDetails: [], // 數量明細(分類數量)
applicantList: [], // 報名人清單
plaqueList: [], //牌位清單
loadingSummary: false,
}
},
methods: {
fetchSummaryStats() {
//獲取匯總明細
this.loadingSummary = true;
axios.get(HTTP_HOST + 'api/activity/statistics/summary',
{
params: {
activity_num: this.activity_num
}
}
)
.then(res => {
this.summaryStats = res.data;
})
.catch(err => {
console.error(err);
})
.finally(() => {
this.loadingSummary = false;
});
}
},
mounted() {
this.fetchSummaryStats(); // 頁面載入時就獲取
}
})
</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_activity_statistics_statistics : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,908 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletarea_index" %>
<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" type="button" @click="showNewtAreadialogMethod">
<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>
<button type="button" class="btn btn-primary" @click="toggleAreaData">
{{ showAreaDataFlag ? '隱藏區域資料' : '顯示區域資料' }}
</button>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<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="area in ancestral_tablet_areas" :key="area.AreaId">
<region-item
:item="area"
:selected-id="currentSelectAreaId"
@select-area="selectAreaMethod"
: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-9" v-if="currentSelectArea && showAreaDataFlag">
<div class="card shadow-sm my-2"style="position: sticky; top: 20px;">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
<div>
<span class="fw-bold">
{{ ' ' + currentSelectArea?.areaName + ' ' }}
</span>
<span>
資料
</span>
</div>
<div>
<button class="btn btn-primary" type="button" @click="showEidtAreadialogMethod">
編輯
</button>
</div>
</div>
<div class="card-body">
<div class="container">
<div class="row">
<div class="col-12 col-sm-6">
<label>區域名稱</label>
<input v-model="currentSelectArea.areaName" type="text" class="form-control" readonly/>
</div>
<div class="col-12 col-sm-6">
<label>區域編號</label>
<input v-model="currentSelectArea.areaCode" type="text" class="form-control" readonly/>
</div>
<div class="col-12 col-sm-6">
<label>上層區域(可選)</label>
<input v-model="currentSelectArea.parentAreaId" type="text" class="form-control" readonly />
</div>
<div class="col-12 col-sm-6">
<label>區域類型(可空)</label>
<input v-model="currentSelectArea.areaType" type="text" class="form-control" readonly/>
</div>
<div class="col-12 col-sm-6">
<label>價格</label>
<input v-model="currentSelectArea.price" class="form-control" readonly/>
</div>
<div class="col-12 col-sm-6">
<label>排序</label>
<input v-model="currentSelectArea.sortOrder" class="form-control" readonly/>
</div>
<div class="col-12 col-sm-6">
<div style="display: flex; align-items: center; height: 100%; margin-top: 8px;">
<input
v-model="currentSelectArea.isDisabled"
type="checkbox"
disabled
id="disabledToggle"
style="width: 20px; height: 20px; margin-right: 8px; cursor: pointer;"
/>
<label
for="disabledToggle"
style="font-weight: bold; font-size: 14px; color: #333; margin: 0;"
>
是否停用
</label>
</div>
</div>
<div class="col-12 mt-3">
<label>描述</label>
<textarea v-model="currentSelectArea.description" rows="3" class="form-control" readonly></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-4 col-lg-9" v-if="currentSelectArea">
<div class="card shadow-sm my-2" style="flex: 1 1 auto; min-height: 0; overflow: auto;">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;background-color: #ffc107;">
<div>
{{currentSelectArea.areaName + ' - ' + '神主牌位置'}}
</div>
<div>
<v-btn color="primary" @click="openNewPositionDialogMethod">批次新增神主牌位置</v-btn>
</div>
</div>
<div class="card-body">
<div class="grid-container">
<div
v-for="pos in positions"
:key="pos.positionCode"
class="grid-item"
:class="'status-' + pos.statusCode"
:style="{ gridRow: pos.rowNo, gridColumn: pos.columnNo }"
>
<div class="position-name">{{ pos.positionName }}</div>
<div class="position-content">
<!-- 這裡可以放更多資訊,比如價格或狀態 -->
<!-- 例如:價格: {{ pos.price }} -->
<v-btn small @click="editPositionMethod(pos)">修改</v-btn>
<v-btn small @click="deletePositionMethod(pos)">刪除</v-btn>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 新增區域彈出視窗 -->
<div>
<v-dialog v-model="showNewAreadialogFlag" max-width="1200px">
<v-card
style="min-height: 50vh; max-height: 80vh; overflow-y: auto;"
>
<v-card-title>
<span class="headline">新增區域</span>
</v-card-title>
<v-card-text>
<div class="container">
<div class="row">
<div class="col-12 col-sm-6">
<label>區域名稱</label>
<input v-model="newArea.areaName" type="text" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>區域編號</label>
<input v-model="newArea.areaCode" type="text" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>上層區域(可選)</label>
<select v-model="newArea.parentAreaId" class="form-control" >
<option value="">請選擇</option>
<!-- 手動添加選項 -->
<option v-for="r in flatAreas"
:value="r.areaId"
:disabled="disabledParentOptions.includes(r.areaId)">{{ r.areaName }}
</option>
</select>
</div>
<div class="col-12 col-sm-6">
<label>區域類型(可空)</label>
<input type="text" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>價格</label>
<input v-model="newArea.price" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>排序</label>
<input v-model="newArea.sortOrder" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<div style="display: flex; align-items: center; height: 100%; margin-top: 8px;">
<input
v-model="newArea.isDisabled"
type="checkbox"
style="width: 20px; height: 20px; margin-right: 8px; cursor: pointer;"
/>
<label
for="disabledToggle"
style="font-weight: bold; font-size: 14px; color: #333; margin: 0;"
>
是否停用
</label>
</div>
</div>
<div class="col-12 mt-3">
<label>描述</label>
<textarea v-model="newArea.description" rows="3" class="form-control" ></textarea>
</div>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="closeNewAreadialogMethod">取消</v-btn>
<v-btn color="primary" @click="createNewAreaMethod">確定新增</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<!-- 編輯區域彈出視窗 -->
<div>
<v-dialog v-model="showEidtAreadialogFlag" max-width="1200px">
<v-card
style="min-height: 50vh; max-height: 80vh; overflow-y: auto;"
>
<v-card-title>
<span class="headline">編輯區域</span>
</v-card-title>
<v-card-text>
<div class="container">
<div class="row">
<div class="col-12 col-sm-6">
<label>區域名稱</label>
<input v-model="editArea.areaName" type="text" class="form-control" required />
</div>
<div class="col-12 col-sm-6">
<label>區域編號</label>
<input v-model="editArea.areaCode" type="text" class="form-control" required />
</div>
<div class="col-12 col-sm-6">
<label>上層區域(可選)</label>
<select v-model="editArea.parentAreaId" class="form-control" >
<option value="">請選擇</option>
<!-- 手動添加選項 -->
<option v-for="r in flatAreas"
:value="r.areaId"
:disabled="disabledParentOptions.includes(r.areaId)">{{ r.areaName }}
</option>
</select>
</div>
<div class="col-12 col-sm-6">
<label>區域類型(可空)</label>
<input v-model="editArea.areaType" type="text" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>價格</label>
<input v-model="editArea.price" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<label>排序</label>
<input v-model="editArea.sortOrder" class="form-control" />
</div>
<div class="col-12 col-sm-6">
<div style="display: flex; align-items: center; height: 100%; margin-top: 8px;">
<input
v-model="editArea.isDisabled"
type="checkbox"
id="disabledToggle1"
style="width: 20px; height: 20px; margin-right: 8px; cursor: pointer;"
/>
<label
for="disabledToggle"
style="font-weight: bold; font-size: 14px; color: #333; margin: 0;"
>
是否停用
</label>
</div>
</div>
<div class="col-12 mt-3">
<label>描述</label>
<textarea v-model="editArea.description" rows="3" class="form-control" ></textarea>
</div>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="closeEidtAreadialogMethod">取消</v-btn>
<v-btn color="primary" @click="editAreaMethod">送出修改</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<!-- 批次新增神主牌彈出視窗 -->
<div>
<!-- 彈出視窗組件 -->
<v-dialog v-model="showNewPositionDialogFlag" max-width="800px">
<v-card>
<v-card-title>
批次新增神主牌位置
<v-spacer></v-spacer>
<v-btn icon @click="closeNewPositionDialogMethod">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div class="card p-3 mt-4">
<div class="row mb-2">
<div class="col">
<label>起始行</label>
<input v-model.number="batchPositionForm.startRow" type="number" class="form-control" />
</div>
<div class="col">
<label>起始列</label>
<input v-model.number="batchPositionForm.startCol" type="number" class="form-control" />
</div>
<div class="col">
<label>行數</label>
<input v-model.number="batchPositionForm.rows" type="number" class="form-control" />
</div>
<div class="col">
<label>列數</label>
<input v-model.number="batchPositionForm.cols" type="number" class="form-control" />
</div>
</div>
<div class="row mb-2">
<div class="col">
<label>價格</label>
<input v-model.number="batchPositionForm.price" type="number" class="form-control" />
</div>
<div class="col">
<label>狀態</label>
<select v-model="batchPositionForm.status" class="form-control">
<option v-for="s in statusList" :key="s.statusCode" :value="s.statusCode">
{{ s.statusName }}
</option>
</select>
</div>
<div class="col">
<label>Name模板</label>
<input v-model="batchPositionForm.nameTemplate" class="form-control" placeholder="如:神位編號{code}" />
</div>
</div>
<div class="row mb-3">
<div class="col">
<label>Code起始值</label>
<input v-model.number="batchPositionForm.startCode" type="number" class="form-control" />
</div>
<div class="col">
<label>Code長度</label>
<input v-model.number="batchPositionForm.codeLength" type="number" class="form-control" />
</div>
</div>
</div>
<div v-if="previewPositions.length">
<h3>預覽新增位置</h3>
<ul>
<li v-for="pos in previewPositions" :key="pos.PositionCode">
{{ pos.PositionCode }} - {{ pos.PositionName }} (行: {{ pos.RowNo }}, 列: {{ pos.ColumnNo }})
</li>
</ul>
</div>
</v-card-text>
<v-card-actions class="justify-end">
<v-btn color="primary" @click="generatePositionsMethod">生成預覽</v-btn>
<v-btn color="pirmary" @click="clearPreviewPositionsMethod">清除預覽</v-btn>
<v-btn color="primary" @click="confirmAddPositionsMethod">確認新增</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<!-- 編輯牌位位置彈出視窗 -->
<div v-if="currentEditPosition">
<v-dialog v-model="editPositionFormVisible" max-width="500">
<v-card>
<v-card-title class="text-h6">
編輯位置:{{ currentEditPosition?.positionName || '未選擇' }}
</v-card-title>
<v-card-text>
<div class="form-row">
<label>位置名稱:</label>
<input v-model="currentEditPosition.positionName" class="form-control" />
</div>
<div class="form-row">
<label>價格:</label>
<input v-model="currentEditPosition.price" type="number" class="form-control" />
</div>
<div class="form-row">
<label>狀態:</label>
<select v-model="currentEditPosition.statusCode" class="form-control">
<option v-for="s in statusList" :key="s.statusCode" :value="s.statusCode">
{{ s.statusName }}
</option>
</select>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" @click="saveEditPositionMethod">保存</v-btn>
<v-btn text @click="editPositionFormVisible = false">取消</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">
<script>
Vue.component('region-item', {
props: ['item', 'selectedId', 'expandAll', 'collapseAll'],
data() {
return {
expanded: false, // 預設全部收起
}
},
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.item.areaId === this.selectedId;
}
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
select() {
this.$emit('select-area', this.item);
},
},
template: `
<div>
<span class="toggle-icon" @click="toggle">{{ icon }}</span>
<span @click="select"
class="region-item-label"
:class="{ 'selected': isSelected }">
{{ item.areaName }}
</span>
<!-- 子區域列表 -->
<ul v-if="hasChildren && expanded">
<li v-for="child in item.children" :key="child.areaId">
<region-item
:item="child"
:selected-id="selectedId"
:expand-all="expandAll"
:collapse-all="collapseAll"
@select-area="$emit('select-area', $event)"
@clear-expand-all="$emit('clear-expand-all')"
@clear-collapse-all="$emit('clear-collapse-all')"
/>
</li>
</ul>
</div>
`
});
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
expandAllFlag: false, // 控制全部展開
collapseAllFlag: false, // 控制全部收起
ancestral_tablet_areas: [], //神主牌區域列表
currentSelectArea: null,
currentSelectAreaId: null,
showEidtAreadialogFlag: false,
showNewAreadialogFlag: false,
showAreaDataFlag: false,//是否顯示區域資料
flatAreas: [],//所有區域展平
disabledParentOptions: [],//某個區域禁止選擇作為父區域的函數
newArea: {
areaId: null, // 自增主鍵,新增時通常為 null
areaName: null, // 區域名稱,必填
areaCode: null, // 區域編號,必填
parentAreaId: null, // 上層區域 ID可為 null
areaType: null, // 區域類型,可空
price: null, // 價格,可空
sortOrder: null, // 排序,可空
description: null, // 區域描述
isDisabled: null
},
editArea: {
areaId: null, // 自增主鍵,新增時通常為 null
areaName: null, // 區域名稱,必填
areaCode: null, // 區域編號,必填
parentAreaId: null, // 上層區域 ID可為 null
areaType: null, // 區域類型,可空
price: null, // 價格,可空
sortOrder: null, // 排序,可空
description: null, // 區域描述
isDisabled: null
},
statusList: [],
//--------------------------------神主牌位置變數
showNewPositionDialogFlag: false,//控制是否顯示批次新增神主牌彈出視窗
editPositionFormVisible: false, // 控制是否顯示編輯彈出視窗
currentEditPosition: null, // 儲存當前正在編輯的位置資訊
batchPositionForm: {
startRow: 1,
startCol: 1,
rows: 5,
cols: 10,
price: 0,
status: 'available',
nameTemplate: '神位{code}',
startCode: 1,
codeLength: 3
},
positions: [],
// 預覽數據
previewPositions: [],
//--------------------------------神主牌位置變數
}
},
methods: {
selectAreaMethod(area) {
this.currentSelectAreaId = area.areaId;
this.currentSelectArea = area;
const node = this.findRegionById(this.ancestral_tablet_areas, area.areaId);
this.disabledParentOptions = this.getAllDescendants(node);
this.loadTabletPositionsMethod(area.areaId);
},
showEidtAreadialogMethod() {
this.showEidtAreadialogFlag = true;
if (this.currentSelectArea) {
this.editArea = {
areaId: this.currentSelectArea?.areaId,
areaName: this.currentSelectArea?.areaName,
areaCode: this.currentSelectArea?.areaCode,
parentAreaId: this.currentSelectArea?.parentAreaId ?? null,
areaType: this.currentSelectArea?.areaType ?? null,
price: this.currentSelectArea?.price ?? null,
sortOrder: this.currentSelectArea?.sortOrder ?? null,
description: this.currentSelectArea?.description ?? null,
isDisabled: this.currentSelectArea?.isDisabled ?? true
};
}
},
closeEidtAreadialogMethod() {
this.showEidtAreadialogFlag = false;
this.resetEditArea();
},
showNewtAreadialogMethod() {
this.showNewAreadialogFlag = true;
},
closeNewAreadialogMethod() {
this.showNewAreadialogFlag = false;
this.resetNewArea();
},
createNewAreaMethod() {
//新建區域
axios.post(HTTP_HOST + 'api/ancestraltablet/area/create', this.newArea)
.then(response => {
this.closeEidtAreadialogMethod();
this.getAreaListMethod();
})
.catch(error => {
console.error('失敗:', error);
});
},
editAreaMethod() {
//修改區域資料
axios.post(HTTP_HOST + 'api/ancestraltablet/area/edit', this.editArea)
.then(response => {
this.currentSelectArea = response.data.area;
this.getAreaListMethod();
this.closeEidtAreadialogMethod();
})
.catch(error => {
console.error('失敗:', error);
});
},
resetNewArea() {
this.newArea = {
areaId: null,
areaName: null,
areaCode: null,
parentAreaId: null,
areaType: null,
price: null,
sortOrder: null,
description: null,
isDisabled: false
};
},
resetEditArea() {
this.editArea = {
areaId: null,
areaName: null,
areaCode: null,
parentAreaId: null,
areaType: null,
price: null,
sortOrder: null,
description: null,
isDisabled: false
};
},
getAreaListMethod() {
//獲取區域列表
axios.get(HTTP_HOST + 'api/ancestraltablet/area/getlist')
.then(res => {
this.ancestral_tablet_areas = res.data
this.flatAreas = this.flattenAreas(res.data);
})
},
expandAll() {
this.expandAllFlag = true;
this.collapseAllFlag = false;
},
collapseAll() {
this.collapseAllFlag = true;
this.expandAllFlag = false;
},
//區域展開是否可以被選擇作為上級區域相關函數
flattenAreas(data, list = []) {
data.forEach(item => {
list.push({ areaId: item.areaId, areaName: item.areaName });
if (item.children && item.children.length) {
this.flattenAreas(item.children, list);
}
});
return list;
},
findRegionById(list, areaId) {
for (const item of list) {
if (item.areaId === areaId) return item;
if (item.children) {
const found = this.findRegionById(item.children, areaId);
if (found) return found;
}
}
return null;
},
getAllDescendants(node) {
//尋找某個區域的所有子區域
const ids = [];
const dfs = (n) => {
ids.push(n.areaId);
if (n.children) {
n.children.forEach(child => dfs(child));
}
};
dfs(node);
return ids;
},
toggleAreaData() {
this.showAreaDataFlag = !this.showAreaDataFlag;
},
//--------------------------------神主牌位置相關函數
padCodeMethod(codeNum, length) {
return codeNum.toString().padStart(length, '0');
},
loadTabletPositionsMethod(areaId) {
axios.get(HTTP_HOST + 'api/ancestraltablet/area/position/getlist', {
params: {
areaId: areaId
}
})
.then(response => {
this.positions = response.data;
})
.catch(error => {
console.error('失敗:', error);
});
},
generatePositionsMethod() {
const form = this.batchPositionForm;
const positions = [];
let codeCounter = form.startCode;
for (let i = 0; i < form.rows; i++) {
for (let j = 0; j < form.cols; j++) {
const row = form.startRow + i;
const col = form.startCol + j;
const paddedCode = this.padCodeMethod(codeCounter, form.codeLength);
const positionCode = paddedCode;
const positionName = form.nameTemplate.replace('{code}', paddedCode);
positions.push({
AreaId: this.currentSelectArea.areaId,
RowNo: row,
ColumnNo: col,
PositionCode: positionCode,
PositionName: positionName,
Price: form.price,
StatusCode: form.status,
Description: ''
});
codeCounter++;
}
}
this.previewPositions = positions; // 先賦值預覽,不發請求
},
async confirmAddPositionsMethod() {
if (this.previewPositions.length === 0) {
alert('請先生成預覽數據');
return;
}
// 調用後端批次新增介面
try {
const response = await axios.post(
`${HTTP_HOST}api/ancestraltablet/position/batchcreate`,
this.previewPositions
);
// 如果後端成功響應HTTP 200/201
alert('批次新增成功');
this.loadTabletPositionsMethod(this.currentSelectArea.areaId); // 刷新數據
} catch (error) {
// 捕獲錯誤響應如500、400等
console.error('批次新增失敗', error);
let msg = '批次新增失敗';
if (error.response && error.response.data && error.response.data.exceptionMessage) {
msg += `${error.response.data.exceptionMessage}`;
}
alert(msg);
}
this.previewPositions = []; // 清空預覽
},
clearPreviewPositionsMethod() {
this.previewPositions = [];
},
openNewPositionDialogMethod() {
this.showNewPositionDialogFlag = true;
this.batchPositionForm.price = this.currentSelectArea.price
},
closeNewPositionDialogMethod() {
this.showNewPositionDialogFlag = false;
this.previewPositions = [];
},
editPositionMethod(position) {
// 彈出編輯表單、打開模態框或跳轉到編輯頁面
this.currentEditPosition = position;
this.editPositionFormVisible = true;
},
async saveEditPositionMethod() {
try {
await axios.post(`${HTTP_HOST}api/ancestraltablet/position/edit`, this.currentEditPosition);
this.$message?.success?.('保存成功') || alert('保存成功');
this.editPositionFormVisible = false;
this.loadTabletPositionsMethod(this.currentSelectArea.areaId);
this.currentEditPosition = null
} catch (error) {
console.error(error);
this.$message?.error?.('保存失敗') || alert('保存失敗');
}
},
async deletePositionMethod(position) {
if (confirm(`確定要刪除【${position.positionName}】嗎?`)) {
try {
await axios.delete(`${HTTP_HOST}api/ancestraltablet/position/delete/${position.positionId}`);
this.$message?.success?.('刪除成功'); // 如果用的是 Element Plus 或其他 UI 框架
this.loadTabletPositionsMethod(this.currentSelectArea.areaId); // 刷新數據
} catch (error) {
console.error('刪除失敗', error);
this.$message?.error?.('刪除失敗,請檢查網路或稍後再試');
}
}
},
//--------------------------------神主牌位置相關函數
async loadStatusList() {
//獲取狀態列表
try {
const response = await axios.get(`${HTTP_HOST}api/ancestraltablet/status/list`);
this.statusList = response.data;
} catch (err) {
console.error('獲取狀態列表失敗', err);
}
}
},
watch: {
},
mounted() {
this.getAreaListMethod();
this.loadStatusList();
}
});
</script>
<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;
}
.grid-container {
display: grid;
grid-template-columns: repeat(10, 120px); /* 6列 */
grid-auto-rows: 100px; /* 行高 */
gap: 10px;
}
/* 可用(綠色) */
.status-available {
background-color: #d4edda;
border-color: #28a745;
}
/* 維護中(黃色) */
.status-maintenance {
background-color: #fff3cd;
border-color: #ffc107;
}
/* 預訂中(藍色) */
.status-reserved {
background-color: #cce5ff;
border-color: #007bff;
}
/* 已使用(灰色) */
.status-used {
background-color: #e2e3e5;
border-color: #6c757d;
}
.grid-item {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
.position-name {
background-color: #f0f0f0;
padding: 4px 8px;
font-weight: bold;
font-size: 14px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.position-content {
flex-grow: 1;
padding: 3px;
font-size: 12px;
color: #666;
}
</style>
</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_ancestraltablet_ancestraltabletarea_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,569 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletposition_index" %>
<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-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>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="container-fluid">
<div class="row">
<div class="col-sm-4 col-lg-2">
<div class="card shadow-sm my-2">
<div class="card-header">神主牌區域列表</div>
<div class="card-body">
<ul class="tree">
<li v-for="area in ancestral_tablet_areas" :key="area.AreaId">
<region-item
:item="area"
:selected-id="currentSelectAreaId"
@select-area="selectAreaMethod"
: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-10" v-if="currentSelectArea">
<div class="card shadow-sm my-2" style="position: sticky; top: 20px; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;">
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;background-color: #ffc107;">
<div>
{{currentSelectArea.areaName + ' - ' + '神主牌位置'}}
</div>
</div>
<div class="card-body" style="flex: 1 1 auto; min-height: 0; overflow: auto;">
<div class="grid-container">
<div
v-for="pos in positions"
:key="pos.positionCode"
class="grid-item"
:class="'status-' + pos.statusCode"
:style="{ gridRow: pos.rowNo, gridColumn: pos.columnNo }"
>
<div class="position-name">{{ pos.positionName }}</div>
<div class="position-content">
<span v-if="pos.statusCode == 'maintenance'">維護中</span>
<v-btn v-else-if="!pos.ancestralTabletRegistrant" @click="showCreatePWMethod(pos)">登記</v-btn>
<!-- 已預訂 -->
<div v-else>
<div>登記人:{{ pos.ancestralTabletRegistrant?.name }}</div>
<div>登記日期:{{ pos.ancestralTabletRegistrant?.registerDate|timeString('YYYY/MM/DD') }}</div>
<v-btn small color="btn-primary" @click="showEditPWMethod(pos)">
詳細資訊
</v-btn>
</>
</div>
<!-- 已使用 -->
<div v-else-if="pos.statusCode === 'used'">
<div>已使用</div>
<div v-if="pos.usedBy">使用人:{{ pos.usedBy }}</div>
<div v-if="pos.usedDate">使用日期:{{ pos.usedDate|timeString('YYYY/MM/DD') }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 登記資料彈出視窗 -->
<div>
<v-dialog v-model="showCreatePWFlag" max-width="800">
<v-card>
<v-card-title class="headline">登記資料({{'位置: ' + selectedPos?.positionName}})</v-card-title>
<v-card-text>
<!-- 登記人資料 -->
<hr />
<h5>登記人資料</h5>
<label>姓名:
<input class="form-control" type="text" v-model="form.createRegister.name" />
</label><br />
<label>電話:
<input class="form-control" type="text" v-model="form.createRegister.phone" />
</label><br />
<label>住址:
<input class="form-control" type="text" v-model="form.createRegister.address" />
</label><br />
<label>費用:
<input class="form-control" v-model="form.createRegister.price" />
</label><br />
<label>登記時間:
<input class="form-control" type="date" v-model="form.createRegister.registerDate" />
</label><br />
<label>開始時間:
<input class="form-control" type="date" v-model="form.createRegister.startDate" />
</label><br />
<label>結束時間:
<input class="form-control" type="date" v-model="form.createRegister.endDate" />
</label><br />
<label>長期有效:
<input type="checkbox" v-model="form.createRegister.isLongTerm" />
</label><br />
<label>是否啟用:
<input type="checkbox" v-model="form.createRegister.isActive" />
</label><br />
<div class="mt-2 mb-4">
<button
v-if="!form.createRegister.registrantCode"
class="btn btn-primary"
type="button"
@click="saveRegistrantMethod">
保存登記人
</button>
<button
v-else
class="btn btn-primary"
type="button"
@click="updateRegistrantMethod">
送出修改
</button>
</div>
<!-- 牌位資料 -->
<hr />
<h5>牌位資料</h5>
<label>牌位標題:
<input class="form-control" type="text" v-model="form.createPositionRecord.npTitle" />
</label><br />
<label>立牌時間:
<input class="form-control" type="date" v-model="form.createPositionRecord.npStandDate" />
</label><br />
<label>陽上:
<input class="form-control" type="text" v-model="form.createPositionRecord.npYangShang" />
</label><br />
<label>內牌內容:</label>
<textarea
class="form-control"
rows="4"
v-model="form.createPositionRecord.wpContent"
style="width: 100%; box-sizing: border-box;"
></textarea><br />
<div class="mt-2 mb-2">
<button v-if="form.createPositionRecord.recordId == null" class="btn btn-primary" type="button" @click="saveCreatePositionRecordMethod">保存牌位</button>
<button v-else type="button" class="btn btn-primary" @click="updateCreatePositionRecordMethod">
送出修改
</button>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<button class="btn btn-secondary" type="button" @click="closePWDialogMethod">關閉</button>
</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">
<script>
Vue.component('region-item', {
props: ['item', 'selectedId', 'expandAll', 'collapseAll'],
data() {
return {
expanded: false, // 預設全部收起
}
},
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.item.areaId === this.selectedId;
}
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
select() {
this.$emit('select-area', this.item);
},
},
template: `
<div>
<span class="toggle-icon" @click="toggle">{{ icon }}</span>
<span @click="select"
class="region-item-label"
:class="{ 'selected': isSelected }">
{{ item.areaName }}
</span>
<!-- 子區域列表 -->
<ul v-if="hasChildren && expanded">
<li v-for="child in item.children" :key="child.areaId">
<region-item
:item="child"
:selected-id="selectedId"
:expand-all="expandAll"
:collapse-all="collapseAll"
@select-area="$emit('select-area', $event)"
@clear-expand-all="$emit('clear-expand-all')"
@clear-collapse-all="$emit('clear-collapse-all')"
/>
</li>
</ul>
</div>
`
});
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 {
expandAllFlag: false, // 控制全部展開
collapseAllFlag: false, // 控制全部收起
ancestral_tablet_areas: [], //神主牌區域列表
currentSelectArea: null,
currentSelectAreaId: null,
statusList: [],
//--------------------------------神主牌位置變數
positions: [],
selectedPos: null, //點擊登記的的pos
showCreatePWFlag: false, //控制是否打開登記神主牌位資料彈出視窗
form: {
createRegister: {
//新增登記人form
registrantCode: null, // 登記編號
positionId: null,
name: null,
phone: null,
address: null,
registerDate: null,
price: null,
startDate: null,
endDate: null,
isLongTerm: false,
isActive: true,
},
createPositionRecord: {
//新增牌位登記form
recordId: null, // 自增主鍵,前端一般不用填
registrantCode: null, // 外鍵,關聯登記人編號
npTitle: null,
npStandDate: null,
npYangShang: null,
wpContent: null,
},
},
//--------------------------------神主牌位置變數
}
},
methods: {
selectAreaMethod(area) {
this.currentSelectAreaId = area.areaId;
this.currentSelectArea = area;
this.loadTabletPositionsMethod(area.areaId);
},
getAreaListMethod() {
//獲取區域列表
axios.get(HTTP_HOST + 'api/ancestraltablet/area/getlist')
.then(res => {
this.ancestral_tablet_areas = res.data
})
},
expandAll() {
this.expandAllFlag = true;
this.collapseAllFlag = false;
},
collapseAll() {
this.collapseAllFlag = true;
this.expandAllFlag = false;
},
//--------------------------------神主牌位置相關函數
loadTabletPositionsMethod(areaId) {
axios.get(HTTP_HOST + 'api/ancestraltablet/position/getlist', {
params: {
areaId: areaId
}
})
.then(response => {
this.positions = response.data;
})
.catch(error => {
console.error('失敗:', error);
});
},
showCreatePWMethod(pos) {
//打開新增彈出視窗
this.selectedPos = pos;
this.showCreatePWFlag = true;
this.form.createRegister.positionId = pos.positionId
this.form.createRegister.price = pos.price
},
showEditPWMethod(pos) {
//打開編輯彈出視窗
this.selectedPos = pos;
const registrant = pos.ancestralTabletRegistrant;
if (registrant) {
this.form.createRegister.registrantCode = registrant.registrantCode;
this.form.createRegister.positionId = pos.positionId;
this.form.createRegister.name = registrant.name;
this.form.createRegister.phone = registrant.phone;
this.form.createRegister.address = registrant.address;
this.form.createRegister.registerDate = registrant.registerDate
? registrant.registerDate?.split('T')[0]
: '';
this.form.createRegister.price = registrant.price;
this.form.createRegister.startDate = registrant.startDate ? registrant.startDate?.split('T')[0]
: '';
this.form.createRegister.endDate = registrant.endDate ? registrant.endDate?.split('T')[0]
: '';
this.form.createRegister.isLongTerm = registrant.isLongTerm;
this.form.createRegister.isActive = registrant.isActive;
this.form.createPositionRecord.registrantCode = registrant.registrantCode;
this.showCreatePWFlag = true;
if (registrant.tabletRecord) {
this.form.createPositionRecord.recordId = registrant.tabletRecord.recordId;
this.form.createPositionRecord.registrantCode = registrant.tabletRecord.registrantCode;
this.form.createPositionRecord.npTitle = registrant.tabletRecord.npTitle;
this.form.createPositionRecord.npStandDate = registrant.tabletRecord.npStandDate ? registrant.tabletRecord.npStandDate.split('T')[0]
: '';
this.form.createPositionRecord.npYangShang = registrant.tabletRecord.npYangShang;
this.form.createPositionRecord.wpContent = registrant.tabletRecord.wpContent;
}
}
},
closePWDialogMethod(pos) {
//關閉編輯彈出視窗
this.selectedPos = null;
// 重設登記人欄位
this.form.createRegister = {
registrantCode: null,
positionId: null,
name: null,
phone: null,
address: null,
registerDate: null,
price: null,
startDate: null,
endDate: null,
isLongTerm: false,
isActive: false
};
// 清空牌位記錄
this.form.createPositionRecord = {
recordId: null,
registrantCode: null,
npTitle: null,
npStandDate: null,
npYangShang: null,
wpContent: null
};
this.showCreatePWFlag = false;
},
//--------------------------------神主牌位置相關函數
//--------------------------------登記人相關函數 start
async saveRegistrantMethod() {
try {
const response = await axios.post(HTTP_HOST + 'api/ancestraltablet/registrant/create', this.form.createRegister);
console.log('保存成功', response.data);
// 可選:提示用戶、關閉對話框、刷新數據等
this.$toast?.success('登記人保存成功'); // 取決於你是否使用 toast 插件
this.form.createRegister.registrantCode = response.data.registrantCode
this.form.createPositionRecord.registrantCode = response.data.registrantCode
this.loadTabletPositionsMethod(this.selectedPos.areaId)
alert("修改成功")
} catch (error) {
console.error('保存失敗', error);
this.$toast?.error('登記人保存失敗');
}
},
updateRegistrantMethod() {
axios.post(HTTP_HOST + 'api/ancestraltablet/registrant/update', this.form.createRegister)
.then(response => {
console.log('登記人更新成功:', response.data);
this.$toast?.success?.('登記人更新成功'); // 可選:使用 toast 彈出提示
//this.showCreatePWFlag = false; // 關閉彈出視窗
// 可選:刷新列表等
this.loadTabletPositionsMethod(this.selectedPos.areaId)
alert("修改成功")
})
.catch(error => {
console.error('更新登記人失敗:', error);
this.$toast?.error?.('更新失敗,請檢查數據');
});
},
//--------------------------------登記人相關函數 end
//--------------------------------牌位資料相關函數 Start
saveCreatePositionRecordMethod() {
// 校驗必須欄位
if (!this.form.createPositionRecord.registrantCode) {
alert('請先填寫登記人資料並且送出保存!');
return;
}
axios.post(HTTP_HOST + 'api/ancestraltablet/pw/create', this.form.createPositionRecord)
.then(res => {
if (res.data && res.data.message) {
alert(res.data.message);
} else {
alert('保存成功');
}
// 成功後可以關閉彈出視窗或清空表單
//this.showCreatePWFlag = false;
})
.catch(err => {
console.error('保存失敗:', err);
alert('保存失敗,請檢查伺服器日誌');
});
},
updateCreatePositionRecordMethod() {
// 校驗必須欄位
if (!this.form.createPositionRecord.recordId) {
alert('不存在牌位資料,無法更新');
return;
}
axios.post(HTTP_HOST + 'api/ancestraltablet/pw/update', this.form.createPositionRecord)
.then(res => {
if (res.data && res.data.message) {
alert(res.data.message);
} else {
alert('牌位資料更新成功');
}
// 成功後可以關閉彈出視窗或清空表單
//this.showCreatePWFlag = false;
})
.catch(err => {
alert('更新失敗:', err);
});
},
//--------------------------------牌位資料相關函數 end
async loadStatusList() {
//獲取狀態列表
try {
const response = await axios.get(`${HTTP_HOST}api/ancestraltablet/status/list`);
this.statusList = response.data;
} catch (err) {
console.error('獲取狀態列表失敗', err);
}
}
},
watch: {
},
mounted() {
this.getAreaListMethod();
this.loadStatusList();
}
});
</script>
<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;
}
.grid-container {
display: grid;
grid-template-columns: repeat(10, 150px); /* 6列 */
grid-auto-rows: 150px; /* 行高 */
gap: 10px;
}
/* 可用(綠色) */
.status-available {
background-color: #d4edda;
border-color: #28a745;
}
/* 維護中(黃色) */
.status-maintenance {
background-color: #fff3cd;
border-color: #ffc107;
}
/* 已使用(灰色) */
.status-used {
background-color: #e2e3e5;
border-color: #6c757d;
}
.grid-item {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
.position-name {
background-color: #f0f0f0;
padding: 4px 8px;
font-weight: bold;
font-size: 14px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.position-content {
flex-grow: 1;
padding: 3px;
font-size: 12px;
color: #666;
}
</style>
</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_ancestraltablet_ancestraltabletposition_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,126 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletstatistics_index" %>
<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">
<h2 class="title">區域牌位統計</h2>
<table class="stats-table" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th>區域編號</th>
<th>區域名稱</th>
<th>總位置數</th>
<th>可用位置數</th>
</tr>
</thead>
<tbody>
<tr v-for="area in areas" :key="area.areaId">
<td>{{ area.areaId }}</td>
<td>{{ area.areaName }}</td>
<td>{{ area.totalPositions }}</td>
<td>{{ area.availableCount }}</td>
</tr>
</tbody>
</table>
</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 {
areas: []
}
},
methods: {
getAncestralTabletPositionsStatisticsMethod() {
axios.get(HTTP_HOST + 'api/ancestraltablet/statistics/positions/availablepositions')
.then((res) => {
this.areas = res.data
})
.catch((error) => {
})
}
},
mounted() {
this.getAncestralTabletPositionsStatisticsMethod()
}
})
</script>
<style>
.container {
max-width: 800px;
margin: 50px auto;
background: #ffffff;
padding: 25px 30px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
font-family: "Microsoft YaHei", sans-serif;
}
.title {
text-align: center;
color: #2c3e50;
font-size: 22px;
font-weight: 600;
margin-bottom: 25px;
letter-spacing: 1px;
}
.stats-table {
width: 100%;
border-collapse: collapse;
font-size: 15px;
text-align: center;
color: #333;
}
.stats-table th {
background-color: #1976d2;
color: #fff;
padding: 12px;
font-weight: 600;
border: none;
}
.stats-table td {
padding: 10px;
border: 1px solid #e0e0e0;
}
/* 奇偶行區分 */
.stats-table tbody tr:nth-child(odd) {
background-color: #f8f9fa;
}
/* 滑鼠懸停高亮 */
.stats-table tbody tr:hover {
background-color: #e3f2fd;
transition: 0.3s ease;
}
/* 響應式支持 */
@media (max-width: 600px) {
.container {
padding: 15px;
}
.title {
font-size: 18px;
}
.stats-table th, .stats-table td {
padding: 8px;
font-size: 13px;
}
}
</style>
</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_ancestraltablet_ancestraltabletstatistics_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,15 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="create.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletuselist_create" %>
<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>
這是新增頁面
</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,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_ancestraltablet_ancestraltabletuselist_create : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,99 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="detail.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletuselist_detail" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<a :href="'edit.aspx?registrantCode=' + registrantCode" class="btn btn-primary">修改資料</a>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div>
{{registrantCode}}
</div>
<div>
<div class="card">
<h2>登記人資訊</h2>
<p><strong>登記編碼:</strong> {{ registrant.registrantCode }}</p>
<p><strong>姓名:</strong> {{ registrant.name }}</p>
<p><strong>電話:</strong> {{ registrant.phone }}</p>
<p><strong>地址:</strong> {{ registrant.address }}</p>
<p><strong>登記日期:</strong> {{ formatDate(registrant.registerDate) }}</p>
<div style="border: 1px solid #007bff; background-color: #f0f8ff; padding: 10px 15px; border-radius: 5px; display: flex; flex-direction: column; gap: 5px; max-width: 400px; margin-bottom: 10px;">
<div>
<label style="font-weight: bold; margin-right: 5px;">已選擇位置:</label>
<span>{{ registrant?.positionName || '未選擇' }}</span>
</div>
</div>
<p><strong>價格:</strong> {{ registrant.price }}</p>
<p><strong>開始日期:</strong> {{ formatDate(registrant.startDate) }}</p>
<p><strong>結束日期:</strong> {{ formatDate(registrant.endDate) }}</p>
<p><strong>是否長期:</strong> {{ registrant.isLongTerm ? '是' : '否' }}</p>
<p><strong>是否啟用:</strong> {{ registrant.isActive ? '是' : '否' }}</p>
</div>
<div class="card" v-if="registrant.tabletRecord">
<h2>牌位資料</h2>
<p><strong>記錄ID:</strong> {{ registrant.tabletRecord.recordId }}</p>
<p><strong>登記編碼:</strong> {{ registrant.tabletRecord.registrantCode }}</p>
<p><strong>牌位標題:</strong> {{ registrant.tabletRecord.npTitle }}</p>
<p><strong>立牌日期:</strong> {{ formatDate(registrant.tabletRecord.npStandDate) }}</p>
<p><strong>陽上:</strong> {{ registrant.tabletRecord.npYangShang }}</p>
<p><strong>內牌內容:</strong> {{ registrant.tabletRecord.wpContent }}</p>
</div>
<div class="card" v-else>
<h2>牌位資料</h2>
<p>暫無牌位資料</p>
</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 {
registrantCode: '<%=Request.QueryString["registrantCode"]%>',
registrant: {}
}
},
methods: {
formatDate(dateStr) {
if (!dateStr) return "";
const date = new Date(dateStr);
return date.toLocaleDateString();
},
getRegistrantByCodeMethod(code) {
axios.get(HTTP_HOST + 'api/ancestraltablet/registrant/getbycode', {
params: {
registrantCode: code
}
})
.then((res => {
this.registrant = res.data
}))
.catch((error => {
}))
}
},
mounted() {
this.getRegistrantByCodeMethod(this.registrantCode);
}
})
</script>
<style>
.card {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
background-color: #fafafa;
}
</style>
</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_ancestraltablet_ancestraltabletuselist_detail : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,419 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="edit.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletuselist_edit" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<a :href="'detail.aspx?registrantCode=' + registrantCode" class="btn btn-secondary">返回詳情</a>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div>
<!-- 登記人資料編輯區域 -->
<div class="card">
<h2>編輯登記人資訊</h2>
<label>登記編碼:</label>
<input type="text" v-model="registrant.registrantCode" class="form-control" disabled />
<label>姓名:</label>
<input type="text" v-model="registrant.name" class="form-control" />
<label>電話:</label>
<input type="text" v-model="registrant.phone" class="form-control" />
<label>地址:</label>
<input type="text" v-model="registrant.address" class="form-control" />
<label>登記日期:</label>
<input type="date" v-model="registrant.registerDate" class="form-control" />
<div class="mt-4" style="border: 1px solid #007bff; background-color: #f0f8ff; padding: 10px 15px; border-radius: 5px; display: flex; flex-direction: column; gap: 5px; max-width: 400px; margin-bottom: 10px;">
<div>
<label style="font-weight: bold; margin-right: 5px;">已選擇位置:</label>
<span>{{ registrant?.positionName || '未選擇' }}</span>
</div>
<div v-if="newPositionId">
<label style="font-weight: bold; margin-right: 5px;">新選擇位置:</label>
<span>{{ this.newPositionEntity?.positionName }}</span>
</div>
<button type="button" @click="isShowPositionDialog=true"
style="align-self: flex-start; padding: 5px 10px; background-color: #007bff; color: #fff; border: none; border-radius: 3px; cursor: pointer;"
>
{{registrant?.positionId ? '更換位置' : '選擇位置'}}
</button>
</div>
<label>價格:</label>
<input type="number" v-model="registrant.price" class="form-control" />
<label>開始日期:</label>
<input type="date" v-model="registrant.startDate" class="form-control" />
<label>結束日期:</label>
<input type="date" v-model="registrant.endDate" class="form-control" />
<div class="mt-3" style="display:flex; align-items:center; gap:15px; margin-bottom:10px; font-family:Arial, sans-serif;">
<label style="font-weight:500;">是否長期:</label>
<input type="checkbox" v-model="registrant.isLongTerm" style="width:16px; height:16px; cursor:pointer;" />
<span>是</span>
</div>
<div style="display:flex; align-items:center; gap:15px; margin-bottom:10px; font-family:Arial, sans-serif;">
<label style="font-weight:500;">是否啟用:</label>
<input type="checkbox" v-model="registrant.isActive" style="width:16px; height:16px; cursor:pointer;" />
<span>是</span>
</div>
<button type="button" class="btn btn-primary mt-2" @click="updateRegistrant">保存登記資料</button>
</div>
<!-- 牌位資料區域 -->
<div class="card" v-if="registrant.tabletRecord">
<h2>編輯牌位資料</h2>
<label>記錄ID:</label>
<input type="text" v-model="registrant.tabletRecord.recordId" class="form-control" disabled />
<label>登記編碼 (外鍵):</label>
<input type="text" v-model="registrant.tabletRecord.registrantCode" class="form-control" disabled />
<label>牌位標題:</label>
<input type="text" v-model="registrant.tabletRecord.npTitle" class="form-control" />
<label>立牌日期:</label>
<input type="date" v-model="registrant.tabletRecord.npStandDate" class="form-control" />
<label>陽上:</label>
<input type="text" v-model="registrant.tabletRecord.npYangShang" class="form-control" />
<label>內牌內容:</label>
<textarea v-model="registrant.tabletRecord.wpContent" class="form-control"></textarea>
<button type="button" class="btn btn-success mt-2" @click="updateTabletRecord">保存牌位資料</button>
</div>
<div class="card" v-else>
<h2>牌位資料</h2>
<p>暫無牌位資料</p>
<button type="button" class="btn btn-primary" @click="createTabletRecordForm">新增牌位資料</button>
<div v-if="creatingTablet" class="mt-3">
<label>牌位標題:</label>
<input type="text" v-model="newTablet.npTitle" class="form-control" />
<label>立牌日期:</label>
<input type="date" v-model="newTablet.npStandDate" class="form-control" />
<label>陽上:</label>
<input type="text" v-model="newTablet.npYangShang" class="form-control" />
<label>牌位內容:</label>
<textarea v-model="newTablet.wpContent" class="form-control"></textarea>
<button type="button" class="btn btn-success mt-2" @click="createTabletRecord">保存新增</button>
</div>
</div>
<div>
<v-dialog v-model="isShowPositionDialog" persistent
width="80%"
height="80%">
<v-card style="
width: 80vw;
height: 80vh;
display: flex;
flex-direction: column;
">
<v-card-title
style="display: flex; align-items: center; font-weight: 600; font-size: 18px;"
>
<span>選擇位置:</span>
<select
class="form-control"
style="
flex: 0 0 200px;
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
font-size: 14px;
cursor: pointer;
outline: none;
transition: border-color 0.2s;
"
v-model="selectedArea"
@focus="e => e.target.style.borderColor = '#007bff'"
@blur="e => e.target.style.borderColor = '#ccc'"
@change="onAreaChange"
>
<option value="">請選擇區域</option>
<option v-for="area in areaList" :value="area.areaId" :key="area.areaId">{{area.areaName}}</option>
</select>
</v-card-title>
<v-card-text style="flex: 1; overflow: auto;">
<div class="grid-container">
<div
v-for="pos in positionList"
:key="pos.positionId"
class="grid-item"
:class="'status-' + (pos.isCanUse? 'canuse':'cannotuse')"
:style="{ gridRow: pos.rowNo, gridColumn: pos.columnNo }"
>
<div class="position-name">{{ pos.positionName }}</div>
<div class="position-content">
<button type="button" v-if="pos.isCanUse"
class="btn btn-primary"
@click="chooseNewPositionMethod(pos)"
>選擇</button>
<span v-else>已被使用</span>
</div>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<div class="me-5">新位置:{{newPositionEntity?.positionName}}</div>
<v-btn color="primary" @click="saveChoosePositionMethod">確定</v-btn>
<v-btn color="grey" @click="cancelChoosePositionMethod">取消</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</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 {
registrantCode: '<%=Request.QueryString["registrantCode"]%>',
registrant: {},
creatingTablet: false,
newPositionId: null,
newPositionEntity: null,
newTablet: {
//新增牌位登記form
recordId: null, // 自增主鍵,前端一般不用填
registrantCode: null, // 外鍵,關聯登記人編號
npTitle: null,
npStandDate: null,
npYangShang: null,
wpContent: null,
},
positionList: [],//選擇神位位置的時候獲取的位置列表
isShowPositionDialog: false,
selectedArea: "",
areaList: [],
}
},
methods: {
formatDate(dateStr) {
if (!dateStr) return "";
return dateStr.split('T')[0];
},
getRegistrantByCodeMethod(code) {
axios.get(HTTP_HOST + 'api/ancestraltablet/registrant/getbycode', {
params: {
registrantCode: code
}
})
.then((res => {
const data = res.data;
// 格式化登記日期欄位
if (data.registerDate) data.registerDate = this.formatDate(data.registerDate);
if (data.startDate) data.startDate = this.formatDate(data.startDate);
if (data.endDate) data.endDate = this.formatDate(data.endDate);
// 格式化牌位日期欄位
if (data.tabletRecord && data.tabletRecord.npStandDate)
data.tabletRecord.npStandDate = this.formatDate(data.tabletRecord.npStandDate);
this.registrant = data;
}))
.catch((error => {
}))
},
// 更新登記資料
updateRegistrant() {
const newPositionId = this.newPositionId; // 假設 newPositionId 存在組件裡
// 如果 newPositionId 不為空,則更新 registrant.PositionId
if (newPositionId != null && newPositionId !== '') {
this.registrant.PositionId = newPositionId;
}
axios.post(HTTP_HOST + 'api/ancestraltablet/registrant/update', this.registrant)
.then(() => {
alert('登記資料已保存!')
this.getRegistrantByCodeMethod(this.registrantCode);
this.newPositionId = null;
}
)
.catch(err => {
console.error(err);
alert('保存失敗!');
});
},
// 更新牌位資料
updateTabletRecord() {
axios.post(HTTP_HOST + 'api/ancestraltablet/pw/update', this.registrant.tabletRecord)
.then(() => alert('牌位資料已更新!'))
.catch(err => {
console.error(err);
alert('保存失敗!');
});
},
// 顯示新增牌位表單
createTabletRecordForm() {
this.creatingTablet = true;
},
// 新增牌位資料
createTabletRecord() {
const data = {
...this.newTablet,
registrantCode: this.registrant.registrantCode,
};
axios.post(HTTP_HOST + 'api/ancestraltablet/pw/create', data)
.then(() => {
alert('牌位資料已新增!');
this.creatingTablet = false;
this.getRegistrantByCodeMethod(this.registrantCode);
})
.catch(err => {
console.error(err);
alert('新增失敗!');
});
},
getPositionList(areaId) {
axios.get(HTTP_HOST + 'api/ancestraltablet/position/shortlist',
{
params: {
areaId: areaId
}
})
.then((res) => {
this.positionList = res.data
})
.catch((error) => {
});
},
getArea() {
axios.get(HTTP_HOST + 'api/ancestraltablet/area/getereawithposition')
.then((res) => {
this.areaList = res.data;
})
.catch();
},
onAreaChange() {
//獲取有神位的區域
if (!this.selectedArea) {
this.positionList = [];
return; // 如果沒有選擇,不請求
}
this.getPositionList(this.selectedArea)
},
chooseNewPositionMethod(newPos) {
this.newPositionEntity = newPos
},
closeChoosePositionDialogMethod() {
this.isShowPositionDialog = false;
this.selectedArea = "";
this.positionList = [];
},
cancelChoosePositionMethod() {
this.newPositionEntity = null;
this.closeChoosePositionDialogMethod()
},
saveChoosePositionMethod() {
this.newPositionId = this.newPositionEntity.positionId
this.closeChoosePositionDialogMethod()
},
},
mounted() {
this.getRegistrantByCodeMethod(this.registrantCode);
this.getArea()
}
})
</script>
<style>
.card {
border: 1px solid #ccc;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
background-color: #fafafa;
width: 90%;
max-width: 900px;
min-width: 300px; /* 最小寬度防止太窄 */
margin: 0 auto; /* 居中 */
}
.grid-container {
display: grid;
grid-template-columns: repeat(10, 150px); /* 6列 */
grid-auto-rows: 150px; /* 行高 */
gap: 10px;
}
.status-available {
background-color: #d4edda;
border-color: #28a745;
}
/* 維護中(黃色) */
.status-maintenance {
background-color: #fff3cd;
border-color: #ffc107;
}
/* 已使用(灰色) */
.status-used {
background-color: #e2e3e5;
border-color: #6c757d;
}
/* 可以使用(綠色) */
.status-canuse {
background-color: #d4edda; /* 淺綠色背景 */
border-color: #28a745; /* 綠色邊框 */
color: #155724; /* 深綠色文字 */
}
/* 不能使用(紅色) */
.status-cannotuse {
background-color: #f8d7da; /* 淺紅色背景 */
border-color: #dc3545; /* 紅色邊框 */
color: #721c24; /* 深紅文字 */
}
.grid-item {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 1px 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
.position-name {
background-color: #f0f0f0;
padding: 4px 8px;
font-weight: bold;
font-size: 14px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.position-content {
flex-grow: 1;
padding: 3px;
font-size: 12px;
color: #666;
}
</style>
</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_ancestraltablet_ancestraltabletuselist_edit : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -0,0 +1,192 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_ancestraltablet_ancestraltabletuselist_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>
-->
<div style="display: inline">
<span>
查詢條件 :
</span>
<input v-model="search.registrantUserName" class="form-control" style="display:inline-block; width: auto;"
placeholder="請輸入登記人"
/>
<button type="button" class="btn btn-primary" @click="handleSearch">搜尋</button>
<button type="button" class="btn btn-primary" @click="clearSearch">清除條件</button>
</div>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
神主牌使用記錄
<div>
<v-data-table
:items="registrantList"
:headers="headers"
hide-default-footer>
<template #item.registerdate="{item}">
{{item.registerDate |timeString('YYYY-MM-DD')}}
</template>
<template #item.startdate="{item}">
{{item.startDate |timeString('YYYY-MM-DD')}}
</template>
<template #item.enddate="{item}">
{{item.endDate |timeString('YYYY-MM-DD')}}
</template>
<template #item.createdat="{item}">
{{item.createdAt |timeString('YYYY-MM-DD HH:MM')}}
</template>
<template #item.islongterm="{item}">
{{item.isLongTerm ? "是": "否"}}
</template>
<template #item.isactive="{item}">
{{item.isActive ? "是" : "否"}}
</template>
<template #item.actions="{item}">
<a :href="'detail.aspx?registrantCode=' + item.registrantCode" class="btn btn-primary">詳細資訊</a>
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap="false">
<v-col cols="12" md="8">
<v-pagination
v-model="options.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ total }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field
v-model="options.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="options.page = parseInt($event, 10)"
></v-text-field>
</v-col>
<!-- 每頁條數選擇 -->
<v-col cols="12" md="1">
<v-select
v-model="options.itemsPerPage"
:items="[5, 10, 20, 50]"
label="每頁條數"
dense
hide-details
style="width: 100px;"
></v-select>
</v-col>
</v-row>
</v-container>
</div>
</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 {
registrantList: [],
headers: [
{ text: '登記編號', value: 'registrantCode' },
{ text: '姓名', value: 'name' },
{ text: '電話', value: 'phone' },
{ text: '住址', value: 'address' },
{ text: '登記日期', value: 'registerdate' },
{ text: '費用', value: 'price' },
{ text: '牌位編號', value: 'positionId' },
{ text: '開始時間', value: 'startdate' },
{ text: '結束時間', value: 'enddate' },
{ text: '是否長期', value: 'islongterm' },
{ text: '是否有效', value: 'isactive' },
{ text: '創建時間', value: 'createdat' },
{ text: '', value: 'actions' },
],
options: {
page: 1, // 當前頁
itemsPerPage: 10, // 每頁條數
sortBy: [],
sortDesc: []
},
search: {
registrantUserName: null,
},
total: 0,
loading: false,
}
},
methods: {
getRegistrantListMethod() {
axios.get(HTTP_HOST + 'api/ancestraltablet/registrant/getlist')
.then((res) => {
this.registrantList = res.data;
}).catch((error) => {
alert("載入失敗")
})
},
getRegistrantListByPageMethod() {
if (this.loading) return;
this.loading = true;
axios.post(HTTP_HOST + 'api/ancestraltablet/registrant/getlistbypage', {
page: this.options.page,
pageSize: this.options.itemsPerPage,
searchName: this.search.registrantUserName
})
.then((res) => {
this.registrantList = res.data.data;
this.total = res.data.total;
}).catch((err) => {
console.log(err);
}).finally(() => {
this.loading = false;
});
},
resetTableOptions() {
this.options = {
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: []
};
},
clearSearch() {
this.search.registrantUserName = null;
this.resetTableOptions();
},
handleSearch() {
this.resetTableOptions();
}
},
watch: {
options: {
handler() {
this.getRegistrantListByPageMethod();
},
deep: true,
}
},
mounted() {
this.getRegistrantListByPageMethod()
},
computed: {
pageCount() {
return Math.ceil(this.total / this.options.itemsPerPage)
},
}
})
</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_ancestraltablet_ancestraltabletuselist_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -103,8 +103,8 @@ public partial class admin_follower_reg : MyWeb.config
if (prod.birthday.HasValue)
{
Literal1.Text = publicFun.chagenDate(prod.birthday.Value);
Literal2.Text = Model.follower.chagenSign(prod.birthday.Value);
Literal1.Text = publicFun.chagenDate(prod.birthday.Value); //農曆生日
Literal2.Text = Model.follower.chagenSign(prod.birthday.Value); //生肖
}
if (prod.leader.HasValue)
{

View File

@@ -15,6 +15,15 @@
<div class="col-sm-4 text-left">
<input class="form-control" v-model="guadanorder.order_form.orderNo" readonly />
</div>
<label class="col-sm-2 col-form-label text-center">關聯活動</label>
<div class="col-sm-4">
<select class="form-control" v-model="guadanorder.order_form.activityNum" >
<option :value="null">未關聯</option>
<option v-for="activity in activityList" :key="activity.num" :value="activity.num">
{{activity.subject}}
</option>
</select>
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">
@@ -85,17 +94,105 @@
<template v-slot:item.name="{item}">
{{item.follower?.u_name}}
</template>
<template #item.actions="{ item }">
<v-btn color="red" variant="outlined" size="small" class="me-2" @click="confirmDeleteGuadanOrderGuest(item)">
取消
</v-btn>
<v-btn color="primary" variant="outlined" size="small" @click="editGuadanOrderGuest(item)">
<v-icon start>mdi-pencil</v-icon>
編輯
</v-btn>
<template v-slot:item.sex="{item}">
{{item.follower?.sex}}
</template>
<template #item.actions="{ item }">
<div>
<!-- 取消預訂 -->
<v-btn
color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '401'"
@click="confirmDeleteGuadanOrderGuest(item)"
>
取消預訂
</v-btn>
<v-btn
color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '401'"
@click="checkinGuadanGuest(item)"
>
入住
</v-btn>
<v-btn
color="red"
variant="outlined"
size="small"
class="me-2"
:disabled="item.statuscode !== '402'"
@click="showXuzhuGuestModalMethod(item)">
續住
</v-btn>
<!-- 退房 -->
<v-btn
color="primary"
variant="outlined"
size="small"
:disabled="item.statuscode !== '402'"
@click="checkoutGuadanOrderGuest(item)"
>
<v-icon start>mdi-exit-run</v-icon>
退房
</v-btn>
</div>
</template>
</v-data-table>
</fieldset>
<!-- 🟢 續住彈出視窗 -->
<div>
<v-dialog v-model="guadanguest.xuzhu.showXuzhuGuestModal" max-width="50%">
<v-card
class="pa-6 d-flex flex-column" style="min-height: 60vh; border-radius: 12px;"
style="min-height: 40vh; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.15);"
>
<!-- 弹窗标题 -->
<v-card-title
class="text-h6 d-flex align-center justify-space-between pb-4"
style="border-bottom: 1px solid #eee;"
>
<div class="d-flex align-center">
<span class="font-weight-bold">续住</span>
</div>
<v-btn icon @click="closeXuzhuGuestModalMethod">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<!-- 弹窗内容 -->
<v-card-text class="flex-grow-1 py-6" style="overflow-y: auto;">
<div class="mb-4">
<span class="font-weight-medium">当前退房时间:</span>
<span class="text-primary">{{ guadanguest.xuzhu.currentCheckoutDate }}</span>
</div>
<div class="d-flex align-center">
<span class="font-weight-medium mr-2">续住后退房时间:</span>
<input
type="date"
id="newCheckoutDate"
v-model="guadanguest.xuzhu.newCheckoutDate"
class="pa-2"
style="border: 1px solid #ccc; border-radius: 6px; padding: 6px 10px;"
/>
</div>
</v-card-text>
<!-- 弹窗操作按钮 -->
<v-card-actions class="justify-end pt-4" style="border-top: 1px solid #eee;">
<v-btn color="primary" class="px-6" @click="xuzhuPost">续住</v-btn>
<v-btn text class="ml-2" @click="closeXuzhuGuestModalMethod">取消</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<!-- 🟢 掛單蓮友彈出視窗 -->
<div>
@@ -174,21 +271,6 @@
<div class="text-info text-xs mt-1">請從床位清單中選擇</div>
</div>
</v-col>
<v-col cols="12" sm="6">
<div class="d-flex flex-column justify-center" style="height: 100%;">
<v-select
v-model="checkInGuest.inGuest.statusUuid"
:items="checkInGuest.status"
item-value="uuid"
item-text="name"
label="选择個人挂单状态"
dense
outlined
></v-select>
</div>
</v-col>
</v-row>
</v-container>
</v-card-text>
@@ -247,13 +329,13 @@
<div v-for="bed in region_modal.currentSelectBeds" :key="bed.uuid" @click="selectBed(bed)" style="padding: 8px; border: 1px solid #d9d9d9; cursor: pointer; max-height: 250px;" :style="{
backgroundColor: region_modal.currentSelectBed?.uuid === bed.uuid
? '#bae7ff' // 當前選中
: (bed.canUsed ? '#f6ffed' : '#fff1f0'), // 可用綠色,不可用紅色
color: bed.canUsed ? 'black' : '#999', // 不可用時灰色文字
pointerEvents: bed.canUsed ? 'auto' : 'none' // 不可用時無法點擊
: ((bed.canUsed && bed.isActive && !bed.bedIsStop) ? '#f6ffed' : '#fff1f0'), // 可用綠色,不可用紅色
color: (bed.canUsed && bed.isActive && !bed.bedIsStop) ? 'black' : '#999', // 不可用時灰色文字
pointerEvents: (bed.canUsed && bed.isActive && !bed.bedIsStop) ? 'auto' : 'none' // 不可用時無法點擊
}">
<div style="font-weight: 500;">{{ bed.name }}</div>
<div style="margin-top: 4px; font-size: 12px;">
{{ bed.canUsed ? '可用' : '不可用' }}
{{ (!bed.isActive || bed.bedIsStop) ? '停用' : (bed.canUsed ? '可用' : '不可用') }}
</div>
<div
@@ -768,6 +850,7 @@
vuetify: new Vuetify(vuetify_options),
data() {
return {
activityList: [],
availableBedCount: {
male: 0,
female: 0,
@@ -782,6 +865,7 @@
bookerName: null,
bookerPhone: null,
bookerFollowerNum: null,
activityNum: null,
},
status_items: [],
},
@@ -804,6 +888,10 @@
text: '姓名',
value: 'name'
},
{
text: '性別',
value: 'sex'
},
{
text: '掛單開始時間',
value: 'checkinat'
@@ -812,10 +900,6 @@
text: '掛單結束時間',
value: 'checkoutat'
},
{
text: '房間',
value: 'roomName'
},
{
text: '床位',
value: 'bedName'
@@ -835,6 +919,13 @@
],
items: [],
showCreateGuestModal: false,
xuzhu: {
showXuzhuGuestModal: false,
currentCheckoutDate: null,
newCheckoutDate: null,
guestUuid: null,
guestBedUuid: null,
}
},
checkInGuest: {
showSelectGuadanOrderGuest: false,
@@ -848,7 +939,7 @@
bedUuid: null,
checkInAt: null,
checkOutAt: null,
statusUuid: null,
statuscode: null,
},
status: [],
},
@@ -938,8 +1029,97 @@
}
},
methods: {
//续住相關方法--------------------start
showXuzhuGuestModalMethod(guest) {
if (!guest.checkoutat) {
return;
}
this.guadanguest.xuzhu.showXuzhuGuestModal = true;
this.guadanguest.xuzhu.currentCheckoutDate = guest.checkoutat;
this.guadanguest.xuzhu.guestUuid = guest.uuid;
this.guadanguest.xuzhu.guestBedUuid = guest.bedUuid;
this.$nextTick(() => { // 确保弹窗 DOM 已渲染
const input = document.getElementById('newCheckoutDate');
if (input) {
const checkoutDate = new Date(guest.checkoutat); // 用指定日期
checkoutDate.setDate(checkoutDate.getDate() + 1); // 明天
const year = checkoutDate.getFullYear();
const month = String(checkoutDate.getMonth() + 1).padStart(2, '0');
const day = String(checkoutDate.getDate()).padStart(2, '0');
const tomorrow = `${year}-${month}-${day}`;
input.min = tomorrow; // 限制最小值
//input.value = tomorrow; // 默认选中明天
}
});
console.log(guest.checkoutat)
},
closeXuzhuGuestModalMethod() {
console.log(this.guadanguest.xuzhu.newCheckoutDate)
this.guadanguest.xuzhu.showXuzhuGuestModal = false;
this.guadanguest.xuzhu.currentCheckoutDate = null;
this.guadanguest.xuzhu.newCheckoutDate = null;
this.guadanguest.xuzhu.guestUuid = null;
this.guadanguest.xuzhu.guestBedUuid = null;
console.log(this.guadanguest.xuzhu.newCheckoutDate)
console.log(this.guadanguest.xuzhu.currentCheckoutDate)
console.log(this.guadanguest.xuzhu.guestUuid)
console.log(this.guadanguest.xuzhu.guestBedUuid)
},
xuzhuPost() {
// 校验必填
if (!this.guadanguest.xuzhu.guestUuid || !this.guadanguest.xuzhu.guestBedUuid) {
alert("GuestUuid 和 GuestBedUuid 不能为空");
return;
}
if (!this.guadanguest.xuzhu.newCheckoutDate || !this.guadanguest.xuzhu.currentCheckoutDate) {
alert("续住时间不能为空");
return;
}
const payload = {
guestUuid: this.guadanguest.xuzhu.guestUuid,
guestBedUuid: this.guadanguest.xuzhu.guestBedUuid,
currentCheckoutDate: this.guadanguest.xuzhu.currentCheckoutDate,
newCheckoutDate: this.guadanguest.xuzhu.newCheckoutDate
};
axios.post(HTTP_HOST + 'api/guadanorderguest/xuzhu', payload)
.then((res) => {
this.$refs.messageModal.open({
title: '续住成功',
message: '客人续住已处理',
status: 'success',
callback: () => {
// 弹窗关闭后的回调
try {
this.getGuadanOrderGuestByOrderNo();
}
catch (error) {
console.error("发生错误:", error.message);
} finally {
this.closeXuzhuGuestModalMethod();
}
}
});
})
.catch((error) => {
this.$refs.messageModal.open({
title: '续住失败',
message: error.response?.data?.message || '系统异常,请稍后重试',
status: 'error'
});
});
},
//续住相關方法--------------------end
getActivityList() {
axios.post(HTTP_HOST + 'api/activity/GetList?page=1&pageSize=500', { kind: 0, subject: "" })
.then((res) => {
this.activityList = res.data.list
})
},
getavailablebedcountbytime(startTime, endTime) {
axios.get('/api/region/bed/getavailablebedcountbytime',{
axios.get(HTTP_HOST + 'api/region/bed/getavailablebedcountbytime', {
params: {
startTime: startTime,
endTime: endTime
@@ -961,7 +1141,7 @@
},
confirmAllocation() {
//確認分配
axios.post('/api/region/bed/confirmallocation', {
axios.post(HTTP_HOST + 'api/region/bed/confirmallocation', {
preBeds: this.automaticBedAllocation.preBeds,
orderNo: this.guadanorder.order_form.orderNo,
checkInAt: this.guadanorder.order_form.startdate,
@@ -970,6 +1150,10 @@
.then(res => {
this.resetAutomaticBedAllocation();
this.getGuadanOrderGuestByOrderNo();
}).catch((error) => {
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
});
});
},
@@ -985,7 +1169,7 @@
CheckInAt: this.guadanorder.order_form.startdate || new Date(), // 入住時間
CheckOutAt: this.guadanorder.order_form.enddate || null // 退房時間,可為空
};
axios.post('/api/region/bed/preallocation', payload)
axios.post(HTTP_HOST + 'api/region/bed/preallocation', payload)
.then(res => {
this.automaticBedAllocation.preBeds = res.data.data;
})
@@ -1033,7 +1217,7 @@
getMultiSelectFollowers: function () {
var fm = this.automaticBedAllocation.followerModal;
var self = this;
axios.post('/api/lianyou/getfollowers', null, {
axios.post(HTTP_HOST + 'api/lianyou/getfollowers', null, {
params: {
page: fm.page,
pageSize: fm.pageSize,
@@ -1104,7 +1288,7 @@
},
getGuadanOrderById() {
if (this.guadanorder.order_form.uuid) {
axios.get('/api/guadan/getorderbyid', {
axios.get(HTTP_HOST + 'api/guadan/getorderbyid', {
params: {
orderId: this.guadanorder.order_form.uuid
}
@@ -1117,12 +1301,13 @@
this.guadanorder.order_form.bookerPhone = res.data.bookerPhone;
this.guadanorder.order_form.bookerFollowerNum = res.data.bookerFollowerNum;
this.guadanorder.order_form.uuid = res.data.uuid;
this.guadanorder.order_form.activityNum = res.data.activityNum;
})
}
},
getGuadanOrderGuestByOrderNo() {
if (this.guadanorder.order_form.orderNo) {
axios.get('/api/guadanorderguest/getbyorderno', {
axios.get(HTTP_HOST + 'api/guadanorderguest/getbyorderno', {
params: {
orderNo: this.guadanorder.order_form.orderNo
}
@@ -1132,7 +1317,7 @@
}
},
getGuadanOrderStatus() {
axios.get('/api/region/guadan/status/list')
axios.get(HTTP_HOST + 'api/region/guadan/status/list')
.then((res) => {
this.guadanorder.status_items = res.data;
})
@@ -1141,7 +1326,7 @@
if (!this.validateOrderForm()) {
return;
}
axios.post('/api/guadan/create', this.guadanorder.order_form)
axios.post(HTTP_HOST + 'api/guadan/create', this.guadanorder.order_form)
.then((res => {
this.$refs.messageModal.open({
title: '掛單提示',
@@ -1165,7 +1350,7 @@
if (!this.validateOrderForm()) {
return;
}
axios.post('/api/guadan/update', this.guadanorder.order_form)
axios.post(HTTP_HOST + 'api/guadan/update', this.guadanorder.order_form)
.then((res => {
this.$refs.messageModal.open({
title: '掛單提示',
@@ -1203,6 +1388,40 @@
//掛單相關方法-------------------end
//增加蓮友方法-------------------start
checkoutGuadanOrderGuest(guest) {
this.$refs.confirmModal.open({
message: `確定要將 ${guest.follower.u_name || ''} 退房嗎?`,
onConfirm: async () => {
try {
const response = await axios.post(`/api/guadanorderguest/checkout`, null, {
params: { uuid: guest.uuid }
});
// 成功提示
this.$refs.messageModal.open({
title: '操作成功',
message: '退房成功!',
status: 'success'
});
// 更新狀態並刷新資料
guest.statusCode = "403"; // 已退房
this.getGuadanOrderGuestByOrderNo();
} catch (error) {
console.error(error);
// 失敗提示
this.$refs.messageModal.open({
title: '操作失敗',
message: error.response?.data?.message || '退房過程中發生錯誤!',
status: 'error'
});
}
}
});
},
resetInGuest() {
this.checkInGuest.inGuest = {
uuid: null,
@@ -1212,7 +1431,7 @@
bedUuid: null,
checkInAt: null,
checkOutAt: null,
statusUuid: null,
statuscode: null,
};
},
setInGuest() {
@@ -1276,16 +1495,15 @@
},
createCheckInGuest() {
return axios.post('/api/guadanorderguest/create', this.checkInGuest.inGuest)
return axios.post(HTTP_HOST + 'api/guadanorderguest/create', this.checkInGuest.inGuest)
},
checkBedAndFollower() {
this.checkInGuest.inGuest
},
getGuadanGuestStatus() {
axios.get('/api/region/bed/status/list')
axios.get(HTTP_HOST + 'api/region/bed/status/list')
.then((res) => {
this.checkInGuest.status = res.data.filter(item => item.category === 4);
console.log(this.checkInGuest.status);
this.checkInGuest.status = res.data.filter(item => item.category === 4 && item.code != '404');
})
},
//增加蓮友方法-------------------end
@@ -1306,7 +1524,7 @@
pageSize: itemsPerPage,
searchName: this.selectGuestModal.searchNameOrPhone
};
axios.post('/api/lianyou/getfollowers', null, {
axios.post(HTTP_HOST + 'api/lianyou/getfollowers', null, {
params: params
}).then((res) => {
this.selectGuestModal.items = res.data.data
@@ -1318,7 +1536,7 @@
},
selectGuadanOrderGuest(guest) {
this.selectGuestModal.currentSelectedGuest = guest;
console.log('----------'+ guest)
console.log('----------' + guest)
this.selectGuestModal.fullNameText = guest.u_name;
this.checkInGuest.inGuest.followerNum = guest.num;
this.selectGuestModal.showSelectGuestModal = false;
@@ -1336,12 +1554,12 @@
this.getCurrentSelectBedTextByBedId(guest.bedUuid);
this.selectGuestModal.fullNameText = guest.follower?.u_name;
this.selectGuestModal.currentSelectedGuest = guest.follower;
this.checkInGuest.inGuest.statusUuid = guest.statusUuid;
this.checkInGuest.inGuest.statuscode = guest.statuscode;
},
async saveEditGuadanOrderGuest() {
try {
const res = await axios.post('/api/guadanorderguest/update', this.checkInGuest.inGuest)
const res = await axios.post(HTTP_HOST + 'api/guadanorderguest/update', this.checkInGuest.inGuest)
this.getGuadanOrderGuestByOrderNo();
this.closeCheckInModal();
} catch (error) {
@@ -1352,9 +1570,14 @@
},
deleteGuadanOrderGuest(guest) {
axios.post('/api/guadanorderguest/delete?uuid=' + guest.uuid).then((res) => {
this.guadanguest.items = this.guadanguest.items.filter(i => i.uuid != guest.uuid);
})
axios.post(HTTP_HOST + 'api/guadanorderguest/cancel?uuid=' + guest.uuid)
.then((res) => {
this.guadanguest.items = this.guadanguest.items.filter(i => i.uuid != guest.uuid);
}).catch((error) => {
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
})
});
},
confirmDeleteGuadanOrderGuest(guest) {
this.$refs.confirmModal.open({
@@ -1364,11 +1587,48 @@
}
})
},
async checkinGuadanGuest(guest) {
// 先確認操作
this.$refs.confirmModal.open({
message: '確認入住?',
onConfirm: async () => {
try {
// 發送請求到後端 API
const response = await axios.post(`/api/guadanorderguest/checkin`, null, {
params: { uuid: guest.uuid }
});
// 成功提示
this.$refs.messageModal.open({
title: '操作成功',
message: '入住成功!',
status: 'success'
});
// 更新本地列表,修改狀態為已入住 (402)
guest.statusCode = "402";
// 如果需要刷新整個列表,也可以調用
this.getGuadanOrderGuestByOrderNo();
} catch (error) {
console.error(error);
// 失敗提示
this.$refs.messageModal.open({
title: '操作失敗',
message: error.response?.data?.message || '入住過程中發生錯誤!',
status: 'error'
});
}
}
});
},
//蓮友選擇相關方法---------------end
//床位選擇相關方法----------------start
async loadRegions() {
const res = await axios.post('/api/region/getRegionList');
const res = await axios.post(HTTP_HOST + 'api/region/getRegionList');
this.region_modal.regions = res.data;
},
async loadRegionsByGender() {
@@ -1383,7 +1643,7 @@
}
}
const res = await axios.post('/api/region/getRegionListByGender', {
const res = await axios.post(HTTP_HOST + 'api/region/getRegionListByGender', {
IsMale: isMale
});
@@ -1400,7 +1660,7 @@
this.region_modal.selectedType = 'room';
this.region_modal.currentSelectBeds = room.beds;
if (this.checkInGuest.inGuest.checkInAt && this.checkInGuest.inGuest.checkOutAt) {
axios.get('/api/region/room/bed/list', {
axios.get(HTTP_HOST + 'api/region/room/bed/list', {
params: {
roomUuid: room.uuid,
StartTime: this.checkInGuest.inGuest.checkInAt,
@@ -1418,7 +1678,7 @@
},
GetRegionRoomBedListByRoomId(roomUuid) {
if (this.checkInGuest.inGuest.checkInAt && this.checkInGuest.inGuest.checkOutAt) {
axios.get('/api/region/bed/list')
axios.get(HTTP_HOST + 'api/region/bed/list')
.then((res) => {
})
@@ -1532,6 +1792,7 @@
this.loadRegions();
this.getGuadanOrderStatus();
this.getGuadanGuestStatus();
this.getActivityList();
},
computed: {

View File

@@ -0,0 +1,239 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="index.aspx.cs" Inherits="admin_guadan_guest_index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav class="search-bar">
<div class="form-item">
<label>姓名:</label>
<input v-model="search.searchName" type="text" placeholder="請輸入姓名">
</div>
<div class="form-item">
<label>入住日期:</label>
<input v-model="search.searchCheckInDate" type="date">
</div>
<div class="form-item">
<label>退房日期:</label>
<input type="date" v-model="search.searchCheckOutDate">
</div>
<div class="form-item">
<label>入住日期區間:</label>
<input type="date" v-model="search.searchCheckInDateStart"> -
<input type="date" v-model="search.searchCheckInDateEnd">
</div>
<div class="form-item">
<label>退房日期區間:</label>
<input type="date" v-model="search.searchCheckOutDateStart"> -
<input type="date" v-model="search.searchCheckOutDateEnd">
</div>
<div class="form-item buttons">
<button @click="handleSearch" type="button">查詢</button>
<button type="button" @click="clearSearch">清除查詢條件</button>
</div>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div>
<v-data-table
:headers="headers"
:items="guests"
:options.sync="options"
:server-items-length="total"
:loading="loading"
hide-default-footer
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>入住人列表</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
</template>
<template #item.checkindate ="{item}">
{{item.checkindate|timeString('YYYY-MM-DD')}}
</template>
<template #item.checkoutdate ="{item}">
{{item.checkoutdate|timeString('YYYY-MM-DD')}}
</template>
<template #item.guadanorderno="{item}">
{{item.guadanorderno}}<a :href="'/admin/guadan/create.aspx?orderId='+item.guadanorderno" class="btn btn-outline-primary">查看掛單</a>
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap>
<v-col cols="12" md="9">
<v-pagination
v-model="options.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ total }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field
v-model="options.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="options.page = parseInt($event, 10)"
></v-text-field>
</v-col>
</v-row>
</v-container>
</div>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<style>
.search-bar {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
background: #f9f9f9;
border-radius: 8px;
}
.form-item {
display: flex;
align-items: center;
gap: 6px;
}
input {
padding: 4px 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 6px 14px;
background: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #66b1ff;
}
</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() {
return {
loading: false,
search: {
searchName: null,
searchCheckInDateStart: null,//入住日期開始
searchCheckInDateEnd: null,//入住日期的結束
searchCheckOutDateStart: null,//退房日期的開始
searchCheckOutDateEnd: null,//退房日期的結束
searchCheckInDate: null,
searchCheckOutDate: null,
},
options: {
page: 1, // 當前頁
itemsPerPage: 10, // 每頁條數
sortBy: [],
sortDesc: []
},
headers: [
{ text: '姓名', value: 'name' },
{ text: '掛單單號', value: 'guadanorderno' },
{ text: '入住日期', value: 'checkindate' },
{ text: '退房日期', value: 'checkoutdate' },
{ text: '房間號', value: 'roomName' },
{ text: '狀態', value: 'statusName'},
],
guests: [], // 表格數據
total: 0,
}
},
methods: {
resetTableOptions() {
this.options = {
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: []
};
},
handleSearch() {
this.resetTableOptions();
},
clearSearch() {
this.search.searchName = null;
this.search.searchCheckInDate = null;
this.search.searchCheckOutDate = null;
this.search.searchCheckInDateStart = null;
this.search.searchCheckInDateEnd = null;
this.search.searchCheckOutDateStart = null;
this.search.searchCheckOutDateEnd = null;
this.resetTableOptions();
},
fetchGuests() {
if (this.search.searchName && this.search.searchName.includes(' ')) {
alert('搜索內容不能包含空格');
return; // 阻止繼續執行
}
if (this.loading) return;
this.loading = true;
axios.post(HTTP_HOST + 'api/guadan/guest/query/list',
{
page: this.options.page,
pageSize: this.options.itemsPerPage,
searchName: this.search.searchName,
searchCheckInDate: this.search.searchCheckInDate,
searchCheckOutDate: this.search.searchCheckOutDate,
searchCheckInDateStart: this.search.searchCheckInDateStart,
searchCheckInDateEnd: this.search.searchCheckInDateEnd,
searchCheckOutDateStart: this.search.searchCheckOutDateStart,
searchCheckOutDateEnd: this.search.searchCheckOutDateEnd,
}).then(res => {
this.guests = res.data.items; // 數據
this.total = res.data.total; // 總數
}).finally(() => {
this.loading = false;
});
}
},
watch: {
options: {
handler() {
this.fetchGuests(); // 監聽分頁、排序變化,自動載入數據
},
deep: true,
}
},
mounted() {
},
computed: {
pageCount() {
return Math.ceil(this.total / this.options.itemsPerPage)
},
}
})
</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_guest_index : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -6,16 +6,45 @@
<nav>
<a href="create.aspx" class="btn btn-primary" >新建掛單</a>
</nav>
<div class="d-flex align-items-center gap-3">
<label class="mb-0">掛單單號</label>
<input class="form-control w-auto" style="width:150px;" v-model="search.guaDanOrderNo" />
<label class="mb-0">掛單登記人</label>
<input class="form-control w-auto" style="width:150px;" v-model="search.guadanUser" />
<label class="mb-0">開始時間</label>
<input class="form-control w-auto" style="width:150px;" type="date" v-model="search.startDate" />
<label class="mb-0">結束時間</label>
<input class="form-control w-auto" style="width:150px;" type="date" v-model="search.endDate" />
<button class="btn btn-primary" type="button" @click="handleSearch">查詢</button>
<button class="btn btn-outline-primary" type="button" @click="clearSearch">清除條件</button>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div class="mx-5">
<v-data-table
:items="items"
:headers="headers">
:headers="headers"
:item-class="item => item.is_timeout ? 'row-timeout' : ''"
hide-default-footer>
<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>
<a :href="'view.aspx?orderId='+item.guaDanOrderNo" class="btn btn-primary">查看</a>
<a :href="'create.aspx?orderId='+item.guaDanOrderNo" class="btn btn-secondary"
:style="item.guadan_status?.code == 502 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''">編輯</a>
<a
class="btn btn-outline-danger"
href="#"
:style="item.guest_count != 0 ? 'pointer-events: none; opacity: 0.5; cursor: not-allowed;' : ''"
@click.prevent="item.guest_count != 0 ? null : deleteGuadanOrder(item)"
>
取消
</a>
</template>
<template #item.room="{item}">
{{item.room.name}}
@@ -23,19 +52,60 @@
<template #item.bed="{item}">
{{item.bed.name}}
</template>
<template #item.status="{item}">
{{item.status}}
<template #item.guadan_status="{item}">
{{item.guadan_status?.name}}
</template>
<template #item.start_date="{item}">
{{item.start_date | timeString('YYYY/MM/DD HH:mm')}}
{{item.start_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.end_date="{item}">
{{item.end_date | timeString('YYYY/MM/DD HH:mm')}}
{{item.end_date | timeString('YYYY/MM/DD')}}
</template>
<template #item.created_at="{item}">
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
</template>
<template #item.activity="{item}">
{{item.activity?.subject}}
</template>
<template #item.is_timeout="{item}">
{{item.is_timeout ? '已超時': '否'}}
</template>
</v-data-table>
<v-container>
<v-row class="align-baseline" wrap="false">
<v-col cols="12" md="8">
<v-pagination
v-model="options.page"
:length="pageCount">
</v-pagination>
</v-col>
<v-col class="text-truncate text-right" cols="12" md="2">
共 {{ total }} 筆, 頁數:
</v-col>
<v-col cols="6" md="1">
<v-text-field
v-model="options.page"
type="number"
hide-details
dense
min="1"
:max="pageCount"
@input="options.page = parseInt($event, 10)"
></v-text-field>
</v-col>
<!-- 每頁條數選擇 -->
<v-col cols="12" md="1">
<v-select
v-model="options.itemsPerPage"
:items="[5, 10, 20, 50]"
label="每頁條數"
dense
hide-details
style="width: 100px;"
></v-select>
</v-col>
</v-row>
</v-container>
</div>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
@@ -56,32 +126,100 @@
return {
items: [],
headers: [
{ text: '登记挂单莲友', value: 'bookerName'},
{ text: '登記掛單蓮友', value: 'bookerName' },
{ text: '掛單單號', value: 'guaDanOrderNo'},
{ text: '起始日期', value: 'start_date', align: 'center' },
{ text: '結束日期', value: 'end_date', align: 'center' },
{ text: '掛單人數', value: 'guest_count' },
{ text: '狀態', value: 'statusName', align: 'center' },
{ text: '狀態', value: 'guadan_status', align: 'center' },
{ text: '建立時間', value: 'created_at', align: 'center' },
{ text: '備註', value: 'notes', align: 'center' },
{ text: '關聯活動', value: 'activity', align: 'center' },
{ text: '超時退房', value: 'is_timeout', align: 'center' },
{ text: '操作', value: 'actions', align: 'center' }
],
options: {
page: 1, // 當前頁
itemsPerPage: 10, // 每頁條數
sortBy: [],
sortDesc: []
},
search: {
startDate: null,
endDate: null,
guadanUser: null,
guaDanOrderNo: null,
},
total: 0,
loading: false,
}
},
methods: {
resetTableOptions() {
this.options = {
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: []
};
},
handleSearch() {
let orderNo = this.search.guaDanOrderNo;
if (orderNo) {
orderNo = orderNo.replace(/\s+/g, '');
this.search.guaDanOrderNo = orderNo;
}
const val = this.search.guadanUser;
// 驗證是否包含空格
if (val && /\s/.test(val)) {
this.$refs.messageModal.open({
message: '掛單登記人不能包含空格'
});
return;
}
// 驗證長度
if (val && val.length > 10) {
this.$refs.messageModal.open({
message: '掛單登記人不能超過 10 個字'
});
return;
}
this.resetTableOptions();
},
clearSearch() {
this.search.startDate = null;
this.search.endDate = null;
this.search.guadanUser = null;
this.search.guaDanOrderNo = null;
this.resetTableOptions();
},
getGuadanOrder() {
axios.get('/api/guadan/list')
if (this.loading) return;
axios.post(HTTP_HOST + 'api/guadan/list', {
startDate: this.search.startDate,
endDate: this.search.endDate,
guadanUser: this.search.guadanUser,
guaDanOrderNo: this.search.guaDanOrderNo,
page: this.options.page,
pageSize: this.options.itemsPerPage
})
.then((res) => {
this.items = res.data;
this.items = res.data.data;
this.total = res.data.total;
}).catch((err) => {
console.log(err);
})
}).finally(() => {
this.loading = false;
});
},
deleteGuadanOrder(order) {
this.$refs.confirmModal.open({
message: '確認取消掛單?',
onConfirm: () => {
axios.post('/api/guadan/delete', null, {
axios.post(HTTP_HOST + 'api/guadan/cancel', null, {
params: {
uuid: order.uuid
}
@@ -92,7 +230,7 @@
})
}).catch((error) => {
this.$refs.messageModal.open({
message: '取消失敗'
message: error.response?.data || '取消失敗'
})
})
}
@@ -101,12 +239,27 @@
},
},
watch: {
options: {
handler() {
this.getGuadanOrder(); // 監聽分頁、排序變化,自動載入數據
},
deep: true,
}
},
mounted() {
this.getGuadanOrder();
},
computed: {
pageCount() {
return Math.ceil(this.total / this.options.itemsPerPage)
},
}
});
</script>
</asp:Content>
<style>
.row-timeout {
background-color: #ffdddd !important;
}
</style>
</asp:Content>

View File

@@ -1,143 +0,0 @@
<%@ 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,348 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="statistics_table.aspx.cs" Inherits="admin_guadan_statistics_table" %>
<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="section mb-6">
<v-card outlined class="pa-1">
<v-card-title class="headline grey--text text--darken-2">
掛單統計
</v-card-title>
<v-divider class="mb-4"></v-divider>
<v-card-text>
<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>
</v-card-text>
</v-card>
</div>
<!-- 近期床位使用統計 -->
<div class="section container">
<!-- 日期筛选区 -->
<div class="d-flex align-center flex-wrap" style="gap: 5px;">
<!-- 开始日期 -->
<v-menu
ref="menu1"
v-model="menu1"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="startDate"
label="開始日期"
prepend-icon="mdi-calendar"
readonly
v-bind="attrs"
v-on="on"
dense
outlined
></v-text-field>
</template>
<v-date-picker v-model="startDate" @input="menu1 = false"></v-date-picker>
</v-menu>
<!-- 结束日期 -->
<v-menu
ref="menu2"
v-model="menu2"
:close-on-content-click="false"
transition="scale-transition"
offset-y
min-width="auto"
>
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-model="endDate"
label="結束日期"
prepend-icon="mdi-calendar"
readonly
v-bind="attrs"
v-on="on"
dense
outlined
></v-text-field>
</template>
<v-date-picker v-model="endDate" @input="menu2 = false"></v-date-picker>
</v-menu>
<!-- 查询按钮 -->
<v-btn
color="primary"
@click="getList"
style="align-self: stretch;"
>
查詢
</v-btn>
<v-btn
color="primary"
style="align-self: stretch;"
@click="exportStatisticsToExcel">
導出下面表格數據到Excel
</v-btn>
</div>
</div>
<v-divider class="mb-4"></v-divider>
<div>
<!-- 表格 -->
<v-data-table
:items="items"
:headers="headers"
class="elevation-2"
dense
hide-default-footer
:items-per-page="10"
>
<template #item.date="{ item }">
<span>{{ item.date | timeString('YYYY-MM-DD') }}</span>
</template>
<template #item.todaytotalbookers="{item}">
<span>
{{item?.todaytotalbookers + '(男: ' + item.bookmale + ', 女:' + item.bookfemale + ')'}}
</span>
<button type="button" class="btn btn-outline-primary" @click="showBookDialog(item, '已預約','booking')">查看預約人</button>
</template>
<template #item.checkin="{item}">
<span>
{{item?.checkin + '(男: ' + item.checkinmale + ', 女:' + item.checkinfemale + ')'}}
</span>
<button type="button" class="btn btn-outline-primary" @click="showBookDialog(item, '已入住', 'checkin')">查看入住人</button>
</template>
<template #item.bedusagerate="{ item }">
{{ ((item.todaytotalbookers / bedcount) * 100).toFixed(2) + '%' }}
</template>
<template #item.roomcount="{item}">
{{roomcount}}
</template>
<template #item.bedcount="{item}">
{{bedcount}}
</template>
</v-data-table>
</div>
<div>
<v-dialog v-model="bookerDialog.show" max-width="500">
<v-card
style="width: 40vw; height: 50vh; display: flex; flex-direction: column;"
>
<!-- 标题 -->
<v-card-title class="text-h5" style="flex: 0 0 auto; background-color: #1976D2; color: white; padding: 16px;">
{{ bookerDialog.title }}
</v-card-title>
<!-- 内容撑满 -->
<v-card-text
style="flex: 1; overflow-y: auto;"
>
<v-data-table
:items="bookerDialog.items"
:headers="bookerDialog.headers">
</v-data-table>
</v-card-text>
<!-- 底部按钮 -->
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="closeBookDialog">關閉</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">
<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: 'date' },
{ text: '房间数量', value: 'roomcount' },
{ text: '床位数量', value: 'bedcount' },
{ text: '预约人数', value: 'todaytotalbookers' },
{ text: '已入住人数', value: 'checkin'},
{ text: '可用床位', value: 'availableBeds' },
{ text: '床位利用率', value: 'bedusagerate' }
],
startDate: null,
endDate: null,
menu1: false,
menu2: false,
bedcount: 0,
roomcount: 0,
guadanStatistics: {
guadanTotalCount: 0,
guadanCurrentCount: 0,
guadanPeopleTotal: 0,
guadanPeopleMale: 0,
guadanPeopleFemale: 0,
guadanPeopleCurrent: 0,
guadanPeopleCurrentMale: 0,
guadanPeopleCurrentFemale: 0
},
bookerDialog: {
title: "",
show: false,
items: [],
headers: [
{ text: '姓名', value: 'name' },
{ text: "性别", value: 'gender'}
]
}
}
},
methods: {
showBookDialog(item, title, type = null) {
this.bookerDialog.title = title;
this.bookerDialog.show = true;
if (type === 'booking') {
this.getBookerDialogBookingItems(item.date);
}
else if (type === 'checkin') {
this.getBookerDialogCheckInItems(item.date);
}
},
closeBookDialog() {
this.bookerDialog.show = false;
this.bookerDialog.title = "";
this.bookerDialog.items = [];
},
async getBookerDialogBookingItems(date) {
try {
const res = await axios.get(HTTP_HOST + 'api/guadan/guest/booking/list', {
params: {
date: date
}
});
this.bookerDialog.items = res.data;
} catch(error) {
}
},
async getBookerDialogCheckInItems(date) {
try {
const res = await axios.get(HTTP_HOST + 'api/guadan/guest/checkin/list', {
params: {
date: date
}
});
this.bookerDialog.items = res.data;
} catch (error) {
}
},
async getList() {
try {
const res = await axios.get(HTTP_HOST + 'api/guadan/guadanstatisticstable/list', {
params: {
start: this.startDate || '',
end: this.endDate || ''
}
});
this.items = res.data.statistics;
this.roomcount = res.data.roomcount;
this.bedcount = res.data.bedcount;
} catch (e) {
console.error(e);
}
},
exportStatisticsToExcel() {
if (!this.items || !this.items.length) {
console.warn("没有数据可导出");
return;
}
// 1. 取 items 数组并格式化
const sheetData = this.items.map(item => ({
日期: item.date.split('T')[0], // 格式化成 YYYY-MM-DD
预订人数: item.todaytotalbookers,
入住人数: item.checkin
}));
// 2. 转换成 XLSX Sheet
const ws = XLSX.utils.json_to_sheet(sheetData);
// 3. 创建 Workbook 并添加 Sheet
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "统计数据");
// 4. 写入 Excel 并下载
const wbout = XLSX.write(wb, { bookType: "xlsx", type: "array" });
saveAs(new Blob([wbout], { type: "application/octet-stream" }), "statistics.xlsx");
},
GetGuadanStatistics() {
axios.get(HTTP_HOST + 'api/guadanStatistics/GetGuadanStatistics')
.then((res) => {
this.guadanStatistics = res.data.guadanStatistics;
})
}
},
mounted() {
this.getList();
this.GetGuadanStatistics();
}
})
</script>
<!-- CDN 方式引入 XLSX 和 FileSaver -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></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_table : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

539
web/admin/guadan/view.aspx Normal file
View File

@@ -0,0 +1,539 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="view.aspx.cs" Inherits="admin_guadan_view" %>
<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">
<fieldset class="border rounded p-4 mb-5 shadow-sm bg-white">
<legend class="w-auto px-3 font-weight-bold text-primary">掛單資訊</legend>
<!-- 🟢 區塊一:掛單資訊 -->
<div class="border rounded p-3 bg-white shadow-sm" style="pointer-events: none; user-select: none; background: #1c5bd9; padding: 5px;">
<h6 class="text-secondary mb-3">📝掛單資訊</h6>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">掛單單號(不可修改)</label>
<div class="col-sm-4 text-left">
<input class="form-control" v-model="guadanorder.order_form.orderNo" readonly />
</div>
<label class="col-sm-2 col-form-label text-center">關聯活動</label>
<div class="col-sm-4">
<select class="form-control" v-model="guadanorder.order_form.activityNum" >
<option :value="null">未關聯</option>
<option v-for="activity in activityList" :key="activity.num" :value="activity.num">
{{activity.subject}}
</option>
</select>
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">
預約開始日期
</label>
<div class="col-sm-4 text-left">
<input class="form-control" type="date" v-model="guadanorder.order_form.startdate" />
</div>
<label class="col-sm-2 col-form-label text-center">
預約結束日期
</label>
<div class="col-sm-4">
<input class="form-control" type="date" v-model="guadanorder.order_form.enddate" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">預定人姓名</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerName" />
</div>
<label class="col-sm-2 col-form-label text-center">預定人電話</label>
<div class="col-sm-4">
<input class="form-control" v-model="guadanorder.order_form.bookerPhone" />
</div>
</div>
<div class="form-group row mt-3">
<label class="col-sm-2 col-form-label text-center">備註</label>
<div class="col-sm-4">
<textarea class="form-control" v-model="guadanorder.order_form.note"></textarea>
</div>
</div>
</div>
</fieldset>
<fieldset class="border rounded p-4 mb-5 shadow-sm bg-white">
<!-- 表格標題緊貼表格上方,居中 -->
<div class="d-flex align-items-center mb-3">
<!-- 中間標題flex-grow撐開居中 -->
<div class="flex-grow-1 text-center">
<h5 class="text-primary fw-bold mb-0">
<i class="bi bi-people-fill me-2"></i>掛單蓮友
</h5>
</div>
</div>
<!-- v-data-table 表格 -->
<v-data-table :headers="guadanguest.headers" :items="guadanguest.items" class="elevation-1 rounded" dense>
<template #item.checkinat="{item}">
{{item.checkinat |timeString('YYYY-MM-DD')}}
</template>
<template #item.checkoutat="{item}">
{{item.checkoutat |timeString('YYYY-MM-DD')}}
</template>
<template v-slot:item.name="{item}">
{{item.follower?.u_name}}
</template>
<template v-slot:item.sex="{item}">
{{item.follower?.sex}}
</template>
</v-data-table>
</fieldset>
</asp:Content>
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
</asp:Content>
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
<style>
/* 調整 fieldset 風格 */
fieldset {
border: 1px solid #dee2e6;
border-radius: 0.5rem;
background-color: #fff;
}
legend {
font-size: 1.25rem;
font-weight: 700;
color: #0d6efd;
width: auto;
padding: 0 0.75rem;
}
.form-group label {
font-weight: 600;
}
/* 按鈕置右 */
.text-right {
text-align: right;
}
/* 選擇床位相關 */
.tree,
.tree ul {
list-style: none;
margin: 0;
padding-left: 1rem;
}
.toggle-icon {
cursor: pointer;
user-select: none;
width: 1rem;
display: inline-block;
color: #007bff;
}
.region-item-label {
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
display: inline-block;
}
.region-item-label.selected {
background-color: #eaf4ff;
color: #0d6efd;
font-weight: bold;
}
.selected-room {
background-color: #cce5ff;
font-weight: bold;
}
/* 選擇床位相關 */
</style>
<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, index) in item.children" :key="child.id + '-' + index">
<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 + '床) ' + (room.gender === true ? '(男客房)' : room.gender === false ? '(女客房)' : '') }}
</span>
</li>
</ul>
</div>
`
});
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 {
activityList: [],
availableBedCount: {
male: 0,
female: 0,
},
guadanorder: {
order_form: {
uuid: '<%= Request.QueryString["orderid"] %>' || null,
startdate: null,
enddate: null,
note: null,
orderNo: null,
bookerName: null,
bookerPhone: null,
bookerFollowerNum: null,
activityNum: null,
},
status_items: [],
},
guadanguest: {
guest: {
uuid: null, // int?
fullName: '', // string
gender: null, // int?
phone: '', // string
idNumber: '', // string
birthday: null, // Date (建議用 date picker)
email: '', // string
address: '', // string
emergencyContact: '', // string
emergencyPhone: '', // string
status: null, // int?
notes: '' // string
},
headers: [{
text: '姓名',
value: 'name'
},
{
text: '性別',
value: 'sex'
},
{
text: '掛單開始時間',
value: 'checkinat'
},
{
text: '掛單結束時間',
value: 'checkoutat'
},
{
text: '床位',
value: 'bedName'
},
{
text: '狀態',
value: 'statusName'
},
{
text: '備註',
value: 'note'
},
{
text: '',
value: 'actions'
},
],
items: [],
showCreateGuestModal: false,
xuzhu: {
showXuzhuGuestModal: false,
currentCheckoutDate: null,
newCheckoutDate: null,
guestUuid: null,
guestBedUuid: null,
}
},
checkInGuest: {
showSelectGuadanOrderGuest: false,
isEdit: false,
inGuest: {
uuid: null,
orderNo: null,
followerNum: null,
roomUuid: null,
bedUuid: null,
checkInAt: null,
checkOutAt: null,
statuscode: null,
},
status: [],
},
region_modal: {
regions: [],
currentSelectRegion: null,
currentSelectRoom: null,
currentSelectBeds: [],
currentSelectBed: null,
showSelectBedModal: false,
selectedId: null, // 被選中項目ID
selectedType: null, // 'region' 或 'room'
expandAllFlag: false, // 控制全部展開
collapseAllFlag: false, // 控制全部收起
currentSelectBedText: null,
},
selectGuestModal: {
showSelectGuestModal: false,
currentSelectedGuest: null,
fullNameText: null,
headers: [{
text: '姓名',
value: 'u_name'
},
{
text: '電話',
value: 'phone'
},
{
text: '',
value: 'actions'
},
],
items: [],
options: { //v-data-table參數
page: 1,
itemsPerPage: 10,
sortBy: [],
sortDesc: [],
multiSort: false,
},
page: 1,
pageSize: 10,
count: 0,
footer: {
showFirstLastPage: true,
disableItemsPerPage: true,
itemsPerPageAllText: '',
itemsPerPageText: '',
},
searchNameOrPhone: null,
},
automaticBedAllocation: {
showModal: false,
// 蓮友選擇彈出視窗
followerModal: {
showModal: false,
followerList: [],
selectedFollowerItems: [],
page: 1,
pageSize: 10,
totalCount: 0,
searchNameOrPhone: '',
headers: [
{ text: '姓名', value: 'u_name' },
{ text: '電話', value: 'phone' },
{ text: '操作', value: 'actions', sortable: false }
],
},
// 已選擇的待分配列表
selectedFollowers: [],
preBeds: [],
headers: [
{ text: '姓名', value: 'u_name' },
{ text: '電話', value: 'phone' },
{ text: '性別', value: 'sex' },
{ text: '預分配床位', value: 'prebed' },
{ text: '', value: 'actions' }
],
},
}
},
methods: {
getActivityList() {
axios.post(HTTP_HOST + 'api/activity/GetList?page=1&pageSize=500', { kind: 0, subject: "" })
.then((res) => {
this.activityList = res.data.list
})
},
//掛單相關方法-------------------start
validateOrderForm() {
if (!this.guadanorder.order_form.startdate) {
this.$refs.messageModal.open({
message: '請輸入必填資訊'
});
return false;
}
if (!this.guadanorder.order_form.enddate) {
this.$refs.messageModal.open({
message: '請輸入必填資訊'
});
return false;
}
if (!this.guadanorder.order_form.bookerName) {
this.$refs.messageModal.open({
message: '請輸入姓名'
});
return false;
}
if (this.guadanorder.order_form.bookerPhone && !/^\d{2,4}-?\d{3,4}-?\d{3,4}$/.test(this.guadanorder.order_form.bookerPhone)) {
this.$refs.messageModal.open({
message: '電話輸入有誤'
});
return false;
}
return true;
},
getGuadanOrderById() {
if (this.guadanorder.order_form.uuid) {
axios.get(HTTP_HOST + 'api/guadan/getorderbyid', {
params: {
orderId: this.guadanorder.order_form.uuid
}
}).then((res) => {
this.guadanorder.order_form.note = res.data.notes;
this.guadanorder.order_form.startdate = res.data.startDate;
this.guadanorder.order_form.enddate = res.data.endDate;
this.guadanorder.order_form.orderNo = res.data.guaDanOrderNo;
this.guadanorder.order_form.bookerName = res.data.bookerName;
this.guadanorder.order_form.bookerPhone = res.data.bookerPhone;
this.guadanorder.order_form.bookerFollowerNum = res.data.bookerFollowerNum;
this.guadanorder.order_form.uuid = res.data.uuid;
this.guadanorder.order_form.activityNum = res.data.activityNum;
})
}
},
getGuadanOrderGuestByOrderNo() {
if (this.guadanorder.order_form.orderNo) {
axios.get(HTTP_HOST + 'api/guadanorderguest/getbyorderno', {
params: {
orderNo: this.guadanorder.order_form.orderNo
}
}).then((res => {
this.guadanguest.items = res.data;
}))
}
},
//掛單相關方法-------------------end
},
watch: {
'guadanorder.order_form.orderNo'(newValue, oldValue) {
if (newValue) {
this.getGuadanOrderGuestByOrderNo();
}
},
'selectGuestModal.options': {
handler() {
this.getGuadanFollowers();
},
deep: true
},
// 分頁變化時自動刷新
'automaticBedAllocation.followerModal.page': function () {
this.getMultiSelectFollowers();
},
'automaticBedAllocation.followerModal.pageSize': function () {
this.getMultiSelectFollowers();
}
},
mounted() {
if (this.guadanorder.order_form.uuid) {
this.getGuadanOrderById();
this.getGuadanOrderGuestByOrderNo();
}
this.getActivityList();
},
computed: {
pageCount() {
return Math.ceil(this.selectGuestModal.count / this.selectGuestModal.pageSize)
},
pageCount2: function () {
var fm = this.automaticBedAllocation.followerModal;
return Math.ceil(fm.totalCount / fm.pageSize) || 1;
}
},
});
</script>
</asp:Content>

View File

@@ -5,7 +5,7 @@ using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class admin_guadan_statistics : MyWeb.config
public partial class admin_guadan_view : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{

View File

@@ -19,53 +19,24 @@ public partial class admin_printpw_index : MyWeb.function
private string previousOrderno = "";
protected void Page_Load(object sender, EventArgs e)
{
//Response.Write("item:"+Request["item"]+"<br>");
//Response.Write("file:"+Request["file"]+"<br>");
//Response.Write("list:"+Request["list"]+"<br>");
//if (!IsPostBack)
//{
this.Title = "預覽牌位";
if (Request.HttpMethod == "POST")
{
/*if (!String.IsNullOrEmpty(Request["item"]) &&
!String.IsNullOrEmpty(Request["file"]) &&
!String.IsNullOrEmpty(Request["list"])
)*/
if (!String.IsNullOrEmpty(Request["order_no"]))
{
var order_no = Request["order_no"];
var oderList = _db.pro_order_detail
.Where(u => u.order_no == order_no && u.printed_files != null)
.Where(u => u.order_no == order_no)
.Where(u => (u.parent_num != null)
|| u.actItem.subject.Contains("牌")
|| !string.IsNullOrEmpty(u.f_num_tablet))
//因為目前在[pro_order_detail]表中,沒有辦法區分那些項目是牌位,
//只有根據某些欄位來做部分篩選,可能會篩選錯
//但是為什麼沒有欄位可以區分一個項目是不是牌位呢?
.Select(u => new { detail_num = u.num, actItem_num = u.actItem_num }).ToArray();
Repeater1.DataSource = oderList;
Repeater1.DataBind();
}
else if (!String.IsNullOrEmpty(Request["activity_num"]))
{
//var _details = Newtonsoft.Json.JsonConvert.DeserializeObject<int[]>(Request["list"]);
//string json = "";
//using (System.IO.StreamReader oSR = new System.IO.StreamReader(Request.InputStream))
// json = oSR.ReadToEnd();
int activity_num = Convert.ToInt32(Request["activity_num"]);
//Repeater1.DataSource = _details;
var orderList = _db.pro_order.Where(u => u.activity_num == activity_num).Select(u => u.order_no).ToList();
var gdzOrderList = _db.pro_order_detail.Where(u => orderList.Contains(u.order_no) && u.print_id.Contains("主") ).Select(u=>u.order_no).Distinct().ToArray();
var datalist = _db.pro_order_detail
.Where(u => gdzOrderList.Contains(u.order_no) && u.print_id.Contains("主") && u.parent_num != null && u.printed_files != null)
.OrderBy(o => o.order_no)
.ThenBy(o => o.actItem_num)
.ThenBy(o=>o.print_id)
.Select(u => new {detail_num=u.num ,actItem_num=u.actItem_num})
.ToArray();
//List<int> ints = new List<int>();
//ints.Add(12133);
Repeater1.DataSource = datalist;
Repeater1.DataBind();
if (!String.IsNullOrEmpty(Request["title"]))
{
this.Title += " - " + Request["title"];
}
}
else
{
Response.Clear();
@@ -74,9 +45,6 @@ public partial class admin_printpw_index : MyWeb.function
}
}
//}
}
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
@@ -248,8 +216,8 @@ public partial class admin_printpw_index : MyWeb.function
catch (Exception ex)
{
var msg = ex.Message;
ret[0] = "??:" + msg;
ret[1] = "??";
ret[0] = "";
ret[1] = "";
}
return ret;
}

View File

@@ -24,6 +24,8 @@
@page a4l {
size: a4 landscape;
margin: 0;
margin-top:6mm;
margin-left:6mm;
}
@page a5 {
@@ -133,6 +135,22 @@ pre {
line-height: calc(var(--fs_w) * 1.0);
}
.text-block.fit-text.add-space {
letter-spacing: 0.5em;
}
.text-block.fit-text.mid_text.add-space {
padding-top: 0.5em;
}
.text-block.fit-text.mid_text.add-space.add-space-3 {
letter-spacing: 1.5em;
padding-top: 1.5em;
}
body.tblt-m .left_text {
--font-max: 30pt;
}
body.tblt-m .top_text_2 {
right: 8mm;
}
.vertical {
writing-mode: vertical-rl;
/*text-orientation: upright;*/
@@ -367,6 +385,10 @@ pre {
left: 2mm;
}
.top_text_3 {
display: none;
}
/*舊式紙張*/
body.tablet-l {
--page-w: 274mm;
@@ -548,6 +570,9 @@ body.tblt-l.a3.l2b .mid_text {
--divh: 60mm;
top: 40%;
}
body.tblt-l.a3 .left_text {
--font-max: 36pt;
}
body.tblt-l.a3.l2b .mid_text_2 {
--divh: 180mm;
@@ -638,7 +663,8 @@ body.tblt-m.a4-mrg {
}
body.tblt-m.a4-mrg .page {
--page-w: 100mm;
--page-w: 95mm;
margin-left:5mm;
float: left;
}
@@ -763,9 +789,8 @@ body.tblt-xs {
}
body.tblt-xs.a3-mrg .page {
/*--page-w: 57mm;*/
--page-w: 59mm;
--page-h: 100mm;
--page-w: 57mm;
--page-h: 98mm;
page-break-after: auto;
float: left;
}
@@ -775,13 +800,11 @@ body.tblt-xs.a4l-mrg {
}
body.tblt-xs.a4l-mrg .page {
/* --page-w: 57mm; */
--page-w: 59mm;
--page-h: 100mm;
--page-w: 57mm;
--page-h: 98mm;
page-break-after: auto;
float: left;
}
body.tblt-xs .top_text_1 {
font-size: 8pt;
}
@@ -800,9 +823,9 @@ body.tblt-xs .mid_text_2 {
}
body.tblt-xs .right_text {
--divw: 12mm;
--divw: 10mm;
--divh: 60mm;
right: 0mm;
right: 2mm;
top: 25mm;
}
@@ -825,8 +848,7 @@ body.tblt-xs .top_text_2{
/*[標準X2B]隨喜牌位-佛力超薦-往生蓮位-A3(直)*/
right: 2mm;
right: 3.0mm;
right: 3.5mm;
/* right: 4.0mm; */
right: 5mm;
}
body.tblt-xs .txt_up {
@@ -1075,35 +1097,4 @@ body.prayer-h pre {
/*[標準X1A]隨喜牌位-佛光注照-長生祿位-A4(橫) */
* {
outline:0 none !important;
}
/*让自定义的分页生效在某个div后面产生class-page从而分页当牌位不是同一家人的时候就分页*/
@media print {
.page-break {
display: block !important;
width: 100%;
height: 1px;
visibility: hidden;
page-break-after: always;
}
.page {
page-break-inside: auto !important;
}
@page {
orphans: 1;
widows: 1;
}
.d-flex {
display: block !important;
}
.page-break::after {
content: "";
display: block;
height: 1px;
visibility: hidden;
}
}
}

View File

@@ -32,17 +32,25 @@ function init_print() {
//console.log(i, $(this).html(),$(this).text());
//console.log(i, txt==htm);
//console.log(i, txt_arr, line, line_len);
line_len = Math.ceil(line_len / 5) * 5;
line_len = Math.ceil(line_len / 3) * 3;
//if(line>1){
css = $(this).attr("style")||"";
if(css.length>0){
css += ";";
}
css += "--lines:" + line + ";--line_len:" + line_len + ";";
//debugger;
//字少時, 加空間
let allShort = txt_arr2.every(line => line.trim().length <= 7);
let addSpaceClass = allShort ? "add-space" : "";
allShort = txt_arr2.every(line => line.trim().length <= 3);
addSpaceClass += allShort ? " add-space-3" : "";
htm_lines = txt_arr2.join("<br>");
$(this).attr("style", css);
$(this).html(htm_lines);
$(this)
.attr("style", css)
.addClass(addSpaceClass)
.html(htm_lines);
//}
//console.log(i, line, line_len, css,txt);
});

View File

@@ -153,6 +153,9 @@
<template #item.slot_type ="{item}">
{{item.detail.actItem?.substring(0,3)}}
</template>
<template #item.up_time ="{item}">
{{item.up_time|timeString('YYYY-MM-DD')}}
</template>
<template #item.print="{item}">
<v-icon color="blue" class="mr-2" @click="yulan_single(item);" >
mdi-printer

View File

@@ -16,7 +16,7 @@
</div>
<div class="card-body">
<asp:Literal ID="L_msg" runat="server" />
<asp:HiddenField ID="HF_Id" runat="server" />
<asp:HiddenField ID="HF_Code" runat="server" />
<div class="form-group mb-3">
<label for="TB_Name">名稱</label>

View File

@@ -1,35 +1,36 @@
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 (!IsPostBack)
{
if (Guid.TryParse(Request.QueryString["statusid"], out Guid id))
var code = Request.QueryString["code"];
if (!string.IsNullOrEmpty(code))
{
LoadData(id);
LoadData(code);
L_title.Text = "編輯區域類型";
}
var categoryList = RegionRoomBedStatus.GetCategoryList();
// 假设你的下拉控件ID叫 ddlCategory
TB_Category.DataSource = categoryList;
TB_Category.DataTextField = "Text"; // 示文字
TB_Category.DataValueField = "Value"; // 选项
TB_Category.DataTextField = "Text"; // 示文字
TB_Category.DataValueField = "Value"; // 選項
TB_Category.DataBind();
TB_Category.Items.Insert(0, new ListItem("--请选择分类--", ""));
TB_Category.Items.Insert(0, new ListItem("--請選擇分類--", ""));
}
}
private void LoadData(Guid id)
private void LoadData(string code)
{
var rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id);
var rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Code == code);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>找不到資料</div>";
@@ -37,22 +38,24 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config
return;
}
HF_Id.Value = rt.Uuid.ToString();
HF_Code.Value = rt.Code; // ✅ 以 Code 為唯一識別
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))
var code = HF_Code.Value; // ✅ 用隱藏欄位的 Code 判斷
if (!string.IsNullOrEmpty(code))
{
// 更新
rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Uuid == id);
rt = _db.RegionRoomBedStatus.FirstOrDefault(r => r.Code == code);
if (rt == null)
{
L_msg.Text = "<div class='alert alert-danger'>資料不存在</div>";
@@ -63,19 +66,20 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config
{
// 新增
rt = new RegionRoomBedStatus();
rt.Uuid = Guid.NewGuid();
rt.Code = TB_Code.Text.Trim(); // ✅ 以 Code 當主鍵
_db.RegionRoomBedStatus.Add(rt);
}
rt.Name = TB_Name.Text.Trim();
if (rt.Name.Length == 0)
if (string.IsNullOrEmpty(rt.Name))
{
L_msg.Text = "<div class='alert alert-danger'>名稱不能空</div>";
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))
if (int.TryParse(TB_Category.SelectedValue, out int category))
{
rt.Category = category;
}
@@ -83,16 +87,18 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config
{
rt.Category = null;
}
_db.SaveChanges();
L_msg.Text = "<div class='alert alert-success'>儲存成功</div>";
// 如果是新增,更新隱藏欄位並切換標題為編輯
if (HF_Id.Value == "")
// 如果是新增,更新隱藏欄位並切換標題
if (string.IsNullOrEmpty(HF_Code.Value))
{
HF_Id.Value = rt.Uuid.ToString();
HF_Code.Value = rt.Code;
L_title.Text = "編輯區域類型";
}
Response.Redirect("index.aspx");
}
catch (Exception ex)
@@ -100,4 +106,4 @@ public partial class admin_region_bed_bedstatus_create : MyWeb.config
L_msg.Text = $"<div class='alert alert-danger'>錯誤:{ex.Message}</div>";
}
}
}
}

View File

@@ -20,7 +20,7 @@
:loading="loading"
>
<template #item.actions="{item}">
<a :href="'create.aspx?statusid='+item.uuid" class="btn btn-primary"><i class="mdi mdi-pencil"></i>修改</a>
<a :href="'create.aspx?code='+item.code" class="btn btn-primary"><i class="mdi mdi-pencil"></i>修改</a>
<button
type="button"
class="btn btn-outline-danger"
@@ -46,11 +46,10 @@
data() {
return {
headers: [
{ text: 'Id', value: 'id' },
{ text: '狀態名稱', value: 'name' },
{ text: '狀態代碼', value: 'code' },
{ text: '描述', value: 'description' },
{ text: '状态分类', value: 'categoryName'},
{ text: '状态分类', value: 'categoryName' },
{ text: '', value: 'actions' }
],
items: [],
@@ -59,7 +58,7 @@
},
methods: {
getStatusList() {
axios.get('/api/region/bed/status/list')
axios.get(HTTP_HOST + 'api/region/bed/status/list')
.then((res) => {
this.items = res.data
this.loading = false;
@@ -74,11 +73,11 @@
})
},
deleteStatus(item) {
axios.post('/api/region/bed/status/delete', null, {
params: { id: item.uuid }
axios.post(HTTP_HOST + 'api/region/bed/status/delete', null, {
params: { code: item.code }
})
.then(() => {
this.items = this.items.filter(i => i.uuid != item.uuid);
this.items = this.items.filter(i => i.code != item.code);
this.$refs.messageModal.open({
title: '操作成功',
message: '刪除成功!',

View File

@@ -108,11 +108,12 @@
</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 v-for="region in regions" :key="region.uuid" class="region-block mb-4"
:style="region.isStop ? { pointerEvents: 'none', opacity: '0.6', backgroundColor: '#f8d7da' } : {}">
<h2 class="region-title mb-3">{{region.isStop?(region.regionPath + '(已停用)'): 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 v-for="room in region.room" :key="room.ruid" class="col-12 col-md-6 col-lg-6">
<div class="card h-100 shadow-sm" style="min-height: 300px; max-height: 400px; overflow-y: auto; border-radius: 0.5rem;">
<!-- 上部:房間名稱 -->
@@ -138,20 +139,26 @@
<thead>
<tr>
<th scope="col">床位名稱</th>
<th scope="col">是否可用</th>
<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>
<td>
<button type="button" class="btn btn-primary">
快速掛單
</button>
</td>
<td>
{{bed.statusname}}
</td>
</tr>
</tbody>
</table>
@@ -165,43 +172,49 @@
</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>
<div>
<v-dialog v-model="bedSchedule.dialogVisible" max-width="900px">
<v-card
style="min-height:50vh; max-height:80vh; display:flex; flex-direction:column;"
>
<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>
<!-- 关键改动flex:1 1 auto; min-height:0; overflow-y:auto -->
<v-card-text style="flex:1 1 auto; min-height:0; overflow-y:auto;">
<div style="min-height:0;">
<v-data-table
:headers="bedSchedule.scheduleHeaders"
:items="bedSchedule.selectedBed?.schedules || []"
:items-per-page="9999"
class="elevation-1"
dense
hide-default-footer
>
<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>
</div>
</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>
<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>
@@ -251,6 +264,7 @@
{ text: '使用日期', value: 'scheduledate' },
{ text: '掛單單號', value: 'guaDanOrderNo' },
{ text: '標題', value: 'title' },
{ text: '掛單人', value: 'usename' },
{ text: '查看掛單', value: 'actions' },
],
},
@@ -302,10 +316,18 @@
unoccupied: this.filter.unoccupied,
gender: this.filter.Gender,
};
axios.post('/api/region/list', payload)
axios.post(HTTP_HOST + 'api/region/list', payload)
.then((res) => {
this.regions = res.data.regions;
this.summary = res.data.summary; // 保存後端統計
try {
this.regions.sort((a, b) => {
return Number(a.isStop) - Number(b.isStop);
});
}
catch (error) {
console.log(error)
}
})
.catch((err) => {
console.error('API 錯誤', err);
@@ -325,7 +347,7 @@
console.log(this.filter.Gender);
},
getRegionWithRoom() {
axios.get('/api/region/regionwithroom')
axios.get(HTTP_HOST + 'api/region/regionwithroom')
.then((res) => {
this.filter.areas = res.data;
})
@@ -381,9 +403,6 @@
return { maleBeds, femaleBeds };
}
}
})
</script>
</asp:Content>

View File

@@ -0,0 +1,217 @@
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="handle_bed_in_used.aspx.cs" Inherits="admin_region_handle_bed_in_used" %>
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
<nav>
<div>
<label style="display: inline-block;">區域:</label>
<select style="display: inline-block; min-width:150px; max-width:20%;"
class="form-select"
v-model="selectedRegionUuid"
:disabled="isFromUrl"
@change="onRegionChange"
><option :value="null">請選擇區域</option>
<option
v-for="region in regions"
:key="region.uuid"
:value="region.uuid"
>
{{ region.name }}
</option>
</select>
<label style="display: inline-block;margin-left: 20px;">客房:</label>
<select style="display: inline-block; min-width:200px; max-width:20%;"
class="form-select"
v-model="selectedRoomUuid"
:disabled="isFromUrl"
@change="onRoomChange"
>
<option :value="null">請選擇房間</option>
<option v-for="room in rooms" :key="room.uuid" :value="room.uuid">{{room.fullName}}</option>
</select>
<label style="display: inline-block;margin-left: 20px;">床位:</label>
<select style="display: inline-block; width:100px;"
class="form-select"
v-model="selectedBedUuid"
:disabled="isFromUrl"
></select>
</div>
</nav>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div>
<v-data-table
:items="items"
show-select
v-model:selected="selectedItems"
item-key="bedUuid"
:headers="headers">
<template #item.actions="{item}">
<button class="btn btn-outline-danger" type="button" @click="confirmAndCancelSingleBedBooking(item)">取消預約</button>
</template>
<template #item.guadan_during="{item}">
{{item.guadan_during.checkInAt|timeString('YYYY-MM-DD')}} - {{item.guadan_during.checkOutAt|timeString('YYYY-MM-DD')}}
</template>
<template #item.status="{item}">
{{item.status.name}}
</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 {
regionUuidFromUrl: '<%= Request.QueryString["region"] %>' || null,
roomUuidFromUrl: '<%= Request.QueryString["room"] %>' || null,
bedUuidFromUrl: '<%= Request.QueryString["bed"] %>' || null,
// 用戶選擇的值
selectedRegionUuid: null,
selectedRoomUuid: null,
selectedBedUuid: null,
regions: [],
rooms: [],
bed: [],
selectedItems: [],
items: [],
headers: [
{ text: '床位名稱', value: 'fullName' },
{ text: '掛單號:', value: 'guaDanOrderNo' },
{ text: '掛單人', value: 'u_name' },
{ text: '掛單時間', value: 'guadan_during' },
{ text: '掛單狀態', value: 'status' },
{ text: '', value: 'actions' },
]
}
},
methods: {
onRegionChange() {
console.log("選擇的區域 UUID:", this.selectedRegionUuid);
this.selectedRoomUuid = null;
this.selectedBedUuid = null;
this.GetInUsedBed();
this.GetRoomList();
},
onRoomChange() {
console.log("選擇的客房 UUID:", this.selectedRoomUuid);
this.GetInUsedBed();
},
async GetInUsedBed() {
//獲取已經預約或者入住的床位
try {
const payload = {
regionUuid: this.selectedRegionUuid || null,
roomUuid: this.selectedRoomUuid || null,
bedUuid: this.selectedBedUuid || null
};
const response = await axios.post(HTTP_HOST + "api/bed/inuse/list", payload);
// 假設返回的就是床位數組
this.items = response.data;
console.log("已獲取床位:");
} catch (error) {
console.error("獲取床位失敗:", error);
}
},
async confirmAndCancelSingleBedBooking(item) {
// 先彈出確認彈出視窗
this.$refs.confirmModal.open({
message: `確定要取消床位 ${item.name || ''} 的所有預約嗎?`,
onConfirm: async () => {
try {
const payload = {
bedUuid: item.bedUuid || null
};
const response = await axios.post(
HTTP_HOST + "api/bed/inuse/cancel/singlebed/booking",
payload
);
// 刷新床位列表
this.GetInUsedBed();
// 成功提示
console.log("取消成功:", item.bedUuid);
this.$refs.messageModal.open({
title: '取消成功',
message: response?.data?.message || '取消成功!',
status: 'success'
});
} catch (error) {
console.error("取消失敗:", error);
this.$refs.messageModal.open({
title: '取消失敗',
message: error.response?.data?.message || '取消過程中發生錯誤!',
status: 'error'
});
}
}
});
},
async CancelAllBedBooking() {
//取消符合條件的所有床位的所有預約
},
async GetRegionList() {
try {
const response = await axios.get(
HTTP_HOST + "api/bed/inuse/region/list"
);
this.regions = response.data;
} catch (error) {
}
},
async GetRoomList() {
try {
const response = await axios.get(
HTTP_HOST + "api/bed/inuse/room/list", {
params: {
regionUuid: this.selectedRegionUuid
}
}
);
this.rooms = response.data;
} catch (error) {
}
},
},
watch: {
},
mounted() {
this.selectedRegionUuid = this.regionUuidFromUrl;
this.selectedRoomUuid = this.roomUuidFromUrl;
this.selectedBedUuid = this.bedUuidFromUrl;
this.GetInUsedBed();
this.GetRegionList();
this.GetRoomList();
},
computed: {
// 判斷是否來自 URL禁用下拉框
isFromUrl() {
return this.regionUuidFromUrl || this.roomUuidFromUrl || this.bedUuidFromUrl;
}
}
});
</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_handle_bed_in_used : MyWeb.config
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

View File

@@ -15,17 +15,16 @@
<i class="mdi mdi-arrow-collapse-all"></i> 全部收起
</button>
</nav>
<nav v-if="form && selectedType==null">
<nav v-if="createRegionFlag">
<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>
<nav class="btn-group mb-2 ps-3 pe-3" role="group" v-if="form && selectedType=='region' && !createRegionFlag">
<div v-if="selectedRegionId">
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
<i class="bi bi-save me-1"></i> 儲存區域資料1
</button>
<button class="btn btn-success me-2" @click="createSubRegion" type="button" >
<i class="mdi mdi-arrow-down-right"></i> 新增下層區域
</button>
@@ -217,8 +216,8 @@
<template #item.isactive="{item}">
{{item.isactive ? '啟用' : '停用'}}
</template>
<template #item.statusuuid="{item}">
{{getBedStatusNameById(item.statusuuid)}}
<template #item.statuscode="{item}">
{{getBedStatusNameById(item.statuscode)}}
</template>
<template #item.action ="{item}">
<button type="button" class="btn btn-primary" @click="editBed(item)">
@@ -252,8 +251,8 @@
<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">
<select class="form-control" v-model="room_bed.newBedForm.statuscode" disabled>
<option v-for="status in room_bed.bed_status" :value="status.code">
{{status.name}}
</option>
</select>
@@ -349,6 +348,23 @@
</v-card-actions>
</v-card>
</v-dialog>
<!-- 停用區域如果有床位正在掛單提示彈出視窗 -->
<div v-if="bed_is_used_modal" style="position:fixed; top:0; left:0; width:100%; height:100%;
background:rgba(0,0,0,0.5); display:flex; align-items:center; justify-content:center; z-index:9999;">
<div style="background:#fff; padding:20px; border-radius:8px; width:300px; text-align:center;">
<p style="margin-bottom:15px;">{{bed_is_used_modal_message}}</p>
<div style="display:flex; justify-content:flex-end; gap:10px;">
<button @click="closeBedInUsedModal"
class="btn btn-danger"
type="button" style="padding:6px 12px; border:none; border-radius:4px; cursor:pointer;">关闭</button>
<a :href="bed_is_used_modal_link" target="_blank" class="btn btn-primary">
前往处理
</a>
</div>
</div>
</div>
<!-- 更新修改確認彈出視窗 -->
<message-modal ref="messageModal"></message-modal>
<!-- 刪除確認彈出視窗 -->
@@ -477,13 +493,14 @@
</div>
`
});
new Vue({
el: '#app',
vuetify: new Vuetify(vuetify_options),
data() {
return {
bed_is_used_modal: false,
bed_is_used_modal_message: null,
bed_is_used_modal_link: "handle_bed_in_used.aspx", // 默认链接
selectedId: null, // 被選中項目ID
selectedType: null, // 'region' 或 'room'
expandAllFlag: false, // 控制全部展開
@@ -499,6 +516,7 @@
regionTypes: [],
currentSelectRegion: null,
currentSelectRoom: null,
createRegionFlag: false,
form: {
uuid: null,
name: '',
@@ -528,7 +546,7 @@
bed_headers: [
{ text: '床位編號', value: 'uuid' },
{ text: '床位名稱', value: 'name' },
{ text: '床位狀態', value: 'statusuuid' },
{ text: '床位狀態', value: 'statuscode' },
{ text: '是否啟用', value: 'isactive' },
{ text: '', value: 'action' },
],
@@ -536,7 +554,7 @@
uuid: null,
RegionUuid: null,
Name: '',
statusuuid: null,
statuscode: null,
IsActive: true,
Gender: null,
},
@@ -546,6 +564,11 @@
};
},
methods: {
closeBedInUsedModal() {
this.bed_is_used_modal = false;
this.bed_is_used_modal_message = null;
this.bed_is_used_modal_link = "handle_bed_in_used.aspx"; // 默认链接
},
expandAll() {
this.expandAllFlag = true;
this.collapseAllFlag = false;
@@ -555,7 +578,7 @@
this.expandAllFlag = false;
},
async loadRegions() {
const res = await axios.post('/api/region/getRegionList');
const res = await axios.post(HTTP_HOST + 'api/region/getRegionList');
this.regions = res.data;
this.flatRegions = this.flatten(res.data);
if (this.currentSelectRoom) {
@@ -563,7 +586,7 @@
}
},
loadRegionType() {
axios.post('/api/region/getRegionType')
axios.post(HTTP_HOST + 'api/region/getRegionType')
.then(res => {
this.regionTypes = res.data
});
@@ -582,6 +605,7 @@
this.selectedType = 'region';
this.selectedRegionId = region.uuid;
this.currentSelectRegion = region;
console.log(this.currentSelectRegion)
this.currentSelectRoom = null;
this.resetRoomForm();
this.form = {
@@ -612,6 +636,8 @@
this.disabledParentOptions = [];
this.currentSelectRegion = null;
this.currentSelectRoom = null;
this.createRegionFlag = true;
this.selectedType = null;
},
createSubRegion() {
if (!this.selectedRegionId) return;
@@ -633,25 +659,39 @@
});
return;
}
const url = this.form.uuid ? '/api/region/update' : '/api/region/create';
const url = this.form.uuid
? HTTP_HOST + 'api/region/update'
: HTTP_HOST + '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.form.uuid = res.data.id;
this.selectedRegionId = res.data.id;
this.currentSelectRegion = JSON.parse(JSON.stringify(this.form));
this.createRegionFlag = false;
this.$refs.messageModal.open({
title: "更新",
message: "更新成功",
});
})
.catch((error) => {
this.$refs.messageModal.open({
title: '更新提示',
message: error.response?.data?.message || "儲存失敗,請稍後再試。",
});
console.error('更新失敗', error);
const code = error.response?.data?.code;
const message = error.response?.data?.message || error.message ||
"未知錯誤,請稍後再試";
if (code === "BED_IS_USED") {
this.bed_is_used_modal = true;
this.bed_is_used_modal_message = message
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?region=' + this.form.uuid
} else {
this.$refs.messageModal.open({
message: (message)
});
}
});
},
deleteRegion() {
@@ -664,7 +704,7 @@
});
},
confirmDeleteRegion() {
axios.post('/api/region/delete', { uuid: this.form.uuid })
axios.post(HTTP_HOST + 'api/region/delete', { Uuid: this.form.uuid })
.then(() => {
this.showDeleteModal = false;
this.$refs.messageModal.open({
@@ -716,7 +756,7 @@
uuid: null,
RoomUuid: this.currentSelectRoom.uuid,
Name: '',
statusuuid: null,
statuscode: "101",
IsActive: true,
Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇
};
@@ -731,7 +771,7 @@
return;
}
try {
var res = await axios.post('/api/region/bed/create', this.room_bed.newBedForm);
var res = await axios.post(HTTP_HOST + 'api/region/bed/create', this.room_bed.newBedForm);
this.room_bed.showBedModal = false;
this.$refs.messageModal.open({
title: '成功',
@@ -759,7 +799,7 @@
});
},
confirmDeleteBed(bed) {
axios.post('/api/region/bed/delete', null, {
axios.post(HTTP_HOST + 'api/region/bed/delete', null, {
params: { uuid: bed.uuid }
}) // 假設後端吃的是 id
.then(() => {
@@ -791,7 +831,7 @@
RegionUuid: bed.regionUuid,
RoomUuid: bed.roomUuid,
Name: bed.name,
statusuuid: bed.statusuuid,
statuscode: bed.statuscode,
IsActive: bed.isactive,
Gender: bed.gender,
};
@@ -800,7 +840,7 @@
async saveEditBed() {
try {
await axios.post('/api/region/bed/update', this.room_bed.newBedForm);
await axios.post(HTTP_HOST + 'api/region/bed/update', this.room_bed.newBedForm);
this.room_bed.showBedModal = false;
const updated = this.room_bed.newBedForm;
@@ -811,7 +851,7 @@
...this.room_bed.bed_items[index], // 保留原本未更新欄位
roomUuid: updated.RoomUuid,
name: updated.Name,
statusuuid: updated.statusuuid,
statuscode: updated.statuscode,
isactive: updated.IsActive
});
}
@@ -824,25 +864,31 @@
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 || '更新失敗'
});
} catch (error) {
console.error('更新失敗', error);
const code = error.response?.data?.code;
const message = error.response?.data?.message || error.message ||
"未知錯誤,請稍後再試";
if (code === "BED_IS_USED") {
this.bed_is_used_modal = true;
this.bed_is_used_modal_message = message;
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?bed=' + this.room_bed.newBedForm.uuid
} else {
this.$refs.messageModal.open({
message: (message)
});
}
}
},
getBedStatus() {
//獲取床位狀態
axios.get('/api/region/bed/status/list')
axios.get(HTTP_HOST + '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);
getBedStatusNameById(statuscode) {
const status = this.room_bed.bed_status.find(i => i.code == statuscode);
if (status) {
return status.name;
}
@@ -861,7 +907,7 @@
});
return;
}
axios.post('/api/region/room/create', this.room.room_form)
axios.post(HTTP_HOST + 'api/region/room/create', this.room.room_form)
.then((res) => {
this.room.showCreateRoomDialog = false;
this.currentSelectRegion.rooms.push(res.data);
@@ -878,7 +924,7 @@
},
async roomUpdate() {
try {
const res = await axios.post('/api/region/room/update', this.room.room_form);
const res = await axios.post(HTTP_HOST + 'api/region/room/update', this.room.room_form);
this.$refs.messageModal.open({
message: '客房資料更新成功'
});
@@ -888,14 +934,23 @@
};
} catch (error) {
console.error('更新失敗', error);
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
});
const code = error.response?.data?.code;
const message = error.response?.data?.message || error.message ||
"未知錯誤,請稍後再試";
if (code === "BED_IS_USED") {
this.bed_is_used_modal = true;
this.bed_is_used_modal_message = message;
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?room=' + this.room.room_form.uuid
} else {
this.$refs.messageModal.open({
message: (message)
});
}
}
},
roomDelete() {
axios.post('/api/region/room/delete', { uuid: this.currentSelectRoom.uuid })
axios.post(HTTP_HOST + 'api/region/room/delete', { uuid: this.currentSelectRoom.uuid })
.then((res) => {
const region = this.findRegionById(this.regions, this.currentSelectRoom.regionUuid)//當前room所在的region
if (region) {
@@ -908,6 +963,10 @@
this.currentSelectRoom = null;
this.room_bed.bed_items = [];
//清空 beds
}).catch((error) => {
this.$refs.messageModal.open({
message: (error.response?.data?.message || error.message)
});
});
},
confirmRoomDelete() {
@@ -944,7 +1003,16 @@
},
watch: {
currentSelectRegion(newVal) {
if (newVal !== null) {
this.createRegionFlag = false;
}
},
currentSelectRoom(newVal) {
if (newVal !== null) {
this.createRegionFlag = false;
}
}
},
mounted() {
this.loadRegions();

View File

@@ -61,7 +61,7 @@
},
methods: {
getRegionTypeList() {
axios.post('/api/regiontype/getreiontypelist')
axios.post(HTTP_HOST + 'api/regiontype/getreiontypelist')
.then((res) => {
this.items = res.data;
})
@@ -71,7 +71,7 @@
'title': '刪除提示',
'message': `確定要刪除 ${item.name} ?`,
onConfirm: () => {
axios.post('/api/regiontype/delete',null, {
axios.post(HTTP_HOST + 'api/regiontype/delete',null, {
params: { uuid: item.uuid }
})
.then(() => {

View File

@@ -18,13 +18,13 @@
<div class="">
<a class="btn btn-outline-primary btn-print" @click="updateShuWen" v-if="currentActivityNum&&!latest">{{updateShuWenLoading ? '更新中': '更新疏文'}}</a>
<a :href="'/api/shuwen/download?activitynum='+currentActivityNum" class="btn btn-outline-primary btn-print">下載疏文Word檔</a>
<a :href="'<%=ResolveUrl("~/api/shuwen/download")%>?activitynum='+currentActivityNum" class="btn btn-outline-primary btn-print">下載疏文Word檔</a>
</div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div v-if="currentActivityNum">
<div><h2 style="text-align:center">消災疏文</h2></div>
<div v-for="item in ShuWenJson.xiaozai" :key="Object.keys(item)[0]">
<div v-for="(item, index) in ShuWenJson?.xiaozai" :key="Object.keys(item)[0] + 'xz' + index">
<h4>報名信眾:{{ item[Object.keys(item)[0]].user.name }}</h4>
<ul>
<li>{{ item[Object.keys(item)[0]]['biaoti'].join(' ') }}</li>
@@ -32,7 +32,7 @@
<hr />
</div>
<div><h2 style="text-align:center">超薦疏文</h2></div>
<div v-for="item in ShuWenJson.chaodu" :key="Object.keys(item)[0]">
<div v-for="(item, index) in ShuWenJson?.chaodu" :key="Object.keys(item)[0] + 'cj' + index">
<h4>報名信眾:{{ item[Object.keys(item)[0]].user.name }}</h4>
<ul>
<li>
@@ -113,7 +113,10 @@
this.ShuWenJson = {}
return;
}
this.ShuWenJson = JSON.parse(this.ShuWenItem.shuWenList)
this.ShuWenJson = this.ShuWenItem?.shuWenList
? JSON.parse(this.ShuWenItem.shuWenList)
: { xiaozai: [], chaodu: [] }; // 默认空对象
})
.catch(err => {
if (err.response && err.response.status === 400) {