diff --git a/backend/my_work/main.go b/backend/my_work/main.go index 77a70bf..042e1fb 100644 --- a/backend/my_work/main.go +++ b/backend/my_work/main.go @@ -66,7 +66,8 @@ func main() { //统一初始化 models.ConfigAllInit() - routers.ApiInit() + routers.ApiUserInit() //用户表先初始化这是必须的因为后面需要用到用户组 + routers.ApiScheduleInit() //创建必要目录 for _, path := range models.ConfigsFile.Pahts { diff --git a/backend/my_work/routers/api.go b/backend/my_work/routers/api.go index 186c24b..cf83551 100644 --- a/backend/my_work/routers/api.go +++ b/backend/my_work/routers/api.go @@ -54,7 +54,7 @@ func ApiRoot(r *gin.RouterGroup) { ApiUser(r.Group("/users")) ApiFiles(r.Group("/files")) ApiPurchase(r.Group("/purchase")) - + ApiSchedule(r.Group("/schedule")) r.GET("/", func(ctx *gin.Context) { ReturnJson(ctx, "apiOK", nil) }) diff --git a/backend/my_work/routers/apiSchedule.go b/backend/my_work/routers/apiSchedule.go new file mode 100644 index 0000000..f79fb0c --- /dev/null +++ b/backend/my_work/routers/apiSchedule.go @@ -0,0 +1,134 @@ +package routers + +//2026-4-2开始每个功能的数据表在各自的api路由下初始化 + +import ( + "encoding/json" + "fmt" + "ops/models" + "time" + + "github.com/gin-gonic/gin" + "github.com/mitchellh/mapstructure" + "gorm.io/gorm" +) + +type TabSchedule struct { + ID uint `gorm:"primarykey"` + 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"` +} + +type TabScheduleLog struct { + ID uint `gorm:"primarykey"` + ScheduleID 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-删除 query-查询"` + 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 fromAddEvent struct { + Title string `json:"title" binding:"required"` // 日程标题 + Start string `json:"start" binding:"required"` // 开始日期 + End string `json:"end" binding:"required"` // 结束日期 + Color string `json:"color" binding:"required"` // 背景颜色 +} + +type fromGetEvents struct { + Start string `json:"start" binding:"required"` // 开始日期 + End string `json:"end" binding:"required"` // 结束日期 +} + +var ( + userGroup models.TabUserGroups_ +) + +func ApiScheduleInit() { + //先初始化数据表 + models.DB.AutoMigrate(&TabSchedule{}) + models.DB.AutoMigrate(&TabScheduleLog{}) + //获取管理员用户组id + //先检查用户组有没有这个key + userGroup.Name = "schedule_admin" + + if models.DB.Where(&userGroup).First(&userGroup).Error == nil { + + } else { + userGroup.Type = "usergroup" + models.DB.Create(&userGroup) + } +} + +func ApiSchedule(r *gin.RouterGroup) { + r.POST("/getevents", func(ctx *gin.Context) { + data, _ := SeparateData(ctx) + //fmt.Println(cookieval, data) + var from fromGetEvents + if err := mapstructure.Decode(data, &from); err == nil { + //fmt.Println(from) + //从数据库获取相关数据 + var list []TabSchedule + models.DB.Where("start_date <= ? AND end_date >= ?", from.End, from.Start).Where("deleted_at IS NULL").Find(&list) + fmt.Println(list) + //ReturnJson(ctx, "ApiOK", list) + ReturnJson(ctx, "apiOK", gin.H{"list": list}) + + } else { + ReturnJson(ctx, "jsonErr", nil) + } + + }) + + r.POST("/addevent", func(ctx *gin.Context) { + isAuth, user, data := AuthenticationAuthority(ctx) + if isAuth { + + var from fromAddEvent + if err := mapstructure.Decode(data, &from); err == nil { + + tosql := TabSchedule{ + UserID: user.ID, + Title: from.Title, + StartDate: from.Start, + EndDate: from.End, + BgColor: from.Color, + } + if models.DB.Create(&tosql).Error == nil { + //记录日志 + newContent, _ := json.Marshal(tosql) // 👈 转 JSON + tosqllog := TabScheduleLog{ + UserID: user.ID, + ScheduleID: tosql.ID, + ActionType: "create", + NewContent: string(newContent), // 👈 直接赋值 + OldContent: "", + IP: ctx.ClientIP(), + } + models.DB.Create(&tosqllog) + ReturnJson(ctx, "apiOK", nil) + } else { + ReturnJson(ctx, "apiErr", nil) + } + + } else { + ReturnJson(ctx, "jsonErr", nil) + } + } else { + ReturnJson(ctx, "userCookieError", nil) + } + + }) + +} diff --git a/backend/my_work/routers/apiUsers.go b/backend/my_work/routers/apiUsers.go index e576ad4..5e4562e 100644 --- a/backend/my_work/routers/apiUsers.go +++ b/backend/my_work/routers/apiUsers.go @@ -12,7 +12,7 @@ import ( "github.com/mitchellh/mapstructure" ) -func ApiInit() { +func ApiUserInit() { //用户模块初始化init fmt.Println("users init") @@ -110,33 +110,16 @@ func AuthenticationAuthorityFromCookie(c string) (*models.TabUser_, error) { } func AuthenticationAuthority(ctx *gin.Context) (bool, models.TabUser_, map[string]interface{}) { - var user models.TabUser_ data, cookieval := SeparateData(ctx) //fmt.Println("cookieis" + cookieval) + var user models.TabUser_ if cookieval != "" { - cookie := models.TabCookie_{ - Value: cookieval, - } - if models.DB.Where(&cookie).First(&cookie).Error == nil { - //找到cookie,验证cookie有效性,以及更新cookie - if models.CheckCookiesAndUpdate(&cookie) { - //cookie有效 - //载入user - user := models.TabUser_{ - ID: cookie.UserID, - } - models.DB.Where(&user).First(&user) - - return true, user, data - - } else { - ReturnJson(ctx, "userCookieExpired", nil) - return false, user, nil - } - + user_, error := AuthenticationAuthorityFromCookie(cookieval) + if error == nil { + user = *user_ + return true, user, data } else { - ReturnJson(ctx, "userCookieNotFund", nil) return false, user, nil } @@ -145,7 +128,6 @@ func AuthenticationAuthority(ctx *gin.Context) (bool, models.TabUser_, map[strin return false, user, nil } - //return false, user } func ApiUser(r *gin.RouterGroup) { @@ -261,26 +243,26 @@ func ApiUser(r *gin.RouterGroup) { } } - if is_save_ok { - //修改数据库内容 - var user_info_fund models.TabUserInfo_ - user_info_fund.UserID = user.ID + if is_save_ok { + //修改数据库内容 + var user_info_fund models.TabUserInfo_ + user_info_fund.UserID = user.ID - var user_update_avatar models.TabUserInfo_ - user_update_avatar.AvatarPath = file_hashi_name + file_extname + var user_update_avatar models.TabUserInfo_ + user_update_avatar.AvatarPath = file_hashi_name + file_extname + + //先查找是否有记录 + if models.DB.Where(&user_info_fund).First(&user_info_fund).Error == nil { + //有记录,更新 + models.DB.Model(&user_info_fund).Updates(&user_update_avatar) + } else { + //无记录,创建 + user_update_avatar.UserID = user.ID + models.DB.Create(&user_update_avatar) + } - //先查找是否有记录 - if models.DB.Where(&user_info_fund).First(&user_info_fund).Error == nil { - //有记录,更新 - models.DB.Model(&user_info_fund).Updates(&user_update_avatar) - } else { - //无记录,创建 - user_update_avatar.UserID = user.ID - models.DB.Create(&user_update_avatar) } - } - } else { ReturnJson(ctx, "postErr", nil) } diff --git a/frontend/ops_vue_js/src/api/schedule.js b/frontend/ops_vue_js/src/api/schedule.js new file mode 100644 index 0000000..bea41c2 --- /dev/null +++ b/frontend/ops_vue_js/src/api/schedule.js @@ -0,0 +1,13 @@ +import { api } from './index' + +export const scheduleApi = { + + getEvents(params = {}) { + return api.post('/schedule/getevents', params) + }, + + + addEvent(data) { + return api.post('/schedule/addevent', data) + }, +} \ No newline at end of file diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json index 460dc62..b9d54e8 100644 --- a/frontend/ops_vue_js/src/i18n/en.json +++ b/frontend/ops_vue_js/src/i18n/en.json @@ -247,7 +247,7 @@ "source_code": "Source Code", "github": "GitHub", "author_home": "Author", - "copy": "Copyright © 2025 Operations. All rights reserved." + "copy": "Copyright © 2025 Operations. MIT Open Source License." }, "cost_type": { "unit_price": "Unit Price", diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json index a6de0b1..4cac096 100644 --- a/frontend/ops_vue_js/src/i18n/zh-CN.json +++ b/frontend/ops_vue_js/src/i18n/zh-CN.json @@ -247,7 +247,7 @@ "source_code": "源码", "github": "GitHub", "author_home": "作者主页", - "copy": "版权 © 2025 Operations. 保留所有权利。" + "copy": "版权 © 2025 Operations. MIT开源协议。" }, "cost_type": { "unit_price": "单价", diff --git a/frontend/ops_vue_js/src/views/loginView.vue b/frontend/ops_vue_js/src/views/loginView.vue index 7883e33..42ac285 100644 --- a/frontend/ops_vue_js/src/views/loginView.vue +++ b/frontend/ops_vue_js/src/views/loginView.vue @@ -1,64 +1,79 @@  @@ -68,55 +83,94 @@ async function handleLogin() {
Operations - Operations + Operations -
-
-

{{ t('message.login_to_your_account') }}

+
+

+ {{ t("message.login_to_your_account") }} +

- + - {{ errors.username }} + {{ + errors.username + }}
- +
-
- {{ errors.password }} + {{ + errors.password + }}
-

- {{ t('message.dont_have_account_yet') }} - {{ t('message.register_now') }} + {{ t("message.dont_have_account_yet") }} + {{ t("message.register_now") }}

diff --git a/frontend/ops_vue_js/src/views/scheduleView.vue b/frontend/ops_vue_js/src/views/scheduleView.vue index 8a16c7d..7ff5f35 100644 --- a/frontend/ops_vue_js/src/views/scheduleView.vue +++ b/frontend/ops_vue_js/src/views/scheduleView.vue @@ -20,6 +20,22 @@ import DatatimePickerForFullCalendar from "@/components/datatimePickerForFullCal import { useToastStore } from "@/stores/toast"; +// 用户状态管理 +import { useUserStore } from "@/stores/user"; + +import { useRouter } from "vue-router"; + +import { scheduleApi } from "@/api/schedule"; + +import { useDateUtils } from "@/composables/useDateUtils"; + +const DateUtils = useDateUtils(); + +const router = useRouter(); + +// 获取用户 store 实例,用于访问和更新用户信息 +const userStore = useUserStore(); + const toast = useToastStore(); // 设置页面标题 @@ -30,6 +46,9 @@ const { t, locale } = useI18n(); // FullCalendar 组件的引用,用于调用日历 API const calendarRef = ref(null); +// 当前视图的年份 +const calendarNowShow = ref(); + // 用于跟踪上次点击时间的响应式变量 const lastClickTime = ref(0); // 用于跟踪上次点击event时间的响应式变量 @@ -127,6 +146,7 @@ const calendarOptions = ref({ text: t("schedule.previous_year"), click() { calendarRef.value.getApi().prevYear(); + getEvents(); }, }, // 下一年按钮 @@ -134,6 +154,7 @@ const calendarOptions = ref({ text: t("schedule.next_year"), click() { calendarRef.value.getApi().nextYear(); + getEvents(); }, }, // 上一个月按钮 @@ -141,6 +162,7 @@ const calendarOptions = ref({ text: t("schedule.previous_month"), click() { calendarRef.value.getApi().prev(); + getEvents(); }, }, // 下一个月按钮 @@ -148,6 +170,7 @@ const calendarOptions = ref({ text: t("schedule.next_month"), click() { calendarRef.value.getApi().next(); + getEvents(); }, }, // 今天按钮:跳转到今天 @@ -155,6 +178,7 @@ const calendarOptions = ref({ text: t("schedule.today"), click() { calendarRef.value.getApi().today(); + getEvents(); }, }, // 周视图按钮:切换到周视图 @@ -169,6 +193,12 @@ const calendarOptions = ref({ // 日历事件列表(目前为空,后续可接入数据源) events: [], + // 👇 加这个!日历渲染完成 / 切换年月都会触发 + datesSet(info) { + calendarNowShow.value = info; + //console.log(info); + }, + // 日期点击事件处理函数 dateClick(info) { const nowTime = new Date().getTime(); @@ -194,7 +224,7 @@ const calendarOptions = ref({ if (info.end - info.start > 86400000) { //选择了多日 console.log("选择了多日:", info); - openEventModal(info.startStr,info.endStr); + openEventModal(info.startStr, info.endStr); } else { //选择单日 无功能 //console.log("选择单日:", info); @@ -223,7 +253,7 @@ const calendarOptions = ref({ }); // 打开模态框 -const openEventModal = (dateStr,dataEnd) => { +const openEventModal = (dateStr, dataEnd) => { eventData.value = { title: "", startDate: dateStr, @@ -241,7 +271,13 @@ const closeEventModal = () => { // 处理双击事件:打开模态框添加事件 const handleDoubleClick = (info) => { - openEventModal(info.dateStr,info.dateStr); + //先判断是否登录 + if (userStore.isLoggedIn) { + openEventModal(info.dateStr, info.dateStr); + } else { + toast.warning(t("message.login_to_your_account")); + router.replace("/login?redirect=/schedule"); + } }; // 处理单机事件:显示日期详情 @@ -293,15 +329,77 @@ const saveEvent = () => { }; // 添加到日历事件列表 - calendarOptions.value.events.push(newEvent); + //提交到后端 - console.log("事件添加成功:", newEvent); - toast.success(t("schedule.event_added_successfully")); - - // 关闭模态框 - closeEventModal(); + scheduleApi + .addEvent({ + title: newEvent.title, + start: newEvent.start, + end: DateUtils.toRealEnd(newEvent.end), + color: newEvent.backgroundColor, + }) + .then((r) => { + //console.log(r); + if (r.errCode == 0) { + //前端提交是否错误 + switch ( + r.raw.err_code //后端返回是否错误 + ) { + case 0: + //calendarOptions.value.events.push(newEvent); + toast.success(t("schedule.event_added_successfully")); + // 关闭模态框 + closeEventModal(); + getEvents(); + break; + default: + toast.danger(t("message.server_error")); + break; + } + } + }); }; +//从后端获取events +const getEvents = () => { + //console.log(calendarNowShow.value) + scheduleApi + .getEvents({ + start: DateUtils.dateToStr(calendarNowShow.value.start), + end: DateUtils.toRealEnd(calendarNowShow.value.end), + }) + .then((r) => { + console.log(r); + if (r.errCode == 0) { + //前端提交是否错误 + switch ( + r.raw.err_code //后端返回是否错误 + ) { + case 0: + calendarOptions.value.events=[]; + var events = r.raw.return.list; + console.log(events); + var eventstemp = []; + events.forEach((item) => { + + calendarOptions.value.events.push({ + id: item.ID, // 后端 ID + title: item.Title, // 标题 + start: item.StartDate, // 开始日期 + end: DateUtils.toCalendarEnd(item.EndDate), // 结束日期 + backgroundColor: item.BgColor, // 背景色 + allDay: true, // 全天事件 + }); + }); + + break; + default: + toast.danger(t("message.server_error")); + break; + } + } + }); +}; // 清除日期选择 const clearDates = () => { eventData.value.startDate = ""; @@ -350,32 +448,31 @@ watch(locale, () => { }); onMounted(() => { - const handleKeydown = (event) => { - // Ctrl+C 事件 - if (event.ctrlKey && event.key === "c") { - event.preventDefault(); // 可选:阻止默认复制行为 - console.log("Ctrl+C 被按下"); - // 你的业务逻辑 - } - - // Ctrl+V 事件 - if (event.ctrlKey && event.key === "v") { - event.preventDefault(); // 可选:阻止默认粘贴行为 - console.log("Ctrl+V 被按下"); - // 你的业务逻辑 - } - }; - - document.addEventListener("keydown", handleKeydown); - - // 清理事件监听器 - onBeforeUnmount(() => { - document.removeEventListener("keydown", handleKeydown); - }); + getEvents(); + // const handleKeydown = (event) => { + // // Ctrl+C 事件 + // if (event.ctrlKey && event.key === "c") { + // event.preventDefault(); // 可选:阻止默认复制行为 + // console.log("Ctrl+C 被按下"); + // // 你的业务逻辑 + // } + // // Ctrl+V 事件 + // if (event.ctrlKey && event.key === "v") { + // event.preventDefault(); // 可选:阻止默认粘贴行为 + // console.log("Ctrl+V 被按下"); + // // 你的业务逻辑 + // } + // }; + // document.addEventListener("keydown", handleKeydown); + // // 清理事件监听器 + // onBeforeUnmount(() => { + // document.removeEventListener("keydown", handleKeydown); + // }); });