优化日程和提醒互导功能
This commit is contained in:
@@ -11,6 +11,34 @@
|
|||||||
<div class="dashboard-columns">
|
<div class="dashboard-columns">
|
||||||
<!-- 左侧 60%:报销相关内容 -->
|
<!-- 左侧 60%:报销相关内容 -->
|
||||||
<div class="col-left">
|
<div class="col-left">
|
||||||
|
<!-- 待办概览卡片:今日 / 本周 / 本月 待办数量 -->
|
||||||
|
<div class="todo-stats-row">
|
||||||
|
<div class="todo-stat-card">
|
||||||
|
<div class="todo-stat-label">今日待办</div>
|
||||||
|
<div class="todo-stat-value">{{ todoTodayPending }}</div>
|
||||||
|
<div class="todo-stat-sub">
|
||||||
|
共 {{ scheduleStats.today_total || 0 }} 项 · 已完成
|
||||||
|
{{ scheduleStats.today_done || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="todo-stat-card">
|
||||||
|
<div class="todo-stat-label">本周待办</div>
|
||||||
|
<div class="todo-stat-value">{{ todoWeekPending }}</div>
|
||||||
|
<div class="todo-stat-sub">
|
||||||
|
共 {{ scheduleStats.week_total || 0 }} 项 · 已完成
|
||||||
|
{{ scheduleStats.week_done || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="todo-stat-card">
|
||||||
|
<div class="todo-stat-label">本月待办</div>
|
||||||
|
<div class="todo-stat-value">{{ todoMonthPending }}</div>
|
||||||
|
<div class="todo-stat-sub">
|
||||||
|
共 {{ scheduleStats.month_total || 0 }} 项 · 已完成
|
||||||
|
{{ scheduleStats.month_done || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 报销统计卡片 -->
|
<!-- 报销统计卡片 -->
|
||||||
<el-row :gutter="16" class="stat-row">
|
<el-row :gutter="16" class="stat-row">
|
||||||
<el-col :span="8" :xs="24" v-for="item in statCards" :key="item.key">
|
<el-col :span="8" :xs="24" v-for="item in statCards" :key="item.key">
|
||||||
@@ -252,7 +280,7 @@
|
|||||||
<SchedulePostpone
|
<SchedulePostpone
|
||||||
v-model="schedulePostponeVisible"
|
v-model="schedulePostponeVisible"
|
||||||
:schedule="scheduleDetailItem"
|
:schedule="scheduleDetailItem"
|
||||||
@saved="loadScheduleReminders"
|
@saved="onPostponeSaved"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -268,7 +296,7 @@ import {
|
|||||||
getNoticeDetail,
|
getNoticeDetail,
|
||||||
getNoticePortal
|
getNoticePortal
|
||||||
} from "@/api/oaNotice";
|
} from "@/api/oaNotice";
|
||||||
import { finishSchedule, getScheduleList } from "@/api/oaSchedule";
|
import { finishSchedule, getScheduleList, getScheduleStats } from "@/api/oaSchedule";
|
||||||
import ScheduleDetail from "../schedule/components/detail.vue";
|
import ScheduleDetail from "../schedule/components/detail.vue";
|
||||||
import SchedulePostpone from "../schedule/components/postponeDialog.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 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 router = useRouter();
|
||||||
const scheduleReminders = ref([]);
|
const scheduleReminders = ref([]);
|
||||||
@@ -526,6 +566,8 @@ const toggleScheduleFinish = async (item) => {
|
|||||||
ElMessage.success(res.data?.status === 1 ? "已完成" : "已反审核");
|
ElMessage.success(res.data?.status === 1 ? "已完成" : "已反审核");
|
||||||
scheduleDetailVisible.value = false;
|
scheduleDetailVisible.value = false;
|
||||||
loadScheduleReminders();
|
loadScheduleReminders();
|
||||||
|
// 完成/反审核会改变待办数量,刷新顶部概览卡片
|
||||||
|
loadScheduleStats();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res?.msg || "操作失败");
|
ElMessage.error(res?.msg || "操作失败");
|
||||||
}
|
}
|
||||||
@@ -540,6 +582,12 @@ const handleSchedulePostpone = (item) => {
|
|||||||
schedulePostponeVisible.value = true;
|
schedulePostponeVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 顺延保存后:原日程标记完成并生成次日待办,需同步刷新列表与顶部概览卡片
|
||||||
|
const onPostponeSaved = () => {
|
||||||
|
loadScheduleReminders();
|
||||||
|
loadScheduleStats();
|
||||||
|
};
|
||||||
|
|
||||||
const goSchedule = () => router.push("/apps/oa/schedule");
|
const goSchedule = () => router.push("/apps/oa/schedule");
|
||||||
|
|
||||||
// 跳转到日程管理页并打开该日程的编辑抽屉
|
// 跳转到日程管理页并打开该日程的编辑抽屉
|
||||||
@@ -606,6 +654,16 @@ const openNoticeDetail = async (item) => {
|
|||||||
|
|
||||||
const goNotice = () => router.push("/apps/oa/notice");
|
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 () => {
|
const loadDashboard = async () => {
|
||||||
try {
|
try {
|
||||||
const data = responseData(await getReimbursementDashboard());
|
const data = responseData(await getReimbursementDashboard());
|
||||||
@@ -618,6 +676,7 @@ const loadDashboard = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("加载报销统计失败:", error?.message);
|
console.warn("加载报销统计失败:", error?.message);
|
||||||
}
|
}
|
||||||
|
await loadScheduleStats();
|
||||||
renderTrendChart();
|
renderTrendChart();
|
||||||
loadScheduleReminders();
|
loadScheduleReminders();
|
||||||
loadNotices();
|
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% 日程提醒 */
|
/* 左右分栏:左 70% 报销,右 30% 日程提醒 */
|
||||||
.dashboard-columns {
|
.dashboard-columns {
|
||||||
display: flex;
|
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 {
|
.chart-card {
|
||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"server/services"
|
"server/services"
|
||||||
|
|
||||||
beego "github.com/beego/beego/v2/server/web"
|
beego "github.com/beego/beego/v2/server/web"
|
||||||
|
"github.com/beego/beego/v2/client/orm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendReminderController struct {
|
type BackendReminderController struct {
|
||||||
@@ -90,12 +91,16 @@ func (c *BackendReminderController) GetReminderList() {
|
|||||||
pageSize = 20
|
pageSize = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
qs := models.Orm.QueryTable(new(models.BackendSchedule)).
|
qs := models.Orm.QueryTable(new(models.BackendSchedule))
|
||||||
Filter("tid", claims.TenantId)
|
cond := orm.NewCondition().And("tid", claims.TenantId)
|
||||||
|
|
||||||
if keyword != "" {
|
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()
|
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{}{
|
item := map[string]interface{}{
|
||||||
"id": s.ID,
|
"id": s.ID,
|
||||||
"title": s.Title,
|
"title": s.Title,
|
||||||
"content": s.Content,
|
"content": displayContent,
|
||||||
"schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"),
|
"schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"),
|
||||||
"remind_channels": channels,
|
"remind_channels": channels,
|
||||||
"user_id": s.UserID,
|
"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{}{
|
data := map[string]interface{}{
|
||||||
"id": schedule.ID,
|
"id": schedule.ID,
|
||||||
"title": schedule.Title,
|
"title": schedule.Title,
|
||||||
"content": schedule.Content,
|
"content": detailContent,
|
||||||
"schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"),
|
"schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"),
|
||||||
"remind_channels": channels,
|
"remind_channels": channels,
|
||||||
"receiver_targets": targets,
|
"receiver_targets": targets,
|
||||||
|
|||||||
@@ -555,6 +555,59 @@ func (c *PlatformReminderController) BatchDeleteReminder() {
|
|||||||
c.ok(nil)
|
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 {
|
type reminderTestPayload struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ func Register() {
|
|||||||
beego.Router("/platform/reminder/:id", &controllers.PlatformReminderController{}, "get:GetReminderDetail;put:UpdateReminder;delete:DeleteReminder")
|
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", &controllers.PlatformReminderController{}, "post:CreateReminder")
|
||||||
beego.Router("/platform/reminder/batchDelete", &controllers.PlatformReminderController{}, "post:BatchDeleteReminder")
|
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")
|
beego.Router("/platform/template/index", &controllers.PlatformTemplateController{}, "get:Index")
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
exit status 0xffffffff
|
|
||||||
Reference in New Issue
Block a user