Files
cpdaily_xiaozhushou/backed/internal/handler/leave_new_handler.go
T
becomer ae4ca4a00e
ci/woodpecker/tag/build Pipeline was successful
v1.2.121:请销假页新增「续假」入口;登录页重构美化
续假入口
- 头卡改为「请假 / 销假 / 续假」三按钮,续假打开只含可续假请假单的列表
- 列表按模式过滤:销假只看可销假单,续假只看可续假单;空态说明续假条件(休假中 + 学校允许)
- LeaveItem 增 canReport(列表含仅可续假的单,销假模式据此排除)

登录页
- 品牌渐变头图(问候语 + 功能标签 + 细网格),登录卡片上浮压住头图
- 滑块分段控件切换学工号/手机号;输入行改为图标 + 原生输入,整行聚焦高亮,已选学校带勾
- 验证码块统一尺寸并带刷新角标;短信按钮倒计时文案;主按钮固定品牌色
- 登录逻辑不变(已验证验证码加载、学校选择、/auth/login 提交体)
2026-10-05 17:41:01 +08:00

234 lines
7.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
})
}