增加通知公告功能

This commit is contained in:
2026-09-08 23:22:49 +08:00
parent beaeb4a234
commit 672c22d9b6
12 changed files with 1416 additions and 6 deletions
+209 -1
View File
@@ -93,8 +93,42 @@
</div>
</div>
<!-- 右侧 40%:工作日程提醒 -->
<!-- 右侧 30%:通知公告 + 工作日程提醒 -->
<div class="col-right">
<div class="chart-card notice-card">
<div class="chart-title notice-title">
<span>通知公告</span>
<el-button link type="primary" @click="goNotice"
>查看全部</el-button
>
</div>
<div v-loading="noticeLoading" class="notice-list">
<div
v-for="item in notices"
:key="item.id"
class="notice-item"
@click="openNoticeDetail(item)"
>
<el-tag
:type="noticeTypeTag(item.notice_type)"
size="small"
effect="light"
class="notice-type"
>
{{ noticeTypeLabel(item.notice_type) }}
</el-tag>
<span v-if="item.is_top === 1" class="notice-top">置顶</span>
<span class="notice-text">{{ item.title }}</span>
<span class="notice-time">{{ noticeTime(item) }}</span>
</div>
<el-empty
v-if="!notices.length && !noticeLoading"
description="暂无通知公告"
:image-size="60"
/>
</div>
</div>
<div class="chart-card schedule-card">
<div class="chart-title schedule-title">
<span>工作日程提醒</span>
@@ -179,6 +213,35 @@
</div>
</div>
<!-- 通知公告详情 -->
<el-dialog
v-model="noticeDetailVisible"
:title="noticeDetailItem?.title"
width="620px"
>
<div v-if="noticeDetailItem" class="notice-detail-body">
<div class="notice-detail-meta">
<el-tag
:type="noticeTypeTag(noticeDetailItem.notice_type)"
size="small"
effect="light"
>
{{ noticeTypeLabel(noticeDetailItem.notice_type) }}
</el-tag>
<span class="notice-detail-text"
>{{ noticeDetailItem.publisher_name }} ·
{{ formatDateTime(noticeDetailItem.publish_time) }}</span
>
<span class="notice-detail-text"
>阅读 {{ noticeDetailItem.read_count || 0 }}</span
>
</div>
<div class="notice-detail-content">
{{ noticeDetailItem.content || "暂无内容" }}
</div>
</div>
</el-dialog>
<!-- 日程详情抽屉:点击任务后由用户明确选择延期 / 已完成 / 反审核 -->
<ScheduleDetail
v-model="scheduleDetailVisible"
@@ -205,6 +268,10 @@ import { ElMessage } from "element-plus";
import * as echarts from "echarts";
import { Refresh } from "@element-plus/icons-vue";
import { getReimbursementDashboard } from "@/api/reimburse";
import {
getNoticeDetail,
getNoticePortal
} from "@/api/oaNotice";
import { finishSchedule, getScheduleList } from "@/api/oaSchedule";
import ScheduleDetail from "../schedule/components/detail.vue";
import SchedulePostpone from "../schedule/components/postponeDialog.vue";
@@ -459,6 +526,62 @@ const editSchedule = (item) => {
router.push("/apps/oa/schedule");
};
// ---------- 通知公告 ----------
const notices = ref([]);
const noticeLoading = ref(false);
const noticeDetailVisible = ref(false);
const noticeDetailItem = ref(null);
const noticeTypeLabel = (type) => ({ 1: "公告", 2: "活动" }[type] || "通知");
const noticeTypeTag = (type) => ({ 1: "primary", 2: "success" }[type] || "info");
const formatDateTime = (value) => {
if (!value) return "-";
const d = new Date(value);
if (Number.isNaN(d.getTime())) return String(value);
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(
d.getHours()
)}:${pad2(d.getMinutes())}`;
};
// 列表右侧时间:今天显示 HH:mm,本年显示 M/D,跨年显示 YYYY-M-D
const noticeTime = (item) => {
const d = item.publish_time ? new Date(item.publish_time) : null;
if (!d || Number.isNaN(d.getTime())) return "";
const now = new Date();
if (fmtDate(d) === fmtDate(now)) {
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
}
if (d.getFullYear() === now.getFullYear()) {
return `${d.getMonth() + 1}/${d.getDate()}`;
}
return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`;
};
const loadNotices = async () => {
noticeLoading.value = true;
try {
const res = await getNoticePortal({ limit: 6 });
notices.value = res?.data?.list || [];
} catch (error) {
console.warn("加载通知公告失败:", error?.message);
} finally {
noticeLoading.value = false;
}
};
const openNoticeDetail = async (item) => {
const res = await getNoticeDetail(item.id);
if (res?.code === 200) {
noticeDetailItem.value = res.data;
noticeDetailVisible.value = true;
} else {
ElMessage.error(res?.msg || "加载公告失败");
}
};
const goNotice = () => router.push("/apps/oa/notice");
const loadDashboard = async () => {
try {
const data = responseData(await getReimbursementDashboard());
@@ -473,6 +596,7 @@ const loadDashboard = async () => {
}
renderTrendChart();
loadScheduleReminders();
loadNotices();
};
const handleResize = () => trendChart?.resize();
@@ -629,6 +753,90 @@ onBeforeUnmount(() => {
margin-bottom: 0;
}
.notice-card {
margin-bottom: 16px;
.notice-title {
display: flex;
align-items: center;
justify-content: space-between;
}
}
.notice-list {
display: flex;
flex-direction: column;
gap: 6px;
min-height: 80px;
max-height: 240px;
overflow-y: auto;
}
.notice-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
background: #fafbfc;
&:hover {
background: #eef2f7;
}
.notice-type {
flex-shrink: 0;
}
.notice-top {
flex-shrink: 0;
font-size: 11px;
color: #f56c6c;
}
.notice-text {
flex: 1;
min-width: 0;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-time {
flex-shrink: 0;
color: #c0c4cc;
font-size: 12px;
}
}
.notice-detail-meta {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding-bottom: 12px;
border-bottom: 1px solid #ebeef5;
}
.notice-detail-text {
font-size: 12px;
color: #909399;
}
.notice-detail-content {
margin-top: 12px;
font-size: 14px;
line-height: 1.8;
color: #303133;
white-space: pre-wrap;
word-break: break-word;
max-height: 420px;
overflow-y: auto;
}
.schedule-card {
display: flex;
flex-direction: column;