- 背景:部分学校(如合肥大学)在「节假日离返校」应用要求假期结束做返校登记,
原系统只支持 信息收集/签到/查寝 等,无法处理该类型。
- 通过应用大厅 getAppInfos 枚举定位到 wec-counselor-leave-return-apps 应用;
实测该校接口:queryReturnConfigList(校区坐标+半径)、queryStuRegisterList(列表)、
submitStuReturnRegisterInfo(提交返校位置)。
- 待办判定:status=="1" 且 returnRegisterInfo 为空;提交参数
{taskWid,stuWid,returnAddress,returnLongitude,returnLatitude,returnSignType}。
- 后端:leave_return.go 服务 + handler + 路由 /leave/return-register/{pending,execute}
(默认用学校配置校区坐标,范围内登记;支持范围外带原因)。
- 前端:请销假页展示「返校登记」卡片,有则显示、一键提交。
This commit is contained in:
@@ -118,6 +118,9 @@ func main() {
|
||||
leave.Post("/apply-approvers", handler.GetLeaveApplyApprovers)
|
||||
leave.Post("/cc-search", handler.GetLeaveNewCcSearch)
|
||||
leave.Post("/apply", handler.ExecuteLeaveApply)
|
||||
// 节假日离返校:返校登记(确认到达)
|
||||
leave.Get("/return-register/pending", handler.GetReturnRegisterPending)
|
||||
leave.Post("/return-register/execute", handler.ExecuteReturnRegister)
|
||||
|
||||
// CDK 兑换
|
||||
cdk := api.Group("/cdk", middleware.AuthRequired())
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"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-return-apps) ==========
|
||||
//
|
||||
// 部分学校(如合肥大学)在「节假日离返校」应用中要求假期结束后做「返校登记/确认到达」。
|
||||
// 本模块提供:列出待办返校登记 + 一键提交返校位置(范围内用配置坐标)。
|
||||
|
||||
// GetReturnRegisterPending 列出当前用户的待办返校登记
|
||||
func GetReturnRegisterPending(c *fiber.Ctx) error {
|
||||
user, err := loadLeaveUser(c)
|
||||
if err != nil {
|
||||
return utils.FailAuth(c, err.Error())
|
||||
}
|
||||
|
||||
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
||||
host := getSchoolHost(user.SchoolID)
|
||||
|
||||
tasks, err := cpd.FetchLeaveReturnPending(cookieMap, host)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
if refreshErr := refreshCpdailyCookie(user); refreshErr == nil {
|
||||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||||
tasks, err = cpd.FetchLeaveReturnPending(cookieMap, host)
|
||||
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
||||
err = ErrPasswordChanged
|
||||
}
|
||||
}
|
||||
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, tasks)
|
||||
}
|
||||
|
||||
// ExecuteReturnRegister 执行返校登记(确认到达)
|
||||
// 请求:{ taskWid, stuWid }(可选 address/longitude/latitude/signType 覆盖,默认用配置校区坐标)
|
||||
func ExecuteReturnRegister(c *fiber.Ctx) error {
|
||||
userID := c.Locals("userId").(uint)
|
||||
|
||||
var req struct {
|
||||
TaskWid int `json:"taskWid"`
|
||||
StuWid int `json:"stuWid"`
|
||||
Address string `json:"address"`
|
||||
Longitude string `json:"longitude"`
|
||||
Latitude string `json:"latitude"`
|
||||
SignType int `json:"signType"` // 0=自动(按配置坐标,1);1=范围内;2=范围外
|
||||
// 范围外登记原因(signType=2 时必填)
|
||||
OutsideReason string `json:"outsideReason"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return utils.FailParam(c, "请求参数错误")
|
||||
}
|
||||
if req.TaskWid == 0 || req.StuWid == 0 {
|
||||
return utils.FailParam(c, "参数不完整")
|
||||
}
|
||||
|
||||
var user model.SysUser
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
return utils.FailAuth(c, "用户不存在")
|
||||
}
|
||||
if user.Cookie == "" {
|
||||
return utils.Fail(c, 400, "请先登录")
|
||||
}
|
||||
|
||||
cookieMap := cpd.ParseCookieJSON(user.Cookie)
|
||||
host := getSchoolHost(user.SchoolID)
|
||||
|
||||
// 位置:优先用请求传入;否则取学校配置的校区坐标(范围内登记)
|
||||
address, longitude, latitude := req.Address, req.Longitude, req.Latitude
|
||||
signType := req.SignType
|
||||
if address == "" || longitude == "" || latitude == "" {
|
||||
places, err := cpd.FetchLeaveReturnPlaces(cookieMap, host)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
||||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||||
places, err = cpd.FetchLeaveReturnPlaces(cookieMap, host)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("获取返校位置配置失败", "userId", userID, "error", err)
|
||||
return utils.Fail(c, 503, "获取返校位置配置失败: "+err.Error())
|
||||
}
|
||||
if len(places) == 0 {
|
||||
return utils.Fail(c, 400, "学校未配置返校确认位置,无法自动登记")
|
||||
}
|
||||
p := places[0]
|
||||
address, longitude, latitude = p.Address, p.Longitude, p.Latitude
|
||||
if signType == 0 {
|
||||
signType = 1
|
||||
}
|
||||
}
|
||||
if signType == 0 {
|
||||
signType = 1
|
||||
}
|
||||
if signType == 2 && strings.TrimSpace(req.OutsideReason) == "" {
|
||||
return utils.FailParam(c, "范围外登记需填写原因")
|
||||
}
|
||||
|
||||
err := cpd.SubmitLeaveReturnRegister(cookieMap, host, req.TaskWid, req.StuWid, address, longitude, latitude, signType, req.OutsideReason)
|
||||
if err != nil && isCookieExpired(err) {
|
||||
if refreshErr := refreshCpdailyCookie(&user); refreshErr == nil {
|
||||
cookieMap = cpd.ParseCookieJSON(user.Cookie)
|
||||
err = cpd.SubmitLeaveReturnRegister(cookieMap, host, req.TaskWid, req.StuWid, address, longitude, latitude, signType, req.OutsideReason)
|
||||
} else if errors.Is(refreshErr, ErrPasswordChanged) {
|
||||
err = ErrPasswordChanged
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("返校登记失败", "userId", userID, "taskWid", req.TaskWid, "stuWid", req.StuWid, "error", err)
|
||||
if errors.Is(err, ErrPasswordChanged) {
|
||||
return utils.Fail(c, 401, "登录凭证已过期,请重新登录")
|
||||
}
|
||||
return utils.Fail(c, 400, "返校登记失败: "+err.Error())
|
||||
}
|
||||
|
||||
slog.Info("返校登记成功", "userId", userID, "taskWid", req.TaskWid, "stuWid", req.StuWid, "address", address)
|
||||
return utils.Success(c, fiber.Map{
|
||||
"message": "返校登记成功",
|
||||
"address": address,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package cpdaily
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ========== 节假日离返校(wec-counselor-leave-return-apps)返校登记 ==========
|
||||
//
|
||||
// 该应用为部分学校(如合肥大学)的"节假日离返校"应用,学生端流程为:
|
||||
// 离校登记(登记假期去向)→ 假期结束返校时「返校登记 / 确认到达」(提交返校位置)。
|
||||
// 本文件只覆盖「返校登记/确认到达」:列出待办 + 提交返校位置。
|
||||
//
|
||||
// 接口(明文 JSON,Header 与签到一致:CpdailyClientType: CPDAILY):
|
||||
// POST /stu/leave/queryReturnConfigList 返校确认位置配置(校区坐标 + 半径)
|
||||
// POST /stu/leave/queryStuRegisterList 登记列表(hasHistory=false 取当前待办)
|
||||
// POST /stu/leave/submitStuReturnRegisterInfo 提交返校登记
|
||||
//
|
||||
// 待办判定:status=="1"(返校登记阶段)且 returnRegisterInfo==null(尚未登记返校)。
|
||||
|
||||
const leaveReturnAPIBase = "/wec-counselor-leave-return-apps"
|
||||
|
||||
// LeaveReturnPlace 返校确认位置(校区坐标 + 允许半径)
|
||||
type LeaveReturnPlace struct {
|
||||
Wid int `json:"wid"`
|
||||
Address string `json:"address"`
|
||||
Longitude string `json:"longitude"`
|
||||
Latitude string `json:"latitude"`
|
||||
Scope float64 `json:"scope"` // 允许半径(米)
|
||||
}
|
||||
|
||||
// LeaveReturnTask 待办返校登记(一名学生在一期离返校任务中的返校登记)
|
||||
type LeaveReturnTask struct {
|
||||
TaskWid int `json:"taskWid"`
|
||||
StuWid int `json:"stuWid"`
|
||||
LeaveName string `json:"leaveName"` // 如「2026年中秋节离返校」
|
||||
StartTime string `json:"startTime"` // 假期开始
|
||||
EndTime string `json:"endTime"` // 假期结束
|
||||
SignType int `json:"signType"` // 1=范围内 2=范围外(提交时按坐标计算)
|
||||
Disclaimer string `json:"disclaimer"` // 承诺书文案(透传给前端展示)
|
||||
}
|
||||
|
||||
// leaveReturnPost 通用 POST(明文 JSON,与签到同 Header)
|
||||
func leaveReturnPost(cookieMap map[string]string, url string, body interface{}) ([]byte, error) {
|
||||
client := newTaskClient()
|
||||
resp, err := client.R().
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Linux; Android 15; Redmi Note 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/131.0.6778.81 Mobile Safari/537.36 cpdaily/9.9.6 wisedu/9.9.6").
|
||||
SetHeader("CpdailyClientType", "CPDAILY").
|
||||
SetHeader("Content-Type", "application/json; charset=UTF-8").
|
||||
SetHeader("Cookie", cookiesToStr(cookieMap)).
|
||||
SetBody(body).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.RawBody().Close()
|
||||
respBody, err := io.ReadAll(resp.RawBody())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if code := resp.StatusCode(); code < 200 || code >= 300 {
|
||||
return respBody, fmt.Errorf("学校服务端响应异常(HTTP %d): %s", code, strings.TrimSpace(string(respBody)))
|
||||
}
|
||||
if len(respBody) == 0 {
|
||||
return respBody, fmt.Errorf("学校服务端返回空响应(HTTP %d)", resp.StatusCode())
|
||||
}
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
// FetchLeaveReturnPlaces 返校确认位置配置(取第一条;该校通常一个校区)
|
||||
func FetchLeaveReturnPlaces(cookieMap map[string]string, host string) ([]LeaveReturnPlace, error) {
|
||||
body, err := leaveReturnPost(cookieMap, host+leaveReturnAPIBase+"/stu/leave/queryReturnConfigList", map[string]interface{}{
|
||||
"pageNumber": 1,
|
||||
"pageSize": 20,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取返校位置配置失败: %w", err)
|
||||
}
|
||||
var result struct {
|
||||
Code string `json:"code"`
|
||||
Datas struct {
|
||||
Rows []LeaveReturnPlace `json:"rows"`
|
||||
} `json:"datas"`
|
||||
}
|
||||
if err := unmarshalBody(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析返校位置配置失败: %w (body: %.200s)", err, string(body))
|
||||
}
|
||||
if result.Code != "0" {
|
||||
return nil, fmt.Errorf("返校位置配置接口返回异常(code=%s)", result.Code)
|
||||
}
|
||||
return result.Datas.Rows, nil
|
||||
}
|
||||
|
||||
// leaveReturnListRow queryStuRegisterList 行
|
||||
type leaveReturnListRow struct {
|
||||
TaskWid int `json:"taskWid"`
|
||||
StuWid int `json:"stuWid"`
|
||||
Status string `json:"status"`
|
||||
LeaveName string `json:"leaveName"`
|
||||
StartTime string `json:"startTime"`
|
||||
EndTime string `json:"endTime"`
|
||||
ReturnSignType string `json:"returnSignType"`
|
||||
Disclaimer string `json:"disclaimer"`
|
||||
ReturnRegisterInfo json.RawMessage `json:"returnRegisterInfo"`
|
||||
}
|
||||
|
||||
// FetchLeaveReturnPending 待办返校登记:status=="1" 且 returnRegisterInfo 为空的行。
|
||||
// hasHistory=false 表示只取当前(未归档)任务。
|
||||
func FetchLeaveReturnPending(cookieMap map[string]string, host string) ([]LeaveReturnTask, error) {
|
||||
body, err := leaveReturnPost(cookieMap, host+leaveReturnAPIBase+"/stu/leave/queryStuRegisterList", map[string]interface{}{
|
||||
"hasHistory": false,
|
||||
"pageNumber": 1,
|
||||
"pageSize": 20,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取返校登记列表失败: %w", err)
|
||||
}
|
||||
var result struct {
|
||||
Code string `json:"code"`
|
||||
Datas struct {
|
||||
Rows []leaveReturnListRow `json:"rows"`
|
||||
} `json:"datas"`
|
||||
}
|
||||
if err := unmarshalBody(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析返校登记列表失败: %w (body: %.200s)", err, string(body))
|
||||
}
|
||||
if result.Code != "0" {
|
||||
return nil, fmt.Errorf("返校登记列表接口返回异常(code=%s)", result.Code)
|
||||
}
|
||||
return leaveReturnTasksFromRows(result.Datas.Rows), nil
|
||||
}
|
||||
|
||||
// leaveReturnTasksFromRows 从列表行中筛出待办返校登记:
|
||||
// status=="1"(返校登记阶段)且 returnRegisterInfo 为空(尚未登记返校)。
|
||||
func leaveReturnTasksFromRows(rows []leaveReturnListRow) []LeaveReturnTask {
|
||||
out := make([]LeaveReturnTask, 0)
|
||||
for _, r := range rows {
|
||||
if r.Status != "1" {
|
||||
continue
|
||||
}
|
||||
ri := strings.TrimSpace(string(r.ReturnRegisterInfo))
|
||||
if ri != "" && ri != "null" {
|
||||
continue // 已登记返校
|
||||
}
|
||||
signType := 1
|
||||
if strings.TrimSpace(r.ReturnSignType) == "2" {
|
||||
signType = 2
|
||||
}
|
||||
out = append(out, LeaveReturnTask{
|
||||
TaskWid: r.TaskWid,
|
||||
StuWid: r.StuWid,
|
||||
LeaveName: r.LeaveName,
|
||||
StartTime: r.StartTime,
|
||||
EndTime: r.EndTime,
|
||||
SignType: signType,
|
||||
Disclaimer: r.Disclaimer,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SubmitLeaveReturnRegister 提交返校登记(确认到达)
|
||||
// taskWid/stuWid 取自列表;address/longitude/latitude 为返校位置。
|
||||
// 范围内使用配置坐标(signType=1);范围外(signType=2)须填原因。
|
||||
func SubmitLeaveReturnRegister(cookieMap map[string]string, host string, taskWid, stuWid int, address, longitude, latitude string, signType int, outsideReason string) error {
|
||||
payload := map[string]interface{}{
|
||||
"taskWid": taskWid,
|
||||
"stuWid": stuWid,
|
||||
"returnAddress": address,
|
||||
"returnLongitude": longitude,
|
||||
"returnLatitude": latitude,
|
||||
"returnSignType": fmt.Sprintf("%d", signType),
|
||||
}
|
||||
if signType == 2 {
|
||||
payload["outsideSignReason"] = outsideReason
|
||||
}
|
||||
body, err := leaveReturnPost(cookieMap, host+leaveReturnAPIBase+"/stu/leave/submitStuReturnRegisterInfo", payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("提交返校登记失败: %w", err)
|
||||
}
|
||||
var result struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := unmarshalBody(body, &result); err != nil {
|
||||
return fmt.Errorf("解析返校登记响应失败: %w (body: %.300s)", err, string(body))
|
||||
}
|
||||
slog.Info("leave-return/submit 响应", "code", result.Code, "message", result.Message)
|
||||
if result.Code != "0" {
|
||||
msg := strings.TrimSpace(result.Message)
|
||||
if msg == "" || msg == "failed" {
|
||||
msg = "返校登记失败,请稍后重试"
|
||||
}
|
||||
return fmt.Errorf("%s", msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cpdaily
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestLeaveReturnTasksFromRows 待办筛选:仅 status=1 且 returnRegisterInfo 为空才算待办。
|
||||
func TestLeaveReturnTasksFromRows(t *testing.T) {
|
||||
rows := []leaveReturnListRow{
|
||||
{TaskWid: 25, StuWid: 471179, Status: "1", LeaveName: "2026年中秋节离返校", ReturnRegisterInfo: []byte("null")}, // 待办
|
||||
{TaskWid: 25, StuWid: 471179, Status: "0", LeaveName: "2026年中秋节离返校"}, // 非返校阶段
|
||||
{TaskWid: 24, StuWid: 455702, Status: "1", LeaveName: "26年暑假离返校",
|
||||
ReturnRegisterInfo: []byte(`{"returnTime":"2026-09-04 11:21"}`)}, // 已登记
|
||||
{TaskWid: 22, StuWid: 1, Status: "1", LeaveName: "五一离返校",
|
||||
ReturnSignType: "2"}, // 待办,范围外
|
||||
}
|
||||
got := leaveReturnTasksFromRows(rows)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("期望 2 条待办, got %d: %+v", len(got), got)
|
||||
}
|
||||
if got[0].TaskWid != 25 || got[0].StuWid != 471179 || got[0].SignType != 1 {
|
||||
t.Errorf("第 1 条待办不符: %+v", got[0])
|
||||
}
|
||||
if got[1].TaskWid != 22 || got[1].SignType != 2 {
|
||||
t.Errorf("第 2 条待办(范围外)不符: %+v", got[1])
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,41 @@ export interface ExecuteLeaveApplyParams {
|
||||
signatureUrl?: string
|
||||
}
|
||||
|
||||
// ======== 节假日离返校:返校登记 ========
|
||||
|
||||
/** 待办返校登记 */
|
||||
export interface ReturnRegisterTask {
|
||||
taskWid: number
|
||||
stuWid: number
|
||||
/** 如「2026年中秋节离返校」 */
|
||||
leaveName: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
/** 1=范围内 2=范围外 */
|
||||
signType: number
|
||||
disclaimer: string
|
||||
}
|
||||
|
||||
/** 获取待办返校登记(节假日离返校应用) */
|
||||
export function getReturnRegisterPending() {
|
||||
return api.get<{ data: ReturnRegisterTask[] }>('/leave/return-register/pending')
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行返校登记(确认到达)。默认使用学校配置的校区坐标(范围内登记)。
|
||||
*/
|
||||
export function executeReturnRegister(params: {
|
||||
taskWid: number
|
||||
stuWid: number
|
||||
address?: string
|
||||
longitude?: string
|
||||
latitude?: string
|
||||
signType?: number
|
||||
outsideReason?: string
|
||||
}) {
|
||||
return api.post<{ data: { message: string; address: string } }>('/leave/return-register/execute', params)
|
||||
}
|
||||
|
||||
// ======== API ========
|
||||
|
||||
/** 获取今日校园待销假列表(实时请求,附带规定销假位置与开关) */
|
||||
|
||||
@@ -33,6 +33,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待办返校登记(节假日离返校应用;有则显示) -->
|
||||
<div v-if="returnRegisterTasks.length" class="bg-white rounded-xl border border-amber-200 p-4 shadow-sm">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-900">返校登记</p>
|
||||
<p class="text-xs text-gray-400 mt-0.5">假期结束需登记返校(确认到达)</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-amber-50 flex items-center justify-center">
|
||||
<Home class="w-5 h-5 text-amber-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="t in returnRegisterTasks"
|
||||
:key="t.taskWid + '-' + t.stuWid"
|
||||
class="flex items-center justify-between gap-2 bg-amber-50/60 rounded-lg px-3 py-2"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">{{ t.leaveName }}</p>
|
||||
<p class="text-[10px] text-gray-400">{{ t.startTime }} ~ {{ t.endTime }}</p>
|
||||
</div>
|
||||
<button
|
||||
:disabled="returnRegistering === t.taskWid + '-' + t.stuWid"
|
||||
@click="doReturnRegister(t)"
|
||||
class="flex-shrink-0 flex items-center gap-1 bg-amber-500 hover:bg-amber-600 disabled:bg-amber-300 text-white rounded-lg px-3 py-1.5 text-xs font-medium transition-colors"
|
||||
>
|
||||
<LoaderCircle v-if="returnRegistering === t.taskWid + '-' + t.stuWid" class="w-3.5 h-3.5 animate-spin" />
|
||||
<Home v-else class="w-3.5 h-3.5" />
|
||||
返校登记
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 请假/销假日志列表 -->
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 shadow-sm">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">请假/销假日志</h3>
|
||||
@@ -715,7 +749,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { CalendarCheck, CalendarPlus, LoaderCircle, Trash2, X, MapPin, Upload, MessageSquare, Smartphone, Edit3 } from 'lucide-vue-next'
|
||||
import { CalendarCheck, CalendarPlus, LoaderCircle, Trash2, X, MapPin, Upload, MessageSquare, Smartphone, Edit3, Home } from 'lucide-vue-next'
|
||||
import * as leaveApi from '@/api/leave'
|
||||
import * as authApi from '@/api/auth'
|
||||
import type { LeaveItem, LeaveReportPlace, LeaveType } from '@/api/leave'
|
||||
@@ -1088,6 +1122,7 @@ const showBindDeviceDialog = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
loadLogs()
|
||||
loadReturnRegister()
|
||||
try {
|
||||
const res = await authApi.getUserInfo()
|
||||
deviceCode.value = res.data.data?.mobileCode || ''
|
||||
@@ -1096,6 +1131,41 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// ======== 返校登记(节假日离返校应用) ========
|
||||
const returnRegisterTasks = ref<leaveApi.ReturnRegisterTask[]>([])
|
||||
const returnRegistering = ref('')
|
||||
|
||||
async function loadReturnRegister() {
|
||||
try {
|
||||
const res = await leaveApi.getReturnRegisterPending()
|
||||
returnRegisterTasks.value = res.data.data || []
|
||||
} catch {
|
||||
// 该校未开通该应用或接口不可用:静默跳过,不打断页面
|
||||
returnRegisterTasks.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function doReturnRegister(t: leaveApi.ReturnRegisterTask) {
|
||||
const key = t.taskWid + '-' + t.stuWid
|
||||
if (returnRegistering.value) return
|
||||
const ok = await confirmDialog({
|
||||
title: '返校登记',
|
||||
content: `确认对「${t.leaveName}」提交返校登记(确认到达)吗?将使用学校配置的校区位置提交。`,
|
||||
confirmText: '确认登记',
|
||||
})
|
||||
if (!ok) return
|
||||
returnRegistering.value = key
|
||||
try {
|
||||
const res = await leaveApi.executeReturnRegister({ taskWid: t.taskWid, stuWid: t.stuWid })
|
||||
message.success(res.data.data?.message || '返校登记成功')
|
||||
await loadReturnRegister()
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.message || e?.message || '返校登记失败')
|
||||
} finally {
|
||||
returnRegistering.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// ======== 请假申请 ========
|
||||
const showApplyModal = ref(false)
|
||||
const loadingTypes = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user