diff --git a/components.d.ts b/components.d.ts index fb241d1..2041530 100644 --- a/components.d.ts +++ b/components.d.ts @@ -42,6 +42,7 @@ declare module 'vue' { ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElMain: typeof import('element-plus/es')['ElMain'] + ElMention: typeof import('element-plus/es')['ElMention'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] diff --git a/src/api/modules/Comment/index.ts b/src/api/modules/Comment/index.ts new file mode 100644 index 0000000..746d3a6 --- /dev/null +++ b/src/api/modules/Comment/index.ts @@ -0,0 +1,36 @@ +import request from '@/request'; + + +type commentProps = { + moduleId:string; + nodeId:string; + instanceId:string; + commentId:string; + pageNo:number; + pageSize:number; +} + +interface addCommentProps { + moduleId:string; + nodeId:string; + instanceId:string; + content:string; + mentions:Record[] + [key:string]:any; +} + + +// 获取评论 +export const getComment = (params: commentProps) => { + return request.get('/comment/getComment', { params }); +} + +// 添加评论-回复评论 +export const addReplyComment = (data: addCommentProps) => { + return request.post('/comment/addComment', data) +}; + +// 删除评论 +export const deleteComment = (id: string) => { + return request.delete(`/comment/deleteComment/${id}`); +} \ No newline at end of file diff --git a/src/api/stage/organization/index.ts b/src/api/stage/organization/index.ts new file mode 100644 index 0000000..7b816f0 --- /dev/null +++ b/src/api/stage/organization/index.ts @@ -0,0 +1,58 @@ +import request from '@/request'; + +type paramsProps = { + name:string; + domain:string; +} + + +type wxWorkProps = { + corpId:string; + agentId:number; + secret:string; + token:string; + encodingAesKey:string; +} +interface addDataProps{ + name:string; + wxWork:wxWorkProps; +} + + +// 查询企业 +export const getEnterprise = (params: paramsProps) =>{ + return request.get('/auth/v1/backend/enterprise', params); +} +// 添加企业 +export const addEnterprise = (data: addDataProps) => { + return request.post('/auth/v1/backend/enterprise', data); +} + +// 启用企业 +export const enableEnterprise = (id:string) => { + return request.post(`/auth/v1/backend/enterprise/${id}/enable`); +} +// 禁用企业 +export const disableEnterprise = (id:string) => { + return request.post(`/auth/v1/backend/enterprise/${id}/disable`); +} + +// 加载企业机构 +export const getEnterpriseOrg = (parentId:number) => { + return request.get(`/auth/v1/backend/enterprise/institution/${parentId}`); +} + +// 加载员工详情 +export const getEnterpriseUser = (employeeId:number) => { + return request.get(`/auth/v1/backend/enterprise/employee/${employeeId}`); +} + +// 企业详情 +export const getEnterpriseDetail = () => { + return request.get(`/auth/v1/backend/enterprise/detail`); +} + +// 加载部门详情 +export const getEnterpriseOrgDetail = (departmentId:string) => { + return request.get(`/auth/v1/backend/enterprise/department/${departmentId}`); +} \ No newline at end of file diff --git a/src/modules/Comment/index.scss b/src/modules/Comment/index.scss index 461a559..87de5fa 100644 --- a/src/modules/Comment/index.scss +++ b/src/modules/Comment/index.scss @@ -11,8 +11,7 @@ $color-white: #fff; flex-direction: column; overflow: hidden; position: relative; - - height: calc(100vh - 170px); + height: 100%; // 评论的样式 .main-publisher{ diff --git a/src/modules/Comment/index.vue b/src/modules/Comment/index.vue index c85ddcf..de3b9a7 100644 --- a/src/modules/Comment/index.vue +++ b/src/modules/Comment/index.vue @@ -2,7 +2,7 @@
- +
@@ -182,29 +182,49 @@
- -
- -
- - - + +
@@ -222,7 +242,7 @@ class="send-btn" type="primary" link - @click="handleSendComment('main')" + @click="handleSendComment" > - - - \ No newline at end of file diff --git a/src/modules/Comment/useUserSearch.ts b/src/modules/Comment/useUserSearch.ts new file mode 100644 index 0000000..06539b1 --- /dev/null +++ b/src/modules/Comment/useUserSearch.ts @@ -0,0 +1,98 @@ +import { ref, shallowRef } from "vue"; +import { debounce } from "lodash-es"; + +interface SearchOptions { + debounceMs?: number; +} + +export function useUserSearch( + apiFn: (keyword: string, signal: AbortSignal) => Promise, + options: SearchOptions = {} +) { + const { debounceMs = 300 } = options; + + const filteredUsers = ref([]); + const selectedUsersCache = new Map(); + const searching = ref(false); + + // 使用 shallowRef 存储 Controller,因为它不需要深度响应 + const abortController = shallowRef(null); + + // 存储用户选中的缓存 + const recordSelection = (user: any) => { + if (user && user.name) { + // 依然按名存数组,支持同名 + const list = selectedUsersCache.get(user.name) || []; + if (!list.find((u) => u.id === user.id)) { + list.push(user); + selectedUsersCache.set(user.name, [...list]); + } + } + }; + + // 获取用户已选中的用户组 + const getSelectedUsers = () => Array.from(selectedUsersCache.values()).flat(); + + // 清除用户选中的缓存 + const clearSelection = () => { + selectedUsersCache.clear(); + }; + + // 从缓存池中查找对应的数据 + const getCacheByName = (name) => { + const list = selectedUsersCache.get(name); + return list ? [...list] : []; + }; + + /** + * 真正的请求逻辑 + */ + const performSearch = async (keyword: string) => { + // 1. 中断之前的请求 + if (abortController.value) { + abortController.value.abort(); + } + + // 2. 创建新的控制实例 + abortController.value = new AbortController(); + + try { + // 3. 执行外部传入的 API + const res = await apiFn(keyword, abortController.value.signal); + filteredUsers.value = res; + } catch (err: any) { + // 仅处理非取消类的错误 + if (err.name !== "AbortError" && err.message !== "canceled") { + console.error("Search API Error:", err); + filteredUsers.value = []; + } + } finally { + // 4. 如果当前请求没有被中断,则关闭 loading + if (!abortController.value?.signal.aborted) { + searching.value = false; + } + } + }; + + // 5. 创建防抖函数 + const debouncedSearch = debounce(performSearch, debounceMs); + + /** + * 暴露给外部调用的入口 + */ + const search = (keyword: string) => { + searching.value = true; + debouncedSearch(keyword); + }; + + return { + filteredUsers, + searching, + selectedUsersCache, + recordSelection, + clearSelection, + getSelectedUsers, + getCacheByName, + search, + }; +} diff --git a/src/modules/Comment/utils.ts b/src/modules/Comment/utils.ts new file mode 100644 index 0000000..b6e29c7 --- /dev/null +++ b/src/modules/Comment/utils.ts @@ -0,0 +1,31 @@ +/** + * @description + * @param {string} text 原始文本 + * @param {string[]} atUsers 这条评论真正圈到的人员列表 + */ +type atUserProps = { + id:string|number; + name?:string; +} +export const parseMention = (text: string, atUsers: atUserProps[]) => { + if (!text) return ""; + if (!atUsers || atUsers.length === 0) { + return text.replace(//g, ">").replace(/\n/g, '
'); + } + // 只循环这条评论里【真正圈到】的人 + const sortedMentions = [...atUsers].sort((a, b) => b.start - a.start); + let result = text; + sortedMentions.forEach((m) => { + const prefix = result.slice(0, m.start); + const suffix = result.slice(m.end); + const rawMentionText = result.slice(m.start, m.end); + const cleanName = rawMentionText.trim().replace(/^@+/, ""); + const displayName = `@${cleanName}`; + const safeDisplayName = displayName.replace(//g, ">"); + const highlight = `${safeDisplayName}`; + + result = prefix + highlight + suffix; + }); + + return result.replace(/\n/g, '
'); +}; diff --git a/src/pages/stage/personnel/index.vue b/src/pages/stage/personnel/index.vue index e127293..ceafe7e 100644 --- a/src/pages/stage/personnel/index.vue +++ b/src/pages/stage/personnel/index.vue @@ -9,13 +9,9 @@ destroy-on-close class="standard-ui-back-drawer" > -
+
-