ae4ca4a00e
ci/woodpecker/tag/build Pipeline was successful
续假入口 - 头卡改为「请假 / 销假 / 续假」三按钮,续假打开只含可续假请假单的列表 - 列表按模式过滤:销假只看可销假单,续假只看可续假单;空态说明续假条件(休假中 + 学校允许) - LeaveItem 增 canReport(列表含仅可续假的单,销假模式据此排除) 登录页 - 品牌渐变头图(问候语 + 功能标签 + 细网格),登录卡片上浮压住头图 - 滑块分段控件切换学工号/手机号;输入行改为图标 + 原生输入,整行聚焦高亮,已选学校带勾 - 验证码块统一尺寸并带刷新角标;短信按钮倒计时文案;主按钮固定品牌色 - 登录逻辑不变(已验证验证码加载、学校选择、/auth/login 提交体)
234 lines
7.9 KiB
Go
234 lines
7.9 KiB
Go
package handler
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"log/slog"
|
||
|
||
"cpdaily-new/internal/database"
|
||
"cpdaily-new/internal/model"
|
||
cpd "cpdaily-new/internal/service/cpdaily"
|
||
"cpdaily-new/internal/utils"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
)
|
||
|
||
// ---------- 新套(wec-counselor-leave-new-apps)handler 适配 ----------
|
||
//
|
||
// 设计:新套数据在 handler 层翻译为现有前端契约(LeaveType/LeaveItem/LeaveApprover),
|
||
// 前端无需感知新旧差异;老套逻辑保持原样,仅按 flavor 分流。
|
||
|
||
// refreshAfterCookieExpired 通用「cookie 过期→刷新→重试一次」包装
|
||
func refreshAfterCookieExpired(user *model.SysUser, err error, retry func() error) error {
|
||
if err == nil || !isCookieExpired(err) {
|
||
return err
|
||
}
|
||
slog.Info("cookie过期,尝试刷新", "userId", user.ID)
|
||
if refreshErr := refreshCpdailyCookie(user); refreshErr != nil {
|
||
if errors.Is(refreshErr, ErrPasswordChanged) {
|
||
return ErrPasswordChanged
|
||
}
|
||
return err
|
||
}
|
||
return retry()
|
||
}
|
||
|
||
// ---------- 销假:待销假列表(新套) ----------
|
||
|
||
// fetchLeaveNewPendingList 新套待销假列表:拉全部记录 → 过滤可销假 → 补位置与开关
|
||
func fetchLeaveNewPendingList(cookieMap map[string]string, host string, user *model.SysUser) ([]cpd.LeaveItem, error) {
|
||
records, err := cpd.FetchLeaveNewList(cookieMap, host, 1, 50, "1")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 规定销假位置全局一份(reportList 与记录无关)
|
||
places := []cpd.LeaveReportPlace{}
|
||
if p, err := cpd.FetchLeaveNewReportPlaces(cookieMap, host); err == nil {
|
||
places = leaveNewPlacesToLeaveReportPlaces(p)
|
||
} else {
|
||
slog.Warn("获取新套规定销假位置失败", "userId", user.ID, "error", err)
|
||
}
|
||
|
||
list := make([]cpd.LeaveItem, 0, len(records))
|
||
for _, r := range records {
|
||
// 列表接口不返回 reportMode/reportUploadMode/isLeaveOut,需补拉详情判断;
|
||
// 仅对可能可销假的状态(3 休假中 / 4 销假逾期)补详情,减少请求
|
||
if r.Status != "3" && r.Status != "4" {
|
||
continue
|
||
}
|
||
detail, derr := cpd.FetchLeaveNewDetail(cookieMap, host, r.LeaveID)
|
||
if derr != nil {
|
||
slog.Warn("获取新套请假详情失败,跳过该记录", "leaveId", r.LeaveID, "error", derr)
|
||
continue
|
||
}
|
||
// 可销假 或 可续假(isAllowExtend)其一满足即展示
|
||
if !detail.CanReport && !detail.IsAllowExtend {
|
||
continue
|
||
}
|
||
// 销假需要确认人:reportMode=2(老师审批销假)
|
||
needApprover := detail.ReportMode == "2"
|
||
enableAttach := detail.ReportUploadMode == "2" || (detail.ReportUploadMode == "1" && detail.IsLeaveOut == "1")
|
||
list = append(list, cpd.LeaveItem{
|
||
ID: detail.LeaveID,
|
||
LeaveType: leaveNewTypeNameOfTopic(detail.TopicName, detail.TypeWid),
|
||
Reason: detail.LeaveReason,
|
||
CreateTime: r.ApplyTime,
|
||
ActEndTime: detail.EndTime,
|
||
Status: detail.Status,
|
||
StatusText: detail.StatusText,
|
||
ReportPlaces: places,
|
||
ReportApplyFlow: needApprover,
|
||
EnableAttach: enableAttach,
|
||
CanReport: detail.CanReport,
|
||
CanExtend: detail.IsAllowExtend,
|
||
TypeWid: detail.TypeWid,
|
||
IsLeaveOut: detail.IsLeaveOut,
|
||
})
|
||
}
|
||
return list, nil
|
||
}
|
||
|
||
// leaveNewTypeNameOfTopic 从 topicName(如「李闯闯的事假」)提取类型名兜底
|
||
func leaveNewTypeNameOfTopic(topicName, typeIcon string) string {
|
||
if topicName == "" {
|
||
return "请假"
|
||
}
|
||
return topicName
|
||
}
|
||
|
||
// ---------- 请假类型(新套) ----------
|
||
|
||
// fetchLeaveNewTypesWithDetail 新套类型列表,逐类型补详情(权威开关)
|
||
func fetchLeaveNewTypesWithDetail(cookieMap map[string]string, host string, user *model.SysUser) ([]cpd.LeaveType, error) {
|
||
types, err := cpd.FetchLeaveNewTypes(cookieMap, host)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
details := make(map[int]*cpd.LeaveNewFormDetail, len(types))
|
||
for _, t := range types {
|
||
d, err := cpd.FetchLeaveNewTypeDetail(cookieMap, host, t.TypeWid)
|
||
if err != nil {
|
||
slog.Warn("获取新套类型详情失败,使用基础信息", "typeWid", t.TypeWid, "error", err)
|
||
continue
|
||
}
|
||
details[t.TypeWid] = d
|
||
}
|
||
return leaveNewToLeaveTypes(types, func(wid int) *cpd.LeaveNewFormDetail {
|
||
return details[wid]
|
||
}), nil
|
||
}
|
||
|
||
// ---------- 销假审批人(新套) ----------
|
||
|
||
// fetchLeaveNewReportApprovers 新套销假确认人(getReportApprovers by leaveId)
|
||
func fetchLeaveNewReportApprovers(cookieMap map[string]string, host, leaveID string, user *model.SysUser) ([]cpd.LeaveApprover, error) {
|
||
list, err := cpd.FetchLeaveNewReportApprovers(cookieMap, host, leaveID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return leaveNewApproversToLeaveApprovers(list), nil
|
||
}
|
||
|
||
// ---------- 请假审批人(新套) ----------
|
||
|
||
// leaveNewApplyApproversResult 新套请假审批人结果(含 flowData 透传)
|
||
type leaveNewApplyApproversResult struct {
|
||
Approvers []cpd.LeaveApprover `json:"approvers"`
|
||
NeedApproval bool `json:"needApproval"`
|
||
InFlowScope bool `json:"inFlowScope"`
|
||
FlowData map[string]interface{} `json:"flowData"`
|
||
}
|
||
|
||
// fetchLeaveNewApplyApprovers 新套请假审批人 + 审批流数据
|
||
func fetchLeaveNewApplyApprovers(cookieMap map[string]string, host string, typeWid int, startTime, endTime string) (*leaveNewApplyApproversResult, error) {
|
||
flow, err := cpd.FetchLeaveNewApproveFlow(cookieMap, host, typeWid, startTime, endTime, "")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &leaveNewApplyApproversResult{
|
||
Approvers: leaveNewApproversToLeaveApprovers(flow.ApproveUsers),
|
||
NeedApproval: flow.IsNeedApproval,
|
||
InFlowScope: flow.IsInFlowScope,
|
||
FlowData: flow.FlowData,
|
||
}, nil
|
||
}
|
||
|
||
// GetLeaveNewCcSearch 抄送人搜索(新套;searchKey 为空返回全部)
|
||
func GetLeaveNewCcSearch(c *fiber.Ctx) error {
|
||
user, err := loadLeaveUser(c)
|
||
if err != nil {
|
||
return utils.FailAuth(c, err.Error())
|
||
}
|
||
|
||
var req struct {
|
||
SearchKey string `json:"searchKey"`
|
||
}
|
||
if err := c.BodyParser(&req); err != nil {
|
||
return utils.FailParam(c, "请求参数错误")
|
||
}
|
||
|
||
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
||
host := getSchoolHost(user.SchoolID)
|
||
|
||
list, err := cpd.FetchLeaveNewCcSearch(cookieMap, host, req.SearchKey)
|
||
if err != nil {
|
||
_ = refreshAfterCookieExpired(user, err, func() error {
|
||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||
var e error
|
||
list, e = cpd.FetchLeaveNewCcSearch(cookieMap, host, req.SearchKey)
|
||
return e
|
||
})
|
||
}
|
||
if err != nil {
|
||
slog.Error("获取抄送人失败", "userId", user.ID, "error", err)
|
||
if errors.Is(err, ErrPasswordChanged) {
|
||
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
||
}
|
||
return utils.Fail(c, 503, "获取抄送人失败: "+err.Error())
|
||
}
|
||
|
||
return utils.Success(c, leaveNewApproversToLeaveApprovers(list))
|
||
}
|
||
|
||
// buildLeaveNewApproverByWid 从审批人列表找 userWid 对应完整对象(提交表单用)
|
||
func buildLeaveNewApproverByWid(list []cpd.LeaveNewApprover, userWid string) *cpd.LeaveNewApprover {
|
||
if userWid == "" {
|
||
return nil
|
||
}
|
||
for i := range list {
|
||
if list[i].UserWid == userWid {
|
||
return &list[i]
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// leaveNewCheckCookieFresh 简单的 cookie 新鲜度检查(新套首次调用失败即触发刷新重试)
|
||
func leaveNewRetryOnExpired(user *model.SysUser, err error, retry func() error) error {
|
||
return refreshAfterCookieExpired(user, err, retry)
|
||
}
|
||
|
||
// leaveNewFriendlyErr 新套错误统一包装(带 401 语义)
|
||
func leaveNewFriendlyErr(c *fiber.Ctx, action string, err error) error {
|
||
if errors.Is(err, ErrPasswordChanged) {
|
||
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
||
}
|
||
return utils.Fail(c, 503, fmt.Sprintf("%s: %v", action, err))
|
||
}
|
||
|
||
// ---------- 数据库日志辅助 ----------
|
||
|
||
// saveLeaveLogEntry 请假/销假日志落库(两套共用)
|
||
func saveLeaveLogEntry(userID uint, leaveID, title, schoolName, action string, isSuccess bool, errMsg string) {
|
||
database.DB.Create(&model.CpLeaveLog{
|
||
UserID: userID,
|
||
LeaveID: leaveID,
|
||
Title: title,
|
||
SchoolName: schoolName,
|
||
Action: action,
|
||
IsSuccess: isSuccess,
|
||
ErrorMessage: errMsg,
|
||
})
|
||
}
|