From a89bdd839b49651f163c305b600d7217badff1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Wed, 9 Sep 2026 18:42:22 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E7=A8=8B=E5=92=8C?= =?UTF-8?q?=E6=8F=90=E9=86=92=E4=BA=92=E5=AF=BC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/views/apps/oa/dashboard/index.vue | 110 +++++++++++++++++- go/controllers/backend_reminder.go | 28 ++++- go/controllers/platform_reminder.go | 53 +++++++++ go/routers/platform/platform.go | 1 + go/server.err | 1 - 5 files changed, 184 insertions(+), 9 deletions(-) delete mode 100644 go/server.err diff --git a/backend/src/views/apps/oa/dashboard/index.vue b/backend/src/views/apps/oa/dashboard/index.vue index 17e1570..f735173 100644 --- a/backend/src/views/apps/oa/dashboard/index.vue +++ b/backend/src/views/apps/oa/dashboard/index.vue @@ -11,6 +11,34 @@
+ +
+
+
今日待办
+
{{ todoTodayPending }}
+
+ 共 {{ scheduleStats.today_total || 0 }} 项 · 已完成 + {{ scheduleStats.today_done || 0 }} +
+
+
+
本周待办
+
{{ todoWeekPending }}
+
+ 共 {{ scheduleStats.week_total || 0 }} 项 · 已完成 + {{ scheduleStats.week_done || 0 }} +
+
+
+
本月待办
+
{{ todoMonthPending }}
+
+ 共 {{ scheduleStats.month_total || 0 }} 项 · 已完成 + {{ scheduleStats.month_done || 0 }} +
+
+
+ @@ -252,7 +280,7 @@
@@ -268,7 +296,7 @@ import { getNoticeDetail, getNoticePortal } from "@/api/oaNotice"; -import { finishSchedule, getScheduleList } from "@/api/oaSchedule"; +import { finishSchedule, getScheduleList, getScheduleStats } from "@/api/oaSchedule"; import ScheduleDetail from "../schedule/components/detail.vue"; import SchedulePostpone from "../schedule/components/postponeDialog.vue"; @@ -366,6 +394,18 @@ const typePercent = (amount) => const responseData = (res) => res?.data?.data ?? res?.data ?? res ?? {}; +// ---------- 待办概览(顶部卡片) ---------- +const scheduleStats = ref({}); +const todoTodayPending = computed( + () => (scheduleStats.value.today_total || 0) - (scheduleStats.value.today_done || 0), +); +const todoWeekPending = computed( + () => (scheduleStats.value.week_total || 0) - (scheduleStats.value.week_done || 0), +); +const todoMonthPending = computed( + () => (scheduleStats.value.month_total || 0) - (scheduleStats.value.month_done || 0), +); + // ---------- 工作日程提醒 ---------- const router = useRouter(); const scheduleReminders = ref([]); @@ -526,6 +566,8 @@ const toggleScheduleFinish = async (item) => { ElMessage.success(res.data?.status === 1 ? "已完成" : "已反审核"); scheduleDetailVisible.value = false; loadScheduleReminders(); + // 完成/反审核会改变待办数量,刷新顶部概览卡片 + loadScheduleStats(); } else { ElMessage.error(res?.msg || "操作失败"); } @@ -540,6 +582,12 @@ const handleSchedulePostpone = (item) => { schedulePostponeVisible.value = true; }; +// 顺延保存后:原日程标记完成并生成次日待办,需同步刷新列表与顶部概览卡片 +const onPostponeSaved = () => { + loadScheduleReminders(); + loadScheduleStats(); +}; + const goSchedule = () => router.push("/apps/oa/schedule"); // 跳转到日程管理页并打开该日程的编辑抽屉 @@ -606,6 +654,16 @@ const openNoticeDetail = async (item) => { const goNotice = () => router.push("/apps/oa/notice"); +// 刷新顶部"待办概览"卡片:今日 / 本周 / 本月 未完成数量 +const loadScheduleStats = async () => { + try { + const res = await getScheduleStats(); + scheduleStats.value = res?.data || {}; + } catch (error) { + console.warn("加载待办统计失败:", error?.message); + } +}; + const loadDashboard = async () => { try { const data = responseData(await getReimbursementDashboard()); @@ -618,6 +676,7 @@ const loadDashboard = async () => { } catch (error) { console.warn("加载报销统计失败:", error?.message); } + await loadScheduleStats(); renderTrendChart(); loadScheduleReminders(); loadNotices(); @@ -723,6 +782,40 @@ onBeforeUnmount(() => { } } +/* 待办概览卡片(顶部) */ +.todo-stats-row { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-bottom: 16px; +} + +.todo-stat-card { + background: #fff; + border-radius: 4px; + padding: 16px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); +} + +.todo-stat-label { + font-size: 13px; + color: #909399; + margin-bottom: 6px; +} + +.todo-stat-value { + font-size: 28px; + font-weight: 600; + line-height: 1.2; + color: #303133; +} + +.todo-stat-sub { + margin-top: 6px; + font-size: 12px; + color: #c0c4cc; +} + /* 左右分栏:左 70% 报销,右 30% 日程提醒 */ .dashboard-columns { display: flex; @@ -1202,6 +1295,19 @@ html.dark & { } } + .todo-stat-card { + background: var(--el-bg-color); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + } + + .todo-stat-label { + color: var(--el-text-color-secondary); + } + + .todo-stat-sub { + color: var(--el-text-color-placeholder); + } + .chart-card { background: var(--el-bg-color); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); diff --git a/go/controllers/backend_reminder.go b/go/controllers/backend_reminder.go index 20064e7..b638f14 100644 --- a/go/controllers/backend_reminder.go +++ b/go/controllers/backend_reminder.go @@ -15,6 +15,7 @@ import ( "server/services" beego "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/client/orm" ) type BackendReminderController struct { @@ -90,12 +91,16 @@ func (c *BackendReminderController) GetReminderList() { pageSize = 20 } - qs := models.Orm.QueryTable(new(models.BackendSchedule)). - Filter("tid", claims.TenantId) - + qs := models.Orm.QueryTable(new(models.BackendSchedule)) + cond := orm.NewCondition().And("tid", claims.TenantId) if keyword != "" { - qs = qs.Filter("content__contains", keyword) + // 关键字同时匹配备注(content)与标题(title),兼容关联 OA 工作日程的提醒 + kwCond := orm.NewCondition(). + Or("content__contains", keyword). + Or("title__contains", keyword) + cond = cond.AndCond(kwCond) } + qs = qs.SetCond(cond) total, _ := qs.Count() @@ -127,10 +132,16 @@ func (c *BackendReminderController) GetReminderList() { } } + // 兼容关联 OA 工作日程的提醒:日程备注(content)可能为空, + // 实际描述存在标题(title),此时回退用标题填充内容,避免列表显示空白。 + displayContent := strings.TrimSpace(s.Content) + if displayContent == "" { + displayContent = s.Title + } item := map[string]interface{}{ "id": s.ID, "title": s.Title, - "content": s.Content, + "content": displayContent, "schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"), "remind_channels": channels, "user_id": s.UserID, @@ -209,10 +220,15 @@ func (c *BackendReminderController) GetReminderDetail() { } } + // 同列表:关联 OA 工作日程的提醒 content 可能为空,回退用 title 填充 + detailContent := strings.TrimSpace(schedule.Content) + if detailContent == "" { + detailContent = schedule.Title + } data := map[string]interface{}{ "id": schedule.ID, "title": schedule.Title, - "content": schedule.Content, + "content": detailContent, "schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"), "remind_channels": channels, "receiver_targets": targets, diff --git a/go/controllers/platform_reminder.go b/go/controllers/platform_reminder.go index e8164c8..5378cde 100644 --- a/go/controllers/platform_reminder.go +++ b/go/controllers/platform_reminder.go @@ -555,6 +555,59 @@ func (c *PlatformReminderController) BatchDeleteReminder() { c.ok(nil) } +// FinishReminder POST /platform/reminder/:id/finish +// 切换日程提醒的结束状态:若尚有提醒未结束则全部置为已结束;若已全部结束则恢复为待提醒。 +func (c *PlatformReminderController) FinishReminder() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.jsonErr(400, 400, "无效的ID") + return + } + + var schedule models.PlatformSchedule + err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule) + if err != nil { + c.jsonErr(404, 404, "日程未找到") + return + } + + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", id). + Filter("is_deleted", 0). + All(&reminders) + + allFinished := len(reminders) > 0 + for _, r := range reminders { + if r.RemindStatus != 2 { + allFinished = false + break + } + } + + var newStatus int8 + if allFinished { + newStatus = 0 + } else { + newStatus = 2 + } + + now := time.Now() + for i := range reminders { + reminders[i].RemindStatus = newStatus + reminders[i].UpdateTime = now + _, _ = models.Orm.Update(&reminders[i], "RemindStatus", "UpdateTime") + } + + c.ok(map[string]interface{}{"is_finished": newStatus == 2}) +} + type reminderTestPayload struct { Title string `json:"title"` Content string `json:"content"` diff --git a/go/routers/platform/platform.go b/go/routers/platform/platform.go index 0440cbe..6596ad0 100644 --- a/go/routers/platform/platform.go +++ b/go/routers/platform/platform.go @@ -293,6 +293,7 @@ func Register() { beego.Router("/platform/reminder/:id", &controllers.PlatformReminderController{}, "get:GetReminderDetail;put:UpdateReminder;delete:DeleteReminder") beego.Router("/platform/reminder", &controllers.PlatformReminderController{}, "post:CreateReminder") beego.Router("/platform/reminder/batchDelete", &controllers.PlatformReminderController{}, "post:BatchDeleteReminder") + beego.Router("/platform/reminder/:id/finish", &controllers.PlatformReminderController{}, "post:FinishReminder") // 官网模板管理(上传/扫描登记/在线编辑/启停删除 + 标签调用说明) beego.Router("/platform/template/index", &controllers.PlatformTemplateController{}, "get:Index") diff --git a/go/server.err b/go/server.err deleted file mode 100644 index d8ec1ad..0000000 --- a/go/server.err +++ /dev/null @@ -1 +0,0 @@ -exit status 0xffffffff