更新日历功能
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/holiday"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
@@ -240,6 +241,162 @@ func (c *BackendOaScheduleController) List() {
|
||||
})
|
||||
}
|
||||
|
||||
// Holidays GET /backend/oa/holiday
|
||||
// 返回指定日期区间(或指定年份)内的中国法定节假日/调休日,供日历控件标注
|
||||
// 「休 / 班」角标与节日名称。
|
||||
//
|
||||
// 参数(二选一):
|
||||
// - start_date + end_date:"YYYY-MM-DD",日历按 42 格区间查询;
|
||||
// - year:整年查询。
|
||||
//
|
||||
// 数据获取顺序:内存缓存 → 内置数据(2024-2026,零网络)→ 外网接口(仅未收录年份)。
|
||||
// 已收录年份不会发起任何网络请求,因此日历翻月是纯内存操作、无卡顿。
|
||||
// 响应里的 builtin_years 字段告知前端内置数据覆盖到哪一年。
|
||||
func (c *BackendOaScheduleController) Holidays() {
|
||||
if _, err := c.oaScheduleClaims(); err != nil {
|
||||
c.oasJsonErr(401, 401, "未登录或无权限")
|
||||
return
|
||||
}
|
||||
|
||||
startDate := strings.TrimSpace(c.GetString("start_date"))
|
||||
endDate := strings.TrimSpace(c.GetString("end_date"))
|
||||
yearStr := strings.TrimSpace(c.GetString("year"))
|
||||
|
||||
// 参数归一化:区间优先,其次按年
|
||||
var start, end string
|
||||
if isValidOaDate(startDate) && isValidOaDate(endDate) && startDate <= endDate {
|
||||
start, end = startDate, endDate
|
||||
} else if y, err := strconv.Atoi(yearStr); err == nil && y >= 2000 && y <= 2100 {
|
||||
start = fmt.Sprintf("%04d-01-01", y)
|
||||
end = fmt.Sprintf("%04d-12-31", y)
|
||||
} else {
|
||||
// 未传参时默认当前年份,保证前端任何情况下都能拿到可用数据
|
||||
now := time.Now()
|
||||
start = now.Format("2006-01-01")
|
||||
end = now.Format("2006") + "-12-31"
|
||||
}
|
||||
|
||||
list := ensureOaHolidays(start, end)
|
||||
|
||||
c.oasOk(map[string]interface{}{
|
||||
"list": list,
|
||||
"start_date": start,
|
||||
"end_date": end,
|
||||
"builtin_years": holiday.Years(),
|
||||
})
|
||||
}
|
||||
|
||||
// ensureOaHolidays 取区间内的节假日数据。
|
||||
//
|
||||
// 顺序上的取舍(性能关键):先调 holiday.FetchYear,而不是先查数据库。
|
||||
// 原因是 FetchYear 对已收录年份(2024-2026)直接返回内置数据、不发网络请求,
|
||||
// 也无需查库;日历每次翻月都会走到这里,先查库会白白多一次 SQL 往返。
|
||||
// 只有 FetchYear 真的联网拉到了数据(即年份未收录),才回写数据库缓存,
|
||||
// 供后续同一年份的请求直接命中。
|
||||
func ensureOaHolidays(start, end string) []holiday.Day {
|
||||
list := make([]holiday.Day, 0, 64)
|
||||
|
||||
startYear, endYear := holidayYear(start), holidayYear(end)
|
||||
for y := startYear; y <= endYear; y++ {
|
||||
if y == 0 {
|
||||
continue
|
||||
}
|
||||
items, src, ferr := holiday.FetchYear(y)
|
||||
if ferr != nil || len(items) == 0 {
|
||||
continue
|
||||
}
|
||||
// 仅当数据来自外网时才落库:内置数据本身就在代码里,落库没有收益,
|
||||
// 反而会让每次翻月都产生一批无谓的写库操作。
|
||||
if src == holiday.SourceHolidayCN {
|
||||
saveOaHolidays(items)
|
||||
}
|
||||
for _, d := range items {
|
||||
if d.Date >= start && d.Date <= end {
|
||||
list = append(list, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergeOaHolidays(list)
|
||||
}
|
||||
|
||||
// holidayYear 从 "YYYY-MM-DD" 取年份,失败返回 0。
|
||||
func holidayYear(date string) int {
|
||||
if len(date) < 4 {
|
||||
return 0
|
||||
}
|
||||
y, err := strconv.Atoi(date[:4])
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// saveOaHolidays 把外网拉取到的数据写入缓存表。
|
||||
// 使用 insert-or-update:已存在的日期更新其名称/类型/来源,保证外网数据
|
||||
// 修正后能覆盖库里的旧值。写入失败静默忽略(缓存非强依赖)。
|
||||
func saveOaHolidays(list []holiday.Day) {
|
||||
now := time.Now()
|
||||
for _, d := range list {
|
||||
if !isValidOaDate(d.Date) {
|
||||
continue
|
||||
}
|
||||
var exist models.OaHoliday
|
||||
ferr := models.Orm.QueryTable(new(models.OaHoliday)).
|
||||
Filter("date", d.Date).
|
||||
One(&exist)
|
||||
if ferr == nil {
|
||||
// 已存在:仅当来源或名称有变化时才更新,减少无谓写库
|
||||
if exist.Name == d.Name && exist.Type == d.Type && exist.Source == d.Source {
|
||||
continue
|
||||
}
|
||||
exist.Name = d.Name
|
||||
exist.Type = d.Type
|
||||
exist.Year = d.Year
|
||||
exist.Wage = d.Wage
|
||||
exist.Source = d.Source
|
||||
exist.UpdateTime = &now
|
||||
_, _ = models.Orm.Update(&exist, "Name", "Type", "Year", "Wage", "Source", "UpdateTime")
|
||||
continue
|
||||
}
|
||||
item := models.OaHoliday{
|
||||
Date: d.Date,
|
||||
Name: d.Name,
|
||||
Type: d.Type,
|
||||
Year: d.Year,
|
||||
Wage: d.Wage,
|
||||
Source: d.Source,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
_, _ = models.Orm.Insert(&item)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeOaHolidays 按日期去重并升序排列;同日重复时保留先出现的记录
|
||||
// (数据库缓存排在前面,因此缓存优先于外网新拉取的数据)。
|
||||
func mergeOaHolidays(list []holiday.Day) []holiday.Day {
|
||||
seen := make(map[string]bool, len(list))
|
||||
out := make([]holiday.Day, 0, len(list))
|
||||
for _, d := range list {
|
||||
if d.Date == "" || seen[d.Date] {
|
||||
continue
|
||||
}
|
||||
seen[d.Date] = true
|
||||
out = append(out, d)
|
||||
}
|
||||
for i := 1; i < len(out); i++ {
|
||||
cur := out[i]
|
||||
j := i - 1
|
||||
for j >= 0 && out[j].Date > cur.Date {
|
||||
out[j+1] = out[j]
|
||||
j--
|
||||
}
|
||||
out[j+1] = cur
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Stats GET /backend/oa/schedule/stats
|
||||
// 返回今日/本周/本月的日程统计与逾期数,供页面顶部统计卡片展示。
|
||||
func (c *BackendOaScheduleController) Stats() {
|
||||
|
||||
Reference in New Issue
Block a user