初始化项目
@@ -0,0 +1,50 @@
|
||||
package com.jeesite.modules.apps.Module;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class FileList implements Serializable {
|
||||
|
||||
private String filePath;
|
||||
private String fileName;
|
||||
private String fileType;
|
||||
private String fileIcon;
|
||||
private String fileSize;
|
||||
|
||||
public FileList() {
|
||||
}
|
||||
|
||||
public FileList(String filePath, String fileName, String fileType, String fileSize) {
|
||||
this.filePath = filePath;
|
||||
this.fileName = fileName;
|
||||
this.fileType = fileType;
|
||||
this.fileIcon = getIcon(fileType);
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
private String getIcon(String type) {
|
||||
|
||||
switch (type) {
|
||||
case "gz":
|
||||
return "icons/file-gz.png";
|
||||
case "py":
|
||||
return "icons/file-py.png";
|
||||
case "pdf":
|
||||
return "icons/file-pdf.png";
|
||||
case "sql":
|
||||
return "icons/file-sql.png";
|
||||
case "zip":
|
||||
return "icons/file-zip.png";
|
||||
case "ppt", "pptx":
|
||||
return "icons/file-pptx.png";
|
||||
case "xls", "xlsx":
|
||||
return "icons/file-xlsx.png";
|
||||
case "wps", "doc", "docx":
|
||||
return "icons/file-wps.png";
|
||||
default:
|
||||
return "icons/file.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ import java.io.Serial;
|
||||
@Column(name = "user_name", attrName = "userName", label = "接收姓名", isQuery = false),
|
||||
@Column(name = "create_user", attrName = "createUser", label = "创建用户", isQuery = false, isUpdate = false, isUpdateForce = true),
|
||||
@Column(name = "login_user", attrName = "loginUser", label = "接收用户"),
|
||||
}, orderBy = "a.id DESC"
|
||||
}, orderBy = "a.create_time DESC"
|
||||
)
|
||||
@Data
|
||||
public class MyNoticeTodo extends DataEntity<MyNoticeTodo> implements Serializable {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package com.jeesite.modules.biz.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jeesite.common.io.FileUtils;
|
||||
import com.jeesite.modules.apps.Module.FileList;
|
||||
import com.jeesite.modules.apps.Module.TabItem;
|
||||
import com.jeesite.modules.apps.dict.NotifyType;
|
||||
import com.jeesite.modules.file.entity.FileUpload;
|
||||
import com.jeesite.modules.file.utils.FileUploadUtils;
|
||||
import com.jeesite.modules.sys.entity.User;
|
||||
import com.jeesite.modules.sys.utils.UserUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -91,6 +98,9 @@ public class MyNoticeTodoController extends BaseController {
|
||||
@PostMapping(value = "save")
|
||||
@ResponseBody
|
||||
public String save(@Validated MyNoticeTodo myNoticeTodo) {
|
||||
User user = UserUtils.getUser();
|
||||
myNoticeTodo.setAvatar(user.getAvatar());
|
||||
myNoticeTodo.setCreateUser(user.getLoginCode());
|
||||
myNoticeTodoService.save(myNoticeTodo);
|
||||
return renderResult(Global.TRUE, text("保存消息成功!"));
|
||||
}
|
||||
@@ -148,6 +158,34 @@ public class MyNoticeTodoController extends BaseController {
|
||||
return renderResult(Global.TRUE, text("删除消息成功!"));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "listAll")
|
||||
@ResponseBody
|
||||
public List<MyNoticeTodo> listAll(MyNoticeTodo myNoticeTodo) {
|
||||
return myNoticeTodoService.findList(myNoticeTodo);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "fileList")
|
||||
@ResponseBody
|
||||
public List<FileList> fileList(MyNoticeTodo myNoticeTodo) {
|
||||
List<FileList> fileLists = new ArrayList<>();
|
||||
List<FileUpload> fileUploadList = FileUploadUtils.findFileUpload(myNoticeTodo.getId(), "myNoticeTodo_file");
|
||||
for (FileUpload fileUpload : fileUploadList) {
|
||||
fileLists.add(new FileList(fileUpload.getFileUrl(), fileUpload.getFileName(), fileUpload.getFileEntity().getFileExtension(), fileUpload.getFileEntity().getFileSizeFormat()));
|
||||
}
|
||||
return fileLists;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "downloadFile")
|
||||
@ResponseBody
|
||||
public void downloadFile(HttpServletRequest request, HttpServletResponse response, FileList fileList) {
|
||||
try {
|
||||
File file = new File("/ogsapp/files/" + fileList.getFilePath());
|
||||
FileUtils.downFile(file, request, response, fileList.getFileName());
|
||||
} catch (Exception e) {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(value = "tabListData")
|
||||
@ResponseBody
|
||||
|
||||
@@ -43,12 +43,27 @@ export interface TabItem {
|
||||
unreadlist?: MyNoticeTodo[];
|
||||
}
|
||||
|
||||
|
||||
export interface MyFileList {
|
||||
filePath: string;
|
||||
fileName: string;
|
||||
fileType?: number;
|
||||
fileIcon?: string;
|
||||
fileSize?: string;
|
||||
}
|
||||
|
||||
export const tabListDataAll = (params?: MyNoticeTodo | any) =>
|
||||
defHttp.get<TabItem[]>({ url: adminPath + '/biz/myNoticeTodo/tabListData', params});
|
||||
|
||||
export const myNoticeTodoList = (params?: MyNoticeTodo | any) =>
|
||||
defHttp.get<MyNoticeTodo>({ url: adminPath + '/biz/myNoticeTodo/list', params });
|
||||
|
||||
export const myNoticeTodoFileList = (params?: MyNoticeTodo | any) =>
|
||||
defHttp.get<MyFileList[]>({ url: adminPath + '/biz/myNoticeTodo/fileList', params });
|
||||
|
||||
export const myNoticeTodoListAll = (params?: MyNoticeTodo | any) =>
|
||||
defHttp.get<MyNoticeTodo[]>({ url: adminPath + '/biz/myNoticeTodo/listAll', params });
|
||||
|
||||
export const myNoticeTodoListData = (params?: MyNoticeTodo | any) =>
|
||||
defHttp.post<Page<MyNoticeTodo>>({ url: adminPath + '/biz/myNoticeTodo/listData', params });
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
sorter: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
dictType: 'msg_type',
|
||||
},
|
||||
{
|
||||
title: t('是否已读'),
|
||||
@@ -138,6 +139,7 @@
|
||||
sorter: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
dictType: 'read_flag',
|
||||
},
|
||||
{
|
||||
title: t('是否关闭'),
|
||||
@@ -146,6 +148,7 @@
|
||||
sorter: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
dictType: 'click_close',
|
||||
},
|
||||
{
|
||||
title: t('额外信息'),
|
||||
@@ -186,6 +189,7 @@
|
||||
sorter: true,
|
||||
width: 130,
|
||||
align: 'center',
|
||||
dictType: 'notice_status',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -103,13 +103,13 @@ const tableProps: BasicTableProps = {
|
||||
},
|
||||
columns: tableColumns,
|
||||
formConfig: searchForm,
|
||||
rowKey: 'userCode',
|
||||
rowKey: 'loginCode',
|
||||
};
|
||||
|
||||
export default {
|
||||
modalProps,
|
||||
tableProps,
|
||||
itemCode: 'userCode',
|
||||
itemCode: 'loginCode',
|
||||
itemName: 'userName',
|
||||
isShowCode: true,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-for="item in tabs"
|
||||
:key="item.key"
|
||||
:class="['tab-item', { active: activeTab === item.key }]"
|
||||
@click="activeTab = item.key"
|
||||
@click="handleTabChange(item.key)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
@@ -15,52 +15,56 @@
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div ref="tableWrapRef" class="table-container">
|
||||
<el-table :data="currentList" :height="tableHeight" :show-header="true" :border="false">
|
||||
<el-table-column prop="title" label="标题" min-width="120">
|
||||
<el-table :data="listData" :height="tableHeight" :show-header="true" :border="false">
|
||||
<el-table-column prop="title" label="标题" min-width="120" show-overflow-tooltip="true" />
|
||||
<el-table-column prop="datetime" label="到期时间" width="180" />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button class="notice-title-button" link type="primary" @click="openNoticeDialog(row)">
|
||||
{{ row.title }}
|
||||
<el-button class="notice-action-button" link type="primary" @click="openNoticeDialog(row)">
|
||||
查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="time" label="时间" width="180" />
|
||||
<el-table-column prop="content" label="内容" min-width="200" show-overflow-tooltip="true" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogVisible" class="notice-info-dialog" title="通知详情" width="60%" destroy-on-close>
|
||||
<el-dialog v-model="dialogVisible" class="notice-info-dialog" title="通知详情" width="50%" destroy-on-close>
|
||||
<template v-if="selectedNotice">
|
||||
<div class="notice-dialog">
|
||||
<div class="notice-dialog__header">
|
||||
<div class="notice-dialog__title">{{ selectedNotice.title }}</div>
|
||||
<div class="notice-dialog__header-divider"></div>
|
||||
<div class="notice-dialog__time">{{ selectedNotice.time }}</div>
|
||||
<div class="notice-dialog__time">
|
||||
<div class="notice-dialog__time-left">
|
||||
<span>发布时间:{{ selectedNotice.createTime }}</span>
|
||||
<span>发布人:{{ selectedNotice.createUser }}</span>
|
||||
</div>
|
||||
<div class="notice-dialog__time-right">截至时间:{{ selectedNotice.datetime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider class="notice-dialog__divider" />
|
||||
<div class="notice-dialog__content-panel">
|
||||
<div class="notice-dialog__content" v-html="selectedNotice.content"></div>
|
||||
<div class="notice-dialog__content" v-html="selectedNotice.description"></div>
|
||||
</div>
|
||||
<div v-if="selectedNotice.attachments?.length" class="notice-dialog__attachments-panel">
|
||||
<div class="notice-dialog__attachments-title">附件区域</div>
|
||||
<div v-if="fileData?.length" class="notice-dialog__attachments-panel">
|
||||
<div class="notice-dialog__attachments-title">附件区域({{ fileData?.length }})</div>
|
||||
<div class="notice-dialog__attachments-list">
|
||||
<div
|
||||
v-for="attachment in selectedNotice.attachments"
|
||||
:key="attachment"
|
||||
class="notice-dialog__attachment-item"
|
||||
>
|
||||
<div v-for="item in fileData" :key="item" class="notice-dialog__attachment-item">
|
||||
<div class="notice-dialog__attachment-meta">
|
||||
<el-icon class="notice-dialog__attachment-icon"><Document /></el-icon>
|
||||
<span class="notice-dialog__attachment-name">{{ attachment }}</span>
|
||||
<Icon :icon="item.fileIcon" class="icon-img" size="24" />
|
||||
<el-tooltip :content="item.fileName" placement="top" :show-after="200">
|
||||
<span class="notice-dialog__attachment-name">{{ item.fileName }}</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="notice-dialog__attachment-actions">
|
||||
<span class="notice-dialog__attachment-size">{{ getAttachmentSize(attachment) }}</span>
|
||||
<span class="notice-dialog__attachment-size">{{ item.fileSize }}</span>
|
||||
<el-button
|
||||
class="notice-dialog__attachment-download"
|
||||
type="primary"
|
||||
link
|
||||
:icon="Download"
|
||||
@click.stop="handleAttachmentDownload(attachment)"
|
||||
@click.stop="handleDownload(item)"
|
||||
>下载</el-button
|
||||
>
|
||||
</div>
|
||||
@@ -75,15 +79,17 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Icon } from '@jeesite/core/components/Icon';
|
||||
import { Document, Download } from '@element-plus/icons-vue';
|
||||
import { useGlobSetting } from '@jeesite/core/hooks/setting';
|
||||
import { downloadByUrl } from '@jeesite/core/utils/file/download';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
interface NoticeItem {
|
||||
title: string;
|
||||
time: string;
|
||||
content: string;
|
||||
attachments?: string[];
|
||||
}
|
||||
import {
|
||||
MyFileList,
|
||||
myNoticeTodoFileList,
|
||||
MyNoticeTodo,
|
||||
myNoticeTodoListAll,
|
||||
} from '@jeesite/biz/api/biz/myNoticeTodo';
|
||||
|
||||
const tabs = [
|
||||
{ key: '0', label: '未读' },
|
||||
@@ -91,74 +97,28 @@
|
||||
];
|
||||
|
||||
const activeTab = ref('0');
|
||||
const fileData = ref<MyFileList[]>([]);
|
||||
const listData = ref<MyNoticeTodo[]>([]);
|
||||
|
||||
const noticeData: Record<string, NoticeItem[]> = {
|
||||
0: [
|
||||
{
|
||||
title: '系统升级通知',
|
||||
time: '2025-03-25 10:00',
|
||||
content: '<p>系统将于今晚进行升级维护,请提前保存工作内容。</p><p><strong>维护时间:</strong>23:00 - 02:00</p>',
|
||||
attachments: [
|
||||
'升级说明.pdf',
|
||||
'影响范围清单.xlsx',
|
||||
'系统切换流程.docx',
|
||||
'升级回退预案.pdf',
|
||||
'服务影响通知单.xls',
|
||||
'值班安排表.doc',
|
||||
'数据库检查项.txt',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '安全检查提醒',
|
||||
time: '2025-03-25 09:30',
|
||||
content:
|
||||
'<p>请及时完成本周安全检查任务,确保系统运行稳定。</p><ul><li>检查设备运行状态</li><li>确认日志采集是否正常</li></ul>',
|
||||
attachments: ['安全检查模板.docx', '巡检项清单.pdf', '整改说明.docx', '告警记录.xlsx'],
|
||||
},
|
||||
|
||||
{
|
||||
title: '备份任务完成',
|
||||
time: '2025-03-25 08:15',
|
||||
content: '<p>数据库备份任务已完成,备份文件已存储至指定位置。</p>',
|
||||
attachments: [],
|
||||
},
|
||||
],
|
||||
1: [
|
||||
{
|
||||
title: '巡检报告提交',
|
||||
time: '2025-03-24 16:20',
|
||||
content: '<p>昨日巡检报告已提交并审核通过。</p><p>请相关负责人及时查看结果。</p>',
|
||||
attachments: ['巡检报告-0324.pdf'],
|
||||
},
|
||||
{
|
||||
title: '权限变更通知',
|
||||
time: '2025-03-24 14:10',
|
||||
content: '<p>用户权限调整已生效,请相关人员知悉。</p>',
|
||||
attachments: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const currentList = computed(() => noticeData[activeTab.value] || []);
|
||||
const tableWrapRef = ref<HTMLElement>();
|
||||
const tableHeight = ref(0);
|
||||
const dialogVisible = ref(false);
|
||||
const selectedNotice = ref<NoticeItem | null>(null);
|
||||
const selectedNotice = ref<MyNoticeTodo | null>(null);
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
function openNoticeDialog(notice: NoticeItem) {
|
||||
function openNoticeDialog(notice: MyNoticeTodo) {
|
||||
getFileList(notice.id);
|
||||
selectedNotice.value = notice;
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
function handleAttachmentDownload(attachment: string) {
|
||||
ElMessage.info(`待接入下载地址:${attachment}`);
|
||||
}
|
||||
|
||||
function getAttachmentSize(attachment: string) {
|
||||
const seed = attachment.split('').reduce((total, char) => total + char.charCodeAt(0), 0);
|
||||
return `${((seed % 9000) / 100 + 0.8).toFixed(2)} MB`;
|
||||
|
||||
async function handleDownload(item: MyFileList) {
|
||||
const { ctxAdminPath } = useGlobSetting();
|
||||
await downloadByUrl({
|
||||
url: ctxAdminPath + '/biz/myNoticeTodo/downloadFile',
|
||||
params: item,
|
||||
});
|
||||
}
|
||||
|
||||
function updateTableHeight() {
|
||||
@@ -169,7 +129,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
function handleTabChange(tabKey: string) {
|
||||
if (activeTab.value === tabKey) return;
|
||||
activeTab.value = tabKey;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
const reqParams = {
|
||||
type: '1',
|
||||
clickClose: '0',
|
||||
readFlag: activeTab.value,
|
||||
};
|
||||
const result = await myNoticeTodoListAll(reqParams);
|
||||
listData.value = result || [];
|
||||
} catch (error) {
|
||||
listData.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const getFileList = async (bizId: string) => {
|
||||
try {
|
||||
const reqParams = {
|
||||
id: bizId,
|
||||
};
|
||||
const result = await myNoticeTodoFileList(reqParams);
|
||||
fileData.value = result || [];
|
||||
|
||||
console.log(fileData.value);
|
||||
} catch (error) {
|
||||
fileData.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
updateTableHeight();
|
||||
if (tableWrapRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
@@ -296,12 +291,16 @@
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.notice-title-button {
|
||||
.notice-action-button {
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.notice-description {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: rgb(71 85 105);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,8 +351,23 @@
|
||||
}
|
||||
|
||||
&__time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
|
||||
&__time-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__time-right {
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
BIN
web-vue/web/public/resource/img/icons/file-docx.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
web-vue/web/public/resource/img/icons/file-gz.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
web-vue/web/public/resource/img/icons/file-mail.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
web-vue/web/public/resource/img/icons/file-pdf.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
web-vue/web/public/resource/img/icons/file-pptx.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
web-vue/web/public/resource/img/icons/file-py.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
web-vue/web/public/resource/img/icons/file-sql.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web-vue/web/public/resource/img/icons/file-wps.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web-vue/web/public/resource/img/icons/file-xlsx.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web-vue/web/public/resource/img/icons/file-zip.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
web-vue/web/public/resource/img/icons/file.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |