优化backend端相关功能
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
<!-- 图表区域 -->
|
||||
<el-row :gutter="16" class="chart-row">
|
||||
<el-col :span="14" :xs="24">
|
||||
<div class="chart-card">
|
||||
<div class="chart-card trend-card">
|
||||
<div class="chart-title">近6个月报销趋势</div>
|
||||
<div ref="trendChartRef" class="chart-container"></div>
|
||||
</div>
|
||||
@@ -42,9 +42,21 @@
|
||||
>查看全部</el-button
|
||||
>
|
||||
</div>
|
||||
<el-tabs v-model="scheduleTab" class="schedule-tabs">
|
||||
<el-tab-pane name="pending">
|
||||
<template #label>
|
||||
待完成 ({{ schedulePendingCount }})
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="done">
|
||||
<template #label>
|
||||
已完成 ({{ scheduleDoneCount }})
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-loading="scheduleLoading" class="schedule-list">
|
||||
<div
|
||||
v-for="item in scheduleReminders"
|
||||
v-for="item in filteredScheduleReminders"
|
||||
:key="item.id"
|
||||
class="schedule-item"
|
||||
:class="{ done: item.status === 1 }"
|
||||
@@ -52,9 +64,9 @@
|
||||
>
|
||||
<el-checkbox
|
||||
:model-value="item.status === 1"
|
||||
@change="toggleScheduleFinish(item)"
|
||||
@change="openScheduleAction(item)"
|
||||
/>
|
||||
<div class="schedule-item-main" @click="toggleScheduleFinish(item)">
|
||||
<div class="schedule-item-main" @click="openScheduleAction(item)">
|
||||
<div
|
||||
class="schedule-item-title"
|
||||
:class="{ done: item.status === 1 }"
|
||||
@@ -81,11 +93,65 @@
|
||||
</div>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!scheduleReminders.length && !scheduleLoading"
|
||||
description="近期暂无日程安排"
|
||||
v-if="!filteredScheduleReminders.length && !scheduleLoading"
|
||||
:description="scheduleTab === 'done' ? '暂无已完成日程' : '近期暂无待完成日程'"
|
||||
:image-size="60"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 日程操作确认弹窗 -->
|
||||
<el-dialog
|
||||
v-model="scheduleActionVisible"
|
||||
title="日程操作"
|
||||
width="420px"
|
||||
align-center
|
||||
>
|
||||
<div v-if="activeSchedule" class="schedule-action-content">
|
||||
<div class="schedule-action-title">{{ activeSchedule.title }}</div>
|
||||
<div class="schedule-action-meta">
|
||||
<span :class="{ overdue: isOverdueSchedule(activeSchedule) }">{{
|
||||
scheduleDateTag(activeSchedule)
|
||||
}}</span>
|
||||
<span>{{ scheduleTimeText(activeSchedule) }}</span>
|
||||
<el-tag
|
||||
v-if="activeSchedule.priority > 0"
|
||||
:type="activeSchedule.priority === 2 ? 'danger' : 'warning'"
|
||||
size="small"
|
||||
effect="light"
|
||||
>
|
||||
{{ activeSchedule.priority === 2 ? "紧急" : "重要" }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="activeSchedule.status === 1 ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ activeSchedule.status === 1 ? "已完成" : "待完成" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="schedule-action-tip">
|
||||
请选择要执行的操作:
|
||||
</div>
|
||||
<div class="schedule-action-btns">
|
||||
<el-button @click="editSchedule(activeSchedule)">
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="activeSchedule.status === 0"
|
||||
type="success"
|
||||
@click="confirmScheduleFinish(activeSchedule)"
|
||||
>
|
||||
标记完成
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="activeSchedule.status === 1"
|
||||
type="warning"
|
||||
@click="confirmScheduleFinish(activeSchedule)"
|
||||
>
|
||||
反审核
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -248,6 +314,18 @@ const responseData = (res) => res?.data?.data ?? res?.data ?? res ?? {};
|
||||
const router = useRouter();
|
||||
const scheduleReminders = ref([]);
|
||||
const scheduleLoading = ref(false);
|
||||
const scheduleTab = ref("pending");
|
||||
|
||||
const schedulePendingCount = computed(
|
||||
() => scheduleReminders.value.filter((r) => r.status === 0).length,
|
||||
);
|
||||
const scheduleDoneCount = computed(
|
||||
() => scheduleReminders.value.filter((r) => r.status === 1).length,
|
||||
);
|
||||
const filteredScheduleReminders = computed(() => {
|
||||
const status = scheduleTab.value === "done" ? 1 : 0;
|
||||
return scheduleReminders.value.filter((r) => r.status === status);
|
||||
});
|
||||
|
||||
const pad2 = (n) => String(n).padStart(2, "0");
|
||||
const fmtDate = (d) =>
|
||||
@@ -335,18 +413,46 @@ const scheduleTimeText = (item) =>
|
||||
const isOverdueSchedule = (item) =>
|
||||
item.status === 0 && item.schedule_date < fmtDate(new Date());
|
||||
|
||||
const toggleScheduleFinish = async (item) => {
|
||||
const res = await finishSchedule(item.id);
|
||||
if (res?.code === 200) {
|
||||
ElMessage.success(res.data?.status === 1 ? "已完成" : "已恢复待办");
|
||||
loadScheduleReminders();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || "操作失败");
|
||||
// 点击日程项:打开操作弹窗,不直接改状态
|
||||
const scheduleActionVisible = ref(false);
|
||||
const activeSchedule = ref(null);
|
||||
const scheduleActioning = ref(false);
|
||||
|
||||
const openScheduleAction = (item) => {
|
||||
activeSchedule.value = item;
|
||||
scheduleActionVisible.value = true;
|
||||
};
|
||||
|
||||
// 在弹窗中确认执行「已完成 / 反审核」操作
|
||||
const confirmScheduleFinish = async (item) => {
|
||||
if (scheduleActioning.value) return;
|
||||
scheduleActioning.value = true;
|
||||
try {
|
||||
const res = await finishSchedule(item.id);
|
||||
if (res?.code === 200) {
|
||||
ElMessage.success(res.data?.status === 1 ? "已完成" : "已恢复待办");
|
||||
scheduleActionVisible.value = false;
|
||||
loadScheduleReminders();
|
||||
} else {
|
||||
ElMessage.error(res?.msg || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "操作失败");
|
||||
} finally {
|
||||
scheduleActioning.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const goSchedule = () => router.push("/apps/oa/schedule");
|
||||
|
||||
// 跳转到日程管理页并打开该日程的编辑抽屉
|
||||
const editSchedule = (item) => {
|
||||
// 通过 sessionStorage 把待编辑日程传递给日程管理页,由其打开编辑抽屉
|
||||
sessionStorage.setItem("schedule_edit_item", JSON.stringify(item));
|
||||
scheduleActionVisible.value = false;
|
||||
router.push("/apps/oa/schedule");
|
||||
};
|
||||
|
||||
const loadDashboard = async () => {
|
||||
try {
|
||||
const data = responseData(await getReimbursementDashboard());
|
||||
@@ -484,12 +590,24 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.trend-card {
|
||||
height: 398px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.type-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.schedule-card {
|
||||
height: 100%;
|
||||
height: 398px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -499,8 +617,27 @@ onBeforeUnmount(() => {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.schedule-tabs {
|
||||
margin-bottom: 20px;
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,6 +701,42 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-action-content {
|
||||
.schedule-action-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.schedule-action-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.overdue {
|
||||
color: #f56c6c;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-action-tip {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.schedule-action-btns {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-dist,
|
||||
.type-dist {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user