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