This commit is contained in:
2026-05-06 16:47:17 +08:00
parent 49785004aa
commit 3fcc98f6f6
6 changed files with 103 additions and 13 deletions
+75 -8
View File
@@ -3,6 +3,7 @@ package routers
import ( import (
"encoding/json" "encoding/json"
"ops/models" "ops/models"
"slices"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -35,6 +36,7 @@ type TabCalendarEvent struct {
EndDate *time.Time `gorm:"size:10;not null;index;comment:结束日期 YYYY-MM-DD"` EndDate *time.Time `gorm:"size:10;not null;index;comment:结束日期 YYYY-MM-DD"`
IsAllDay bool `gorm:"default:true;comment:是否全日事件"` IsAllDay bool `gorm:"default:true;comment:是否全日事件"`
BgColor string `gorm:"size:50;default:#3788d9;comment:背景颜色"` BgColor string `gorm:"size:50;default:#3788d9;comment:背景颜色"`
IsPublic bool `gorm:"default:false;comment:是否为公共日程"`
Remark string `gorm:"type:text;comment:备注"` Remark string `gorm:"type:text;comment:备注"`
CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:创建时间"` CreatedAt *time.Time `gorm:"type:datetime;autoCreateTime;comment:创建时间"`
@@ -89,27 +91,61 @@ type fromAddCalendarEvent struct {
Start *time.Time `json:"start" binding:"required"` Start *time.Time `json:"start" binding:"required"`
End *time.Time `json:"end" binding:"required"` End *time.Time `json:"end" binding:"required"`
Color string `json:"color"` Color string `json:"color"`
Is_public bool `json:"is_public"`
Remark string `json:"remark"` Remark string `json:"remark"`
} }
type fromUpdateCalendarEvent struct { type fromUpdateCalendarEvent struct {
ID uint `json:"id" binding:"required"` ID uint `json:"id" binding:"required"`
Title string `json:"title" binding:"required"` Title string `json:"title" binding:"required"`
Start *time.Time `json:"start" binding:"required"` Start *time.Time `json:"start" binding:"required"`
End *time.Time `json:"end" binding:"required"` End *time.Time `json:"end" binding:"required"`
Color string `json:"color"` Color string `json:"color"`
Remark string `json:"remark"` Is_public bool `json:"is_public"`
Remark string `json:"remark"`
} }
type fromDeleteCalendarEvent struct { type fromDeleteCalendarEvent struct {
ID uint `json:"id" binding:"required"` ID uint `json:"id" binding:"required"`
} }
var (
calendarUserGroup TabUserGroups
calendarAdmins []uint
)
// CalendarUpdateAdminsCash 更新客户管理员缓存
func CalendarUpdateAdminsCash() {
calendarAdmins = nil
calendarAdmins = append(calendarAdmins, 1) // id=1 系统管理员默认拥有所有权限
var binds []TabUserGroupBinds
models.DB.Where("group_id = ?", calendarUserGroup.ID).Find(&binds)
for _, item := range binds {
if !slices.Contains(calendarAdmins, item.UserID) {
calendarAdmins = append(calendarAdmins, item.UserID)
}
}
}
// canModifyCustomer 判断是否有权限修改/删除客户(创建者或管理员)
func canModifyCalendar(userID, creatorUserID uint) bool {
if slices.Contains(calendarAdmins, userID) {
return true
}
return userID == creatorUserID
}
func ApiCalendarInit() { func ApiCalendarInit() {
// 初始化数据表 // 初始化数据表
models.DB.AutoMigrate(&TabCalendar{}) models.DB.AutoMigrate(&TabCalendar{})
models.DB.AutoMigrate(&TabCalendarEvent{}) models.DB.AutoMigrate(&TabCalendarEvent{})
models.DB.AutoMigrate(&TabCalendarLog{}) models.DB.AutoMigrate(&TabCalendarLog{})
// 自动创建 calendar_admin 用户组
models.DB.Where("name = ?", "calendar_admin").FirstOrCreate(&calendarUserGroup, TabUserGroups{
Name: "calendar_admin",
Type: "usergroup",
})
} }
func ApiCalendar(r *gin.RouterGroup) { func ApiCalendar(r *gin.RouterGroup) {
@@ -152,12 +188,41 @@ func ApiCalendar(r *gin.RouterGroup) {
} }
}) })
// 获取日历列表 // 获取日历列表(不需要登录)
r.POST("/calendar/list", func(ctx *gin.Context) { r.POST("/calendar/list", func(ctx *gin.Context) {
isAuth, user, _ := AuthenticationAuthority(ctx)
var calendars []TabCalendar var calendars []TabCalendar
models.DB.Where("deleted_at IS NULL").Order("created_at DESC").Find(&calendars) models.DB.Where("deleted_at IS NULL").Order("created_at DESC").Find(&calendars)
ReturnJson(ctx, "apiOK", gin.H{"list": calendars})
type CalendarWithEdit struct {
TabCalendar
CanEdit bool `json:"canEdit"`
}
var result []CalendarWithEdit
for _, cal := range calendars {
// 私有日历:只有创建者可见
if !cal.IsPublic {
if !isAuth || cal.UserID != user.ID {
continue
}
result = append(result, CalendarWithEdit{
TabCalendar: cal,
CanEdit: true,
})
continue
}
// 公开日历
canEdit := false
if isAuth {
canEdit = canModifyCalendar(user.ID, cal.UserID)
}
result = append(result, CalendarWithEdit{
TabCalendar: cal,
CanEdit: canEdit,
})
}
ReturnJson(ctx, "apiOK", gin.H{"list": result})
}) })
@@ -303,6 +368,7 @@ func ApiCalendar(r *gin.RouterGroup) {
StartDate: from.Start, StartDate: from.Start,
EndDate: from.End, EndDate: from.End,
BgColor: from.Color, BgColor: from.Color,
IsPublic: from.Is_public,
Remark: from.Remark, Remark: from.Remark,
} }
if event.BgColor == "" { if event.BgColor == "" {
@@ -352,6 +418,7 @@ func ApiCalendar(r *gin.RouterGroup) {
StartDate: from.Start, StartDate: from.Start,
EndDate: from.End, EndDate: from.End,
BgColor: from.Color, BgColor: from.Color,
IsPublic: from.Is_public,
Remark: from.Remark, Remark: from.Remark,
} }
if newEvent.BgColor == "" { if newEvent.BgColor == "" {
+3 -1
View File
@@ -164,7 +164,7 @@ func ApiSysAdmin(r *gin.RouterGroup) {
return return
} }
var params struct { var params struct {
GroupID float64 `json:"group_id" mapstructure:"group_id"` GroupID float64 `json:"group_id" mapstructure:"group_id"`
Page float64 `json:"page" mapstructure:"page"` Page float64 `json:"page" mapstructure:"page"`
PageSize float64 `json:"page_size" mapstructure:"page_size"` PageSize float64 `json:"page_size" mapstructure:"page_size"`
@@ -462,6 +462,8 @@ func ApiSysAdmin(r *gin.RouterGroup) {
WarehouseUpdateAdminsCash() WarehouseUpdateAdminsCash()
case "customer_admin": case "customer_admin":
CustomerUpdateAdminsCash() CustomerUpdateAdminsCash()
case "calendar_admin":
CalendarUpdateAdminsCash()
} }
ReturnJson(ctx, "apiOK", nil) ReturnJson(ctx, "apiOK", nil)
+1 -1
View File
@@ -339,7 +339,7 @@ func AuthenticationAuthority(ctx *gin.Context) (bool, TabUser, map[string]interf
} }
} else { } else {
ReturnJson(ctx, "userCookieError", nil) //ReturnJson(ctx, "userCookieError", nil)
return false, user, nil return false, user, nil
} }
+2 -1
View File
@@ -638,6 +638,7 @@
"remark_placeholder": "Enter remark", "remark_placeholder": "Enter remark",
"event_save_success": "Event saved successfully", "event_save_success": "Event saved successfully",
"event_delete_success": "Event deleted successfully", "event_delete_success": "Event deleted successfully",
"confirm_delete_event": "Are you sure you want to delete this event?" "confirm_delete_event": "Are you sure you want to delete this event?",
"is_public_event": "Public Event"
} }
} }
+2 -1
View File
@@ -638,6 +638,7 @@
"remark_placeholder": "请输入备注", "remark_placeholder": "请输入备注",
"event_save_success": "事件保存成功", "event_save_success": "事件保存成功",
"event_delete_success": "事件删除成功", "event_delete_success": "事件删除成功",
"confirm_delete_event": "确定要删除此事件吗?" "confirm_delete_event": "确定要删除此事件吗?",
"is_public_event": "公共日程"
} }
} }
@@ -37,6 +37,7 @@ const eventData = ref({
startDate: "", startDate: "",
endDate: "", endDate: "",
color: "#3788d9", color: "#3788d9",
isPublic: false,
isEditing: false, isEditing: false,
isEditable: false, isEditable: false,
}) })
@@ -77,13 +78,14 @@ function closeEventModal() {
showModal.value = false showModal.value = false
} }
function openEventModal(dateStr, dataEnd, id = 0, title = "", color = "#3788d9", isEditing = false, isEditable = true) { function openEventModal(dateStr, dataEnd, id = 0, title = "", color = "#3788d9", isPublic = false, isEditing = false, isEditable = true) {
eventData.value = { eventData.value = {
id: id, id: id,
title: title, title: title,
startDate: dateStr, startDate: dateStr,
endDate: dataEnd, endDate: dataEnd,
color: color, color: color,
isPublic: isPublic,
isEditing: isEditing, isEditing: isEditing,
isEditable: isEditable, isEditable: isEditable,
} }
@@ -97,6 +99,7 @@ function editEvent(info) {
parseInt(info.event.id), parseInt(info.event.id),
info.event.title, info.event.title,
info.event.backgroundColor, info.event.backgroundColor,
info.event.extendedProps?.isPublic || false,
true, true,
info.event.durationEditable, info.event.durationEditable,
) )
@@ -142,6 +145,7 @@ async function saveEvent() {
: DateUtils.toRealEnd(eventData.value.endDate), : DateUtils.toRealEnd(eventData.value.endDate),
), ),
schedule_type: scheduleType, schedule_type: scheduleType,
is_public: eventData.value.isPublic,
}) })
} else { } else {
result = await calendarApi.addEvent({ result = await calendarApi.addEvent({
@@ -154,6 +158,7 @@ async function saveEvent() {
: DateUtils.toRealEnd(eventData.value.endDate), : DateUtils.toRealEnd(eventData.value.endDate),
), ),
schedule_type: scheduleType, schedule_type: scheduleType,
is_public: eventData.value.isPublic,
}) })
} }
@@ -563,6 +568,20 @@ onMounted(() => {
</div> </div>
</div> </div>
</div> </div>
<!-- 公共日程开关 -->
<div class="mb-4 flex items-center justify-between">
<span class="text-gray-700">{{ t('calendar.is_public_event') }}</span>
<label class="relative inline-flex items-center cursor-pointer">
<input
v-model="eventData.isPublic"
type="checkbox"
class="sr-only peer"
:disabled="!eventData.isEditable"
/>
<div class="w-11 h-6 bg-gray-300 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-cyan-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-cyan-600 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed"></div>
</label>
</div>
</div> </div>
<!-- 底部固定 --> <!-- 底部固定 -->