|
|
|
|
@@ -268,6 +268,7 @@ import {
|
|
|
|
|
getComment,
|
|
|
|
|
addReplyComment,
|
|
|
|
|
deleteComment,
|
|
|
|
|
getEmployeeByKeyword
|
|
|
|
|
} from "@/api/modules/Comment";
|
|
|
|
|
const { formatTime } = useRelativeTime();
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
@@ -275,7 +276,7 @@ const { t } = useI18n();
|
|
|
|
|
// 当前用户信息
|
|
|
|
|
const currentUser = computed(() => {
|
|
|
|
|
return {
|
|
|
|
|
name: userStore.userInfo.username,
|
|
|
|
|
name: userStore.userInfo.nickname,
|
|
|
|
|
avatar: userStore.userInfo.avatar,
|
|
|
|
|
id: 1,
|
|
|
|
|
};
|
|
|
|
|
@@ -299,6 +300,8 @@ const {
|
|
|
|
|
|
|
|
|
|
// 评论业务逻辑
|
|
|
|
|
const activeReply = reactive({
|
|
|
|
|
id:"",
|
|
|
|
|
rootId:"",
|
|
|
|
|
replyUserId: "",
|
|
|
|
|
parentId: null,
|
|
|
|
|
targetName: "",
|
|
|
|
|
@@ -312,23 +315,61 @@ const commentData = ref([]);
|
|
|
|
|
// 滚动加载
|
|
|
|
|
const infinityLoading = ref(false);
|
|
|
|
|
const loadMoreAnchor = ref(null);
|
|
|
|
|
const isProcessing = ref(false);
|
|
|
|
|
const noMore = ref(false);
|
|
|
|
|
// FIXME:请求用户列表的接口函数
|
|
|
|
|
// 请求用户列表的接口函数
|
|
|
|
|
const handleFetchSearch = async (keyword, signal) => {
|
|
|
|
|
console.log("获取参数信息", keyword, signal);
|
|
|
|
|
const selectedIds = new Set();
|
|
|
|
|
selectedUsersCache.forEach(userList => {
|
|
|
|
|
userList.forEach(u => selectedIds.add(u.id));
|
|
|
|
|
});
|
|
|
|
|
// FIXME:模拟接口返回的数据人员信息
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
|
|
return [
|
|
|
|
|
{ id: 1, name: "李星倩" },
|
|
|
|
|
{ id: 2, name: "冯娜" },
|
|
|
|
|
{ id: 4, name: "张三1" },
|
|
|
|
|
{ id: 5, name: "张三2" },
|
|
|
|
|
{ id: 6, name: "张三3" },
|
|
|
|
|
{ id: 7, name: "张三4" },
|
|
|
|
|
{ id: 8, name: "张三5" },
|
|
|
|
|
{ id: 9, name: "张三6" },
|
|
|
|
|
{ id: 10, name: "张三7" },
|
|
|
|
|
{ id: 11, name: "张三8" },
|
|
|
|
|
];
|
|
|
|
|
const allEmployees = [
|
|
|
|
|
{
|
|
|
|
|
id:1,
|
|
|
|
|
name:"张三",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id:2,
|
|
|
|
|
name:"李四",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id:3,
|
|
|
|
|
name:"王五",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id:4,
|
|
|
|
|
name:"赵六",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id:5,
|
|
|
|
|
name:"冯七 ",
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
return allEmployees.filter(user => {
|
|
|
|
|
const isMatch = user.name.toLowerCase().includes(keyword.toLowerCase());
|
|
|
|
|
const notSelected = !selectedIds.has(user.id);
|
|
|
|
|
return isMatch && notSelected;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 正式请求
|
|
|
|
|
const queryParams = {
|
|
|
|
|
keyword,
|
|
|
|
|
kind:0,
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
};
|
|
|
|
|
try {
|
|
|
|
|
const allEmployees = await getEmployeeByKeyword(queryParams);
|
|
|
|
|
return allEmployees.filter(user => {
|
|
|
|
|
const isMatch = user.name.toLowerCase().includes(keyword.toLowerCase());
|
|
|
|
|
const notSelected = !selectedIds.has(user.id);
|
|
|
|
|
return isMatch && notSelected;
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('fetch user error:',error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 获取当前的的评论信息
|
|
|
|
|
@@ -373,7 +414,7 @@ const onUserSelect = (user: any) => {
|
|
|
|
|
// 回复
|
|
|
|
|
const openReply = (target, group) => {
|
|
|
|
|
// 1. 设置回复的目标关系
|
|
|
|
|
activeReply.groupId = target.rootId; // 根评论ID
|
|
|
|
|
activeReply.rootId = target.rootId; // 根评论ID
|
|
|
|
|
activeReply.replyUserId = target.employee.userId; //回复-人的id
|
|
|
|
|
activeReply.parentId = target.parentId; // 直接父级ID
|
|
|
|
|
activeReply.targetName = target.employee.username; //回复人员的username
|
|
|
|
|
@@ -393,7 +434,6 @@ const deleteReply = async (target, group) => {
|
|
|
|
|
try {
|
|
|
|
|
// 删除成功
|
|
|
|
|
await deleteComment(target.id);
|
|
|
|
|
ElMessage.success("删除成功");
|
|
|
|
|
const index = group.children.findIndex((item) => item.id === target.id);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
group.children.splice(index, 1);
|
|
|
|
|
@@ -414,12 +454,11 @@ const deleteReply = async (target, group) => {
|
|
|
|
|
const deleteMainComment = async (target) => {
|
|
|
|
|
try {
|
|
|
|
|
await deleteComment(target.id);
|
|
|
|
|
ElMessage.success("删除成功");
|
|
|
|
|
// 前端动态操作
|
|
|
|
|
const index = commentData.value.findIndex((item) => item.id === target.id);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
commentData.value.splice(index, 1);
|
|
|
|
|
if (activeReply.groupId === target.id) {
|
|
|
|
|
if (activeReply.rootId === target.id) {
|
|
|
|
|
cancelReply();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -433,11 +472,14 @@ const onSelectEmoji = (emoji) => {
|
|
|
|
|
mainInput.value += emoji;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 清除回复信息的操作
|
|
|
|
|
// 清除回复信息的人员数据
|
|
|
|
|
const resetReplyStatus = () => {
|
|
|
|
|
activeReply.parentId = null;
|
|
|
|
|
activeReply.targetName = "";
|
|
|
|
|
activeReply.groupId = null;
|
|
|
|
|
activeReply.rootId = "";
|
|
|
|
|
activeReply.replyUserId = "";
|
|
|
|
|
activeReply.id = "";
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 取消评论
|
|
|
|
|
@@ -451,7 +493,8 @@ const cancelReply = () => {
|
|
|
|
|
*/
|
|
|
|
|
const handleSendComment = async () => {
|
|
|
|
|
let rawText = mainInput.value;
|
|
|
|
|
if (!rawText.trim()) return ElMessage.warning("内容不能为空");
|
|
|
|
|
if (!rawText.trim() || isProcessing.value) return;
|
|
|
|
|
isProcessing.value = true;
|
|
|
|
|
|
|
|
|
|
let finalReplyId = null;
|
|
|
|
|
const expectedPrefix = `@${activeReply.targetName} `;
|
|
|
|
|
@@ -484,7 +527,7 @@ const handleSendComment = async () => {
|
|
|
|
|
`匹配到用户: ${currentUser?.name}, ID: ${currentUser?.id}, 位置: ${match.index}`
|
|
|
|
|
);
|
|
|
|
|
mentionList.push({
|
|
|
|
|
id: currentUser.id,
|
|
|
|
|
userId: currentUser.id,
|
|
|
|
|
name: currentUser.name,
|
|
|
|
|
start: match.index,
|
|
|
|
|
end: match.index + match[0].length,
|
|
|
|
|
@@ -495,12 +538,12 @@ const handleSendComment = async () => {
|
|
|
|
|
// 组装接口请求的参数
|
|
|
|
|
const params = {
|
|
|
|
|
content: rawText,
|
|
|
|
|
mentions: Object.keys(mentionList).length ? mentionList : null,
|
|
|
|
|
mentions: mentionList.length ? mentionList : null,
|
|
|
|
|
...props.queryParams,
|
|
|
|
|
};
|
|
|
|
|
// 回复数据复制
|
|
|
|
|
if(type === "reply"){
|
|
|
|
|
params.rootId = activeReply.groupId ? activeReply.groupId : activeReply.id;
|
|
|
|
|
params.rootId = activeReply.rootId ? activeReply.rootId : activeReply.id;
|
|
|
|
|
params.parentId = activeReply.parentId;
|
|
|
|
|
params.replyUserId = activeReply.replyUserId;
|
|
|
|
|
}
|
|
|
|
|
@@ -513,7 +556,7 @@ const handleSendComment = async () => {
|
|
|
|
|
// 清空输入框
|
|
|
|
|
cancelReply();
|
|
|
|
|
clearSelection();
|
|
|
|
|
ElMessage.success(type === "reply" ? "回复成功" : "评论成功");
|
|
|
|
|
// ElMessage.success(type === "reply" ? "回复成功" : "评论成功"); //屏蔽当前的提示
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log("error", error);
|
|
|
|
|
}
|
|
|
|
|
@@ -532,9 +575,9 @@ const updateUIAfterSend = (type, params, response) => {
|
|
|
|
|
userId: activeReply.replyUserId,
|
|
|
|
|
username: activeReply.targetName,
|
|
|
|
|
},
|
|
|
|
|
rootId: params.groupId,
|
|
|
|
|
rootId: params.rootId,
|
|
|
|
|
content: params.content,
|
|
|
|
|
mentions: params.mentionList,
|
|
|
|
|
mentions: params.mentions,
|
|
|
|
|
createTime: new Date().valueOf(),
|
|
|
|
|
children: [],
|
|
|
|
|
};
|
|
|
|
|
@@ -544,33 +587,47 @@ const updateUIAfterSend = (type, params, response) => {
|
|
|
|
|
commentData.value.unshift(newComment);
|
|
|
|
|
} else {
|
|
|
|
|
//回复某人的数据渲染
|
|
|
|
|
const targetGroup = commentData.value.find(
|
|
|
|
|
(i) => i.id === params.rootId
|
|
|
|
|
); //获取返回的数据信息
|
|
|
|
|
const targetGroup = commentData.value.find((i) => i.id === params.rootId);
|
|
|
|
|
if (targetGroup) {
|
|
|
|
|
if (!targetGroup.children) targetGroup.children = [];
|
|
|
|
|
targetGroup.children.unshift(newComment);
|
|
|
|
|
targetGroup.childrenCount = targetGroup.children.length;
|
|
|
|
|
targetGroup.childrenCount = (targetGroup.childrenCount || 0) + 1;
|
|
|
|
|
if (targetGroup.showAllReplies) {
|
|
|
|
|
if (!targetGroup.children) targetGroup.children = [];
|
|
|
|
|
targetGroup.children.unshift(newComment);
|
|
|
|
|
}else{
|
|
|
|
|
handleExpand(targetGroup);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* 二级评论加载 可能会触发一级加载的问题 */
|
|
|
|
|
// 是否有任何二级回复正在加载
|
|
|
|
|
const isSubTaskRunning = computed(() => {
|
|
|
|
|
return commentData.value.some(item => item.loading);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 综合加载状态:主列表加载中 OR 子任务运行中 OR 发送中
|
|
|
|
|
const isBusy = computed(() => {
|
|
|
|
|
return infinityLoading.value || isSubTaskRunning.value || isProcessing.value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 滚动加载数据
|
|
|
|
|
const setupObserver = () => {
|
|
|
|
|
const observer = new IntersectionObserver(
|
|
|
|
|
(entries) => {
|
|
|
|
|
// 如果探测器进入视口,且当前没在加载,且还有更多数据
|
|
|
|
|
const target = entries[0];
|
|
|
|
|
if (
|
|
|
|
|
entries[0].isIntersecting &&
|
|
|
|
|
!infinityLoading.value &&
|
|
|
|
|
target.isIntersecting &&
|
|
|
|
|
!isBusy.value &&
|
|
|
|
|
!noMore.value
|
|
|
|
|
) {
|
|
|
|
|
loadMainComments();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
threshold: 0.1,
|
|
|
|
|
threshold: 0.1
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
@@ -580,6 +637,7 @@ const setupObserver = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const nextPage = ref(1);
|
|
|
|
|
// TODO:能会存在数据重复的问题
|
|
|
|
|
const loadMainComments = async () => {
|
|
|
|
|
infinityLoading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
@@ -629,13 +687,15 @@ const loadMoreReplies = async (item) => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getCommentData({
|
|
|
|
|
rootId: item.id,
|
|
|
|
|
pageNum: nextPage,
|
|
|
|
|
pageNo: nextPage,
|
|
|
|
|
pageSize: PAGE_SIZE_MORE,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const newRecords = res?.records || [];
|
|
|
|
|
// 将新数据追加到列表末尾
|
|
|
|
|
item.children = [...item.children, ...res];
|
|
|
|
|
item.currentPage = nextPage;
|
|
|
|
|
if(newRecords.length > 0){
|
|
|
|
|
item.children = [...item.children, ...newRecords];
|
|
|
|
|
item.currentPage = nextPage;
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
item.loading = false;
|
|
|
|
|
}
|
|
|
|
|
@@ -644,7 +704,7 @@ const loadMoreReplies = async (item) => {
|
|
|
|
|
const collapseReplies = (item) => {
|
|
|
|
|
item.showAllReplies = false;
|
|
|
|
|
item.children = []; //清空数据
|
|
|
|
|
item.currentPage = 0; //重置页码
|
|
|
|
|
item.currentPage = 1; //重置页码
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|