diff --git a/src/api/notice.ts b/src/api/notice.ts new file mode 100644 index 00000000..efa94850 --- /dev/null +++ b/src/api/notice.ts @@ -0,0 +1,209 @@ +import request from "@/utils/request"; + +const NOTICE_BASE_URL = "/api/v1/notices"; + +class NoticeAPI { + /** 获取通知公告分页数据 */ + static getPage(queryParams?: NoticePageQuery) { + return request>({ + url: `${NOTICE_BASE_URL}/page`, + method: "get", + params: queryParams, + }); + } + + /** + * 获取通知公告表单数据 + * + * @param id NoticeID + * @returns Notice表单数据 + */ + static getFormData(id: number) { + return request({ + url: `${NOTICE_BASE_URL}/${id}/form`, + method: "get", + }); + } + + /** + * 添加通知公告 + * @param data Notice表单数据 + * @returns + */ + static add(data: NoticeForm) { + return request({ + url: `${NOTICE_BASE_URL}`, + method: "post", + data: data, + }); + } + + /** + * 更新通知公告 + * + * @param id NoticeID + * @param data Notice表单数据 + */ + static update(id: number, data: NoticeForm) { + return request({ + url: `${NOTICE_BASE_URL}/${id}`, + method: "put", + data: data, + }); + } + + /** + * 批量删除通知公告,多个以英文逗号(,)分割 + * + * @param ids 通知公告ID字符串,多个以英文逗号(,)分割 + */ + static deleteByIds(ids: string) { + return request({ + url: `${NOTICE_BASE_URL}/${ids}`, + method: "delete", + }); + } + + /** + * 发布通知 + * + * @param id 被发布的通知公告id + * @returns + */ + static releaseNotice(id: number) { + return request({ + url: `${NOTICE_BASE_URL}/release/${id}`, + method: "patch", + }); + } + + /** + * 撤回通知 + * + * @param id 撤回的通知id + * @returns + */ + static recallNotice(id: number): Promise<[]> { + return request({ + url: `${NOTICE_BASE_URL}/recall/${id}`, + method: "patch", + }); + } + + /** + * 获取未读消息 + * @returns 消息 + */ + static listUnreadNotice() { + return request({ + url: `${NOTICE_BASE_URL}/unread`, + method: "get", + }); + } + + /** + * 查看通知 + * @param id + */ + static readNotice(id: number): Promise { + return request({ + url: `${NOTICE_BASE_URL}/read/${id}`, + method: "PATCH", + }); + } + + /** + * 查看通知详情 + * @param id + */ + static getDetail(id: number): Promise { + return request({ + url: `${NOTICE_BASE_URL}/detail/${id}`, + method: "get", + }); + } + + /** + * 全部已读 + */ + static readAllNotice() { + return request({ + url: `${NOTICE_BASE_URL}/readAll`, + method: "PATCH", + }); + } + + static getMyNoticePage(queryParams?: NoticePageQuery) { + return request>({ + url: `${NOTICE_BASE_URL}/my/page`, + method: "get", + params: queryParams, + }); + } +} + +export default NoticeAPI; + +/** 通知公告分页查询参数 */ +export interface NoticePageQuery extends PageQuery { + /** 标题 */ + title?: string; + /** 发布状态(0-未发布 1已发布 2已撤回) */ + sendStatus?: number; +} + +/** 通知公告表单对象 */ +export interface NoticeForm { + id?: number; + /** 通知标题 */ + title?: string; + /** 通知内容 */ + content?: string; + /** 通知类型 */ + noticeType?: number; + /** 优先级(0-低 1-中 2-高) */ + priority?: number; + /** 目标类型(0-全体 1-指定) */ + tarType?: number; + /** 目标ID合集,以,分割 */ + tarIds?: string; +} + +/** 通知公告分页对象 */ +export interface NoticePageVO { + id?: string; + /** 通知标题 */ + title?: string; + /** 通知内容 */ + content?: string; + /** 通知类型 */ + noticeType?: number; + /** 发布人 */ + releaseBy?: bigint; + /** 优先级(0-低 1-中 2-高) */ + priority?: number; + /** 目标类型(0-全体 1-指定) */ + tarType?: number; + /** 发布状态(0-未发布 1已发布 2已撤回) */ + releaseStatus?: number; + /** 发布时间 */ + releaseTime?: Date; + /** 撤回时间 */ + recallTime?: Date; +} + +export interface NoticeDetailVO { + id?: string; + /** 通知标题 */ + title?: string; + /** 通知内容 */ + content?: string; + /** 通知类型 */ + noticeType?: number; + /** 发布人 */ + releaseBy?: string; + /** 优先级(0-低 1-中 2-高) */ + priority?: number; + /** 发布时间 */ + releaseTime?: Date; +} diff --git a/src/api/socket.ts b/src/api/socket.ts new file mode 100644 index 00000000..37038076 --- /dev/null +++ b/src/api/socket.ts @@ -0,0 +1,113 @@ +import { Client } from "@stomp/stompjs"; +import { TOKEN_KEY } from "@/enums/CacheEnum"; + +const MAX_RETRIES = 3; // 最大重试次数 +const RETRY_DELAY_MS = 5000; // 重试延迟时间,单位:毫秒 +const HEARTBEAT_INTERVAL = 30000; // 心跳间隔时间,单位:毫秒 + +class Socket { + private clients: Map = new Map(); + private retryCountMap: Map = new Map(); + private subscriptions: Map void)[]> = new Map(); + constructor() {} + + public getWebSocketClient( + url: string, + onMessage: (message: string) => void, + onError?: (error: any) => void + ): Client { + if (this.clients.has(url)) { + // 如果连接已存在,添加新的订阅回调 + this.subscriptions.get(url)?.push(onMessage); + return this.clients.get(url)!; + } else { + const client = this.createClient(url, onMessage, onError); + this.clients.set(url, client); + this.subscriptions.set(url, [onMessage]); + return client; + } + } + + /** + * 创建 WebSocket 客户端 + * @param url WebSocket订阅地址 + * @param onMessage 收到消息时的回调 + * @param onError 出现错误时的回调 + * @private + */ + private createClient( + url: string, + onMessage: (message: string) => void, + onError?: (error: any) => void + ): Client { + const token = localStorage.getItem(TOKEN_KEY) || ""; + const client = new Client({ + brokerURL: import.meta.env.VITE_APP_WS_ENDPOINT, + connectHeaders: { + Authorization: token, + }, + heartbeatIncoming: HEARTBEAT_INTERVAL, + heartbeatOutgoing: HEARTBEAT_INTERVAL, + onConnect: () => { + console.log(`Connected to ${url}`); + client.subscribe(url, (message) => { + onMessage(message.body); + }); + }, + onStompError: (frame) => { + console.error(`Error on ${url}: ${frame.headers["message"]}`); + console.error(`Details: ${frame.body}`); + if (onError) { + onError(frame); + } + this.handleReconnect(url); + }, + onDisconnect: () => { + console.log(`连接失败 ${url}`); + this.handleReconnect(url); + }, + }); + + client.activate(); + return client; + } + + /** + * 处理重连 + * @param url WebSocket订阅地址 + */ + private handleReconnect(url: string) { + const retryCount = this.retryCountMap.get(url) || 0; + + if (retryCount < MAX_RETRIES) { + this.retryCountMap.set(url, retryCount + 1); + console.log(`重试连接 ${url} (${retryCount + 1}/${MAX_RETRIES})...`); + setTimeout( + () => + this.getWebSocketClient( + url, + () => {}, + () => {} + ), + RETRY_DELAY_MS + ); + } else { + console.error(`已经达到最大重试次数 ${url}`); + this.retryCountMap.delete(url); + } + } + + /** + * 断开所有 WebSocket 连接 + */ + public disconnectAll() { + this.clients.forEach((client, url) => { + console.log(`断开连接: ${url}`); + client.deactivate(); + }); + this.clients.clear(); + this.retryCountMap.clear(); + } +} + +export default new Socket(); diff --git a/src/api/user.ts b/src/api/user.ts index 47402eb0..e7ac59c3 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,4 +1,5 @@ import request from "@/utils/request"; +import { AxiosPromise, AxiosResponse } from "axios"; const USER_BASE_URL = "/api/v1/users"; @@ -194,6 +195,16 @@ class UserAPI { data: data, }); } + + /** + * 获取用户下拉列表 + */ + static getOptions(): AxiosPromise { + return request({ + url: `${USER_BASE_URL}/options`, + method: "get", + }); + } } export default UserAPI; diff --git a/src/components/Notice/index.vue b/src/components/Notice/index.vue new file mode 100644 index 00000000..f7ecf8d0 --- /dev/null +++ b/src/components/Notice/index.vue @@ -0,0 +1,156 @@ + + diff --git a/src/components/NoticeModal/index.vue b/src/components/NoticeModal/index.vue new file mode 100644 index 00000000..2a958e03 --- /dev/null +++ b/src/components/NoticeModal/index.vue @@ -0,0 +1,110 @@ + + + diff --git a/src/components/WangEditor/index.vue b/src/components/WangEditor/index.vue index bdc4cc8d..bc63be4d 100644 --- a/src/components/WangEditor/index.vue +++ b/src/components/WangEditor/index.vue @@ -30,6 +30,10 @@ const props = defineProps({ type: [String], default: "", }, + excludeKeys: { + type: Array, + default: [], + }, }); const emit = defineEmits(["update:modelValue"]); @@ -37,8 +41,10 @@ const emit = defineEmits(["update:modelValue"]); const modelValue = useVModel(props, "modelValue", emit); const editorRef = shallowRef(); // 编辑器实例,必须用 shallowRef -const mode = ref("default"); // 编辑器模式 -const toolbarConfig = ref({}); // 工具条配置 +const mode = ref("simple"); // 编辑器模式 +const toolbarConfig = ref({ + excludeKeys: props.excludeKeys, +}); // 工具条配置 // 编辑器配置 const editorConfig = ref({ placeholder: "请输入内容...", diff --git a/src/enums/MessageTypeEnum.ts b/src/enums/MessageTypeEnum.ts index 42a6fb94..071f1b26 100644 --- a/src/enums/MessageTypeEnum.ts +++ b/src/enums/MessageTypeEnum.ts @@ -1,7 +1,7 @@ /* 消息类型枚举 */ export const enum MessageTypeEnum { /* 消息 */ - MESSAGE = "MESSAGE", + MESSAGE = "SYSTEM_MESSAGE", /* 通知 */ NOTICE = "NOTICE", /* 待办 */ @@ -10,6 +10,6 @@ export const enum MessageTypeEnum { export const MessageTypeLabels = { [MessageTypeEnum.MESSAGE]: "消息", - [MessageTypeEnum.NOTICE]: "通知", - [MessageTypeEnum.TODO]: "待办", + // [MessageTypeEnum.NOTICE]: "通知", + // [MessageTypeEnum.TODO]: "待办", }; diff --git a/src/layout/components/NavBar/components/NavbarAction.vue b/src/layout/components/NavBar/components/NavbarAction.vue index f1b79e2d..c107c5d9 100644 --- a/src/layout/components/NavBar/components/NavbarAction.vue +++ b/src/layout/components/NavBar/components/NavbarAction.vue @@ -1,7 +1,7 @@