From cb363c93a0b670cb93e463078217416a692ed3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Thu, 30 Apr 2026 21:44:22 +0800 Subject: [PATCH] =?UTF-8?q?Signed-off-by:=20=E5=90=B4=E6=96=87=E5=B3=B0=20?= =?UTF-8?q??= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/my_work/main.go | 1 + backend/my_work/routers/api.go | 1 + backend/my_work/routers/apiCalendar.go | 434 +++++++++++++++ frontend/ops_vue_js/src/api/calendar.js | 43 ++ .../ops_vue_js/src/components/AppHeader.vue | 1 + frontend/ops_vue_js/src/i18n/en.json | 39 +- frontend/ops_vue_js/src/i18n/zh-CN.json | 39 +- frontend/ops_vue_js/src/router/index.js | 10 + .../src/views/calendar/CalendarDetail.vue | 492 ++++++++++++++++++ .../src/views/calendar/CalendarList.vue | 376 +++++++++++++ 10 files changed, 1434 insertions(+), 2 deletions(-) create mode 100644 backend/my_work/routers/apiCalendar.go create mode 100644 frontend/ops_vue_js/src/api/calendar.js create mode 100644 frontend/ops_vue_js/src/views/calendar/CalendarDetail.vue create mode 100644 frontend/ops_vue_js/src/views/calendar/CalendarList.vue diff --git a/backend/my_work/main.go b/backend/my_work/main.go index a8e0393..62d3124 100644 --- a/backend/my_work/main.go +++ b/backend/my_work/main.go @@ -81,6 +81,7 @@ func main() { routers.ApiWorkOrderInit() routers.ApiWarehouseInit() routers.ApiCustomerInit() + routers.ApiCalendarInit() routers.BindsInit() //最后初始化绑定数据表 diff --git a/backend/my_work/routers/api.go b/backend/my_work/routers/api.go index 4a202df..922c63c 100644 --- a/backend/my_work/routers/api.go +++ b/backend/my_work/routers/api.go @@ -45,6 +45,7 @@ func ApiRoot(r *gin.RouterGroup) { ApiWarehouse(r.Group("/warehouse")) ApiSysAdmin(r.Group("/admin")) ApiCustomer(r.Group("/customer")) + ApiCalendar(r.Group("/calendar")) r.GET("/", func(ctx *gin.Context) { ReturnJson(ctx, "apiOK", gin.H{ "isOpsApiRoot": true, diff --git a/backend/my_work/routers/apiCalendar.go b/backend/my_work/routers/apiCalendar.go new file mode 100644 index 0000000..59a3479 --- /dev/null +++ b/backend/my_work/routers/apiCalendar.go @@ -0,0 +1,434 @@ +package routers + +import ( + "encoding/json" + "ops/models" + "time" + + "github.com/gin-gonic/gin" + "github.com/mitchellh/mapstructure" + "gorm.io/gorm" +) + +// TabCalendar 日历表 +type TabCalendar struct { + ID uint `gorm:"primarykey"` + UserID uint `gorm:"not null;comment:创建人ID"` + Name string `gorm:"size:100;not null;comment:日历名称"` + Description string `gorm:"size:500;comment:日历描述"` + Color string `gorm:"size:50;default:#3788d9;comment:日历颜色"` + IsPublic bool `gorm:"default:false;comment:是否公开"` + + CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:创建时间"` + UpdatedAt *time.Time `gorm:"type:datetime;autoUpdateTime;comment:最后修改时间"` + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +// TabCalendarEvent 日历事件表 +type TabCalendarEvent struct { + ID uint `gorm:"primarykey"` + CalendarID uint `gorm:"not null;index;comment:关联日历ID"` + UserID uint `gorm:"not null;comment:创建人ID"` + Title string `gorm:"size:200;not null;comment:事件标题"` + StartDate string `gorm:"size:10;not null;index;comment:开始日期 YYYY-MM-DD"` + EndDate string `gorm:"size:10;not null;index;comment:结束日期 YYYY-MM-DD"` + BgColor string `gorm:"size:50;default:#3788d9;comment:背景颜色"` + Remark string `gorm:"type:text;comment:备注"` + + CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:创建时间"` + UpdatedAt *time.Time `gorm:"type:datetime;autoUpdateTime;comment:最后修改时间"` + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +// TabCalendarLog 日历操作日志表 +type TabCalendarLog struct { + ID uint `gorm:"primarykey"` + CalendarID uint `gorm:"not null;index;comment:关联日历ID"` + EventID uint `gorm:"not null;index;comment:关联事件ID(可选)"` + UserID uint `gorm:"not null;comment:操作人ID"` + ActionType string `gorm:"size:50;not null;comment:操作类型: create-创建 update-修改 delete-删除"` + OldContent string `gorm:"type:text;comment:修改前内容(JSON)"` + NewContent string `gorm:"type:text;comment:修改后内容(JSON)"` + IP string `gorm:"size:50;comment:操作IP"` + Remark string `gorm:"size:500;comment:备注/操作描述"` + + CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:操作时间"` +} + +// 请求结构体 +type fromCreateCalendar struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + Color string `json:"color"` + IsPublic bool `json:"is_public"` +} + +type fromUpdateCalendar struct { + ID uint `json:"id" binding:"required"` + Name string `json:"name" binding:"required"` + Description string `json:"description"` + Color string `json:"color"` + IsPublic bool `json:"is_public"` +} + +type fromDeleteCalendar struct { + ID uint `json:"id" binding:"required"` +} + +type fromGetCalendarEvents struct { + CalendarID uint `json:"calendar_id" binding:"required"` + Start string `json:"start" binding:"required"` + End string `json:"end" binding:"required"` +} + +type fromAddCalendarEvent struct { + CalendarID uint `json:"calendar_id" binding:"required"` + Title string `json:"title" binding:"required"` + Start string `json:"start" binding:"required"` + End string `json:"end" binding:"required"` + Color string `json:"color"` + Remark string `json:"remark"` +} + +type fromUpdateCalendarEvent struct { + ID uint `json:"id" binding:"required"` + Title string `json:"title" binding:"required"` + Start string `json:"start" binding:"required"` + End string `json:"end" binding:"required"` + Color string `json:"color"` + Remark string `json:"remark"` +} + +type fromDeleteCalendarEvent struct { + ID uint `json:"id" binding:"required"` +} + +func ApiCalendarInit() { + // 初始化数据表 + models.DB.AutoMigrate(&TabCalendar{}) + models.DB.AutoMigrate(&TabCalendarEvent{}) + models.DB.AutoMigrate(&TabCalendarLog{}) +} + +func ApiCalendar(r *gin.RouterGroup) { + // 创建日历 + r.POST("/calendar/create", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromCreateCalendar + if err := mapstructure.Decode(data, &from); err == nil { + calendar := TabCalendar{ + UserID: user.ID, + Name: from.Name, + Description: from.Description, + Color: from.Color, + IsPublic: from.IsPublic, + } + if calendar.Color == "" { + calendar.Color = "#3788d9" + } + if models.DB.Create(&calendar).Error == nil { + // 记录日志 + newContent, _ := json.Marshal(calendar) + log := TabCalendarLog{ + CalendarID: calendar.ID, + UserID: user.ID, + ActionType: "create", + NewContent: string(newContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", gin.H{"id": calendar.ID}) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 获取日历列表 + r.POST("/calendar/list", func(ctx *gin.Context) { + isAuth, _, _ := AuthenticationAuthority(ctx) + if isAuth { + var calendars []TabCalendar + models.DB.Where("deleted_at IS NULL").Order("created_at DESC").Find(&calendars) + ReturnJson(ctx, "apiOK", gin.H{"list": calendars}) + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 更新日历 + r.POST("/calendar/update", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromUpdateCalendar + if err := mapstructure.Decode(data, &from); err == nil { + oldCalendar := TabCalendar{} + if models.DB.Where("id = ?", from.ID).First(&oldCalendar).Error == nil { + // 检查权限(只有创建人可以修改) + if oldCalendar.UserID != user.ID { + ReturnJson(ctx, "permission_denied", nil) + return + } + + newCalendar := TabCalendar{ + Name: from.Name, + Description: from.Description, + Color: from.Color, + IsPublic: from.IsPublic, + } + if newCalendar.Color == "" { + newCalendar.Color = "#3788d9" + } + + if models.DB.Model(&oldCalendar).Updates(&newCalendar).Error == nil { + // 记录日志 + newContent, _ := json.Marshal(newCalendar) + oldContent, _ := json.Marshal(oldCalendar) + log := TabCalendarLog{ + CalendarID: oldCalendar.ID, + UserID: user.ID, + ActionType: "update", + OldContent: string(oldContent), + NewContent: string(newContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", nil) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "calendar_not_find", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 删除日历 + r.POST("/calendar/delete", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromDeleteCalendar + if err := mapstructure.Decode(data, &from); err == nil { + oldCalendar := TabCalendar{} + if models.DB.Where("id = ?", from.ID).First(&oldCalendar).Error == nil { + // 检查权限(只有创建人可以删除) + if oldCalendar.UserID != user.ID { + ReturnJson(ctx, "permission_denied", nil) + return + } + + // 软删除日历 + if models.DB.Delete(&oldCalendar).Error == nil { + // 记录日志 + oldContent, _ := json.Marshal(oldCalendar) + log := TabCalendarLog{ + CalendarID: oldCalendar.ID, + UserID: user.ID, + ActionType: "delete", + OldContent: string(oldContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", nil) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "calendar_not_find", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 获取日历事件 + r.POST("/calendar/events", func(ctx *gin.Context) { + isAuth, _, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromGetCalendarEvents + if err := mapstructure.Decode(data, &from); err == nil { + var events []TabCalendarEvent + models.DB.Where("calendar_id = ? AND start_date <= ? AND end_date >= ? AND deleted_at IS NULL", + from.CalendarID, from.End, from.Start).Find(&events) + + // 为事件添加编辑权限标识 + var relist []map[string]interface{} + for _, event := range events { + data, _ := json.Marshal(event) + var temp map[string]interface{} + json.Unmarshal(data, &temp) + // 这里可以根据需要添加 edit 字段 + relist = append(relist, temp) + } + + ReturnJson(ctx, "apiOK", gin.H{"list": relist}) + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 添加日历事件 + r.POST("/calendar/addevent", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromAddCalendarEvent + if err := mapstructure.Decode(data, &from); err == nil { + // 检查日历是否存在 + var calendar TabCalendar + if models.DB.Where("id = ? AND deleted_at IS NULL", from.CalendarID).First(&calendar).Error != nil { + ReturnJson(ctx, "calendar_not_find", nil) + return + } + + event := TabCalendarEvent{ + CalendarID: from.CalendarID, + UserID: user.ID, + Title: from.Title, + StartDate: from.Start, + EndDate: from.End, + BgColor: from.Color, + Remark: from.Remark, + } + if event.BgColor == "" { + event.BgColor = calendar.Color + } + + if models.DB.Create(&event).Error == nil { + // 记录日志 + newContent, _ := json.Marshal(event) + log := TabCalendarLog{ + CalendarID: event.CalendarID, + EventID: event.ID, + UserID: user.ID, + ActionType: "create_event", + NewContent: string(newContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", gin.H{"id": event.ID}) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 更新日历事件 + r.POST("/calendar/updateevent", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromUpdateCalendarEvent + if err := mapstructure.Decode(data, &from); err == nil { + oldEvent := TabCalendarEvent{} + if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error == nil { + // 检查权限(只有创建人可以修改) + if oldEvent.UserID != user.ID { + ReturnJson(ctx, "permission_denied", nil) + return + } + + newEvent := TabCalendarEvent{ + Title: from.Title, + StartDate: from.Start, + EndDate: from.End, + BgColor: from.Color, + Remark: from.Remark, + } + if newEvent.BgColor == "" { + // 获取日历颜色 + var calendar TabCalendar + models.DB.Where("id = ?", oldEvent.CalendarID).First(&calendar) + newEvent.BgColor = calendar.Color + } + + if models.DB.Model(&oldEvent).Updates(&newEvent).Error == nil { + // 记录日志 + newContent, _ := json.Marshal(newEvent) + oldContent, _ := json.Marshal(oldEvent) + log := TabCalendarLog{ + CalendarID: oldEvent.CalendarID, + EventID: oldEvent.ID, + UserID: user.ID, + ActionType: "update_event", + OldContent: string(oldContent), + NewContent: string(newContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", nil) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "event_not_find", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) + + // 删除日历事件 + r.POST("/calendar/deleteevent", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + var from fromDeleteCalendarEvent + if err := mapstructure.Decode(data, &from); err == nil { + oldEvent := TabCalendarEvent{} + if models.DB.Where("id = ?", from.ID).First(&oldEvent).Error == nil { + // 检查权限(只有创建人可以删除) + if oldEvent.UserID != user.ID { + ReturnJson(ctx, "permission_denied", nil) + return + } + + if models.DB.Delete(&oldEvent).Error == nil { + // 记录日志 + oldContent, _ := json.Marshal(oldEvent) + log := TabCalendarLog{ + CalendarID: oldEvent.CalendarID, + EventID: oldEvent.ID, + UserID: user.ID, + ActionType: "delete_event", + OldContent: string(oldContent), + IP: ctx.ClientIP(), + } + models.DB.Create(&log) + ReturnJson(ctx, "apiOK", nil) + } else { + ReturnJson(ctx, "apiErr", nil) + } + } else { + ReturnJson(ctx, "event_not_find", nil) + } + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + }) +} diff --git a/frontend/ops_vue_js/src/api/calendar.js b/frontend/ops_vue_js/src/api/calendar.js new file mode 100644 index 0000000..26fb8fb --- /dev/null +++ b/frontend/ops_vue_js/src/api/calendar.js @@ -0,0 +1,43 @@ +import { api } from './index' + +export const calendarApi = { + // 创建日历 + createCalendar(data) { + return api.post('/calendar/calendar/create', { data }) + }, + + // 获取日历列表 + getCalendars() { + return api.post('/calendar/calendar/list', { data: {} }) + }, + + // 更新日历 + updateCalendar(data) { + return api.post('/calendar/calendar/update', { data }) + }, + + // 删除日历 + deleteCalendar(id) { + return api.post('/calendar/calendar/delete', { data: { id } }) + }, + + // 获取日历事件 + getEvents(data) { + return api.post('/calendar/calendar/events', { data }) + }, + + // 添加日历事件 + addEvent(data) { + return api.post('/calendar/calendar/addevent', { data }) + }, + + // 更新日历事件 + updateEvent(data) { + return api.post('/calendar/calendar/updateevent', { data }) + }, + + // 删除日历事件 + deleteEvent(id) { + return api.post('/calendar/calendar/deleteevent', { data: { id } }) + } +} diff --git a/frontend/ops_vue_js/src/components/AppHeader.vue b/frontend/ops_vue_js/src/components/AppHeader.vue index 504a485..e1d632c 100644 --- a/frontend/ops_vue_js/src/components/AppHeader.vue +++ b/frontend/ops_vue_js/src/components/AppHeader.vue @@ -54,6 +54,7 @@ const normalClass = "rounded-md px-3 py-2 text-sm font-medium text-gray-600 tran const navItems = computed(() => [ { label: t("appname.home"), to: "/" }, { label: t("appname.schedule"), to: "/schedule" }, + { label: t("appname.calendar"), to: "/calendars" }, { label: t("appname.purchase"), to: "/purchase" }, { label: t("appname.work_order"), to: "/work_order" }, { label: t("appname.warehouse"), to: "/warehouse" }, diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index ef99ab5..ec6d7f7 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -33,7 +33,8 @@ "purchase": "Purchase", "warehouse": "Warehouse", "warehouse_items": "Items Overview", - "work_order": "Work Order" + "work_order": "Work Order", + "calendar": "Calendar" }, "tagadder": { "not_fund_item": "No matching items found", @@ -601,5 +602,41 @@ "created_by": "Created By", "not_found": "Customer not found", "related_customers": "Related Customers" + }, + "calendar": { + "calendars": "Calendar List", + "calendar_detail": "Calendar Detail", + "create_calendar": "Create Calendar", + "edit_calendar": "Edit Calendar", + "delete_calendar": "Delete Calendar", + "calendar_not_found": "Calendar not found", + "no_calendars": "No calendars yet", + "name": "Name", + "name_required": "Calendar name is required", + "name_placeholder": "Enter calendar name", + "description": "Description", + "description_placeholder": "Enter calendar description", + "color": "Color", + "is_public": "Public Calendar", + "public": "Public", + "private": "Private", + "create_success": "Calendar created successfully", + "update_success": "Calendar updated successfully", + "delete_success": "Calendar deleted successfully", + "confirm_delete": "Are you sure you want to delete this calendar?", + "loading": "Loading...", + "add_event": "Add Event", + "edit_event": "Edit Event", + "delete_event": "Delete Event", + "event_title": "Event Title", + "event_title_required": "Event title is required", + "event_title_placeholder": "Enter event title", + "start_date": "Start Date", + "end_date": "End Date", + "remark": "Remark", + "remark_placeholder": "Enter remark", + "event_save_success": "Event saved successfully", + "event_delete_success": "Event deleted successfully", + "confirm_delete_event": "Are you sure you want to delete this event?" } } diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index 4e27e17..e14f6f3 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -33,7 +33,8 @@ "purchase": "采购", "warehouse": "仓库", "warehouse_items": "物品总览", - "work_order": "工单" + "work_order": "工单", + "calendar": "日历" }, "tagadder": { "not_fund_item": "没有找到匹配项", @@ -601,5 +602,41 @@ "created_by": "创建者", "not_found": "客户不存在", "related_customers": "关联客户" + }, + "calendar": { + "calendars": "日历列表", + "calendar_detail": "日历详情", + "create_calendar": "新建日历", + "edit_calendar": "编辑日历", + "delete_calendar": "删除日历", + "calendar_not_found": "日历未找到", + "no_calendars": "暂无日历", + "name": "名称", + "name_required": "请输入日历名称", + "name_placeholder": "请输入日历名称", + "description": "描述", + "description_placeholder": "请输入日历描述", + "color": "颜色", + "is_public": "公开日历", + "public": "公开", + "private": "私有", + "create_success": "创建成功", + "update_success": "更新成功", + "delete_success": "删除成功", + "confirm_delete": "确定要删除此日历吗?", + "loading": "加载中...", + "add_event": "添加事件", + "edit_event": "编辑事件", + "delete_event": "删除事件", + "event_title": "事件标题", + "event_title_required": "请输入事件标题", + "event_title_placeholder": "请输入事件标题", + "start_date": "开始日期", + "end_date": "结束日期", + "remark": "备注", + "remark_placeholder": "请输入备注", + "event_save_success": "事件保存成功", + "event_delete_success": "事件删除成功", + "confirm_delete_event": "确定要删除此事件吗?" } } diff --git a/frontend/ops_vue_js/src/router/index.js b/frontend/ops_vue_js/src/router/index.js index 52c21f2..f30419b 100644 --- a/frontend/ops_vue_js/src/router/index.js +++ b/frontend/ops_vue_js/src/router/index.js @@ -129,6 +129,16 @@ const router = createRouter({ name: 'customer-edit', component: () => import('@/views/customer/CustomerFormPage.vue'), }, + { + path: 'calendars', + name: 'calendars', + component: () => import('@/views/calendar/CalendarList.vue'), + }, + { + path: 'calendar/:id', + name: 'calendar-detail', + component: () => import('@/views/calendar/CalendarDetail.vue'), + }, ], }, diff --git a/frontend/ops_vue_js/src/views/calendar/CalendarDetail.vue b/frontend/ops_vue_js/src/views/calendar/CalendarDetail.vue new file mode 100644 index 0000000..0657f2c --- /dev/null +++ b/frontend/ops_vue_js/src/views/calendar/CalendarDetail.vue @@ -0,0 +1,492 @@ + + + diff --git a/frontend/ops_vue_js/src/views/calendar/CalendarList.vue b/frontend/ops_vue_js/src/views/calendar/CalendarList.vue new file mode 100644 index 0000000..a0374fe --- /dev/null +++ b/frontend/ops_vue_js/src/views/calendar/CalendarList.vue @@ -0,0 +1,376 @@ + + +