Files
schisandra-cloud-album-front/src/components/CommentReply/src/ReplyReplyInput/ReplyReply.vue
2024-11-22 00:57:38 +08:00

268 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<AFlex :vertical="true" class="reply-input-main-child"
v-if="comment.showReplyInput && child.id === comment.showReplyInput">
<AFlex :vertical="false" align="center" class="reply-input-header-child">
<span class="reply-input-title-child">{{ t('comment.reply') + '' }}</span>
<span class="reply-input-author-child">{{ props.child.nickname }}</span>
<AButton @click="comment.closeReplyInput" type="dashed" size="small"
:icon="h(CloseOutlined )"
class="reply-input-cancel-child">
{{ t('comment.cancelReply') }}
</AButton>
</AFlex>
<AFlex :vertical="false" class="reply-input-content-child">
<!-- 评论输入框 -->
<AFlex :vertical="true" class="reply-input-content-text-child">
<ATextarea :rows="3" class="comment-text-reply-child"
v-model:value="replyReplyContent"
@keyup.ctrl.enter="showSlideCaptcha"
:placeholder="t('comment.placeholder')" allow-clear :showCount="false"/>
<AFlex :vertical="false" align="center" justify="space-between"
class="comment-actions-reply-child"
>
<AFlex :vertical="false" align="center">
<AFlex :vertical="false" align="center" class="comment-action-item-reply-child">
<APopover trigger="click" placement="bottom">
<template #content>
<div style="width: 250px;height: 200px;overflow: auto;">
<AList :grid="{ gutter: 0, column: 4 }" :data-source="comment.emojiList">
<template #renderItem="{ item }">
<AListItem style="display: flex;align-items: center;justify-content: center;">
<AButton @click="insertEmojiToReplyReplyContent(item.name)" type="text" shape="circle"
size="large"
class="comment-emoji-item">
<template #icon>
<img :width="35" :height="35" loading="lazy" :src="item.path"
:alt="item.name"/>
</template>
</AButton>
</AListItem>
</template>
</AList>
</div>
</template>
<AButton type="text" size="small" :icon="h(SmileOutlined)"
class="comment-action-icon-reply-child">
{{ t('comment.emoji') }}
</AButton>
</APopover>
</AFlex>
<AFlex :vertical="false" align="center" class="comment-action-item-reply-child">
<AUpload
:accept="'image/jpg, image/png, image/jpeg'"
name="images"
:max-count="3"
:multiple="true"
method="post"
:directory="false"
:show-upload-list="false"
:custom-request="comment.customUploadRequest"
:before-upload="comment.beforeUpload"
:disabled="comment.imageList.length >= 3 || comment.uploadLoading"
>
<ABadge :count="comment.imageList.length">
<AButton type="text" size="small" :icon="h(PictureOutlined)"
class="comment-action-icon-reply-child" :loading="comment.uploadLoading">
{{ t('comment.picture') }}
</AButton>
</ABadge>
</AUpload>
<template v-if="comment.imageList.length > 0">
<AImagePreviewGroup>
<ABadge style="margin-left: 10px;" v-for="(item, index) in comment.imageList"
:key="index">
<template #count>
<CloseCircleOutlined @click="comment.removeBase64Image(index)"
style="color: #f5222d"/>
</template>
<AAvatar shape="square" size="small">
<template #icon>
<AImage v-if="item" :width="24" :height="24" :src="item"/>
</template>
</AAvatar>
</ABadge>
</AImagePreviewGroup>
</template>
</AFlex>
</AFlex>
<AFlex :vertical="false" align="center">
<AButton
@click="showSlideCaptcha"
:disabled="replyReplyContent.trim().length === 0" type="primary" size="middle"
class="comment-action-btn-reply-child">
{{ t('comment.sendComment') }}
</AButton>
</AFlex>
</AFlex>
</AFlex>
</AFlex>
</AFlex>
<AModal v-model:open="showSubmitCaptcha" :footer="null" :closable="false" width="375" :centered="true"
:maskClosable="false" :bodyStyle="{padding: 0}">
<gocaptcha-slide
:config="{}"
:data="comment.slideCaptchaData"
:events="commentSlideCaptchaEvent"
/>
</AModal>
</template>
<script lang="ts" setup>
import {h, ref} from "vue";
import {CloseOutlined, PictureOutlined, SmileOutlined} from "@ant-design/icons-vue";
import {useI18n} from "vue-i18n";
import useStore from "@/store";
import {message} from "ant-design-vue";
import {replyReplySubmitApi} from "@/api/comment";
import {ReplyCommentParams} from "@/types/comment";
import {useThrottleFn} from "@vueuse/core";
const {t} = useI18n();
const comment = useStore().comment;
const replyReplyContent = ref<string>("");
const user = useStore().user;
const topicId = ref<string>("123");
const showSubmitCaptcha = ref<boolean>(false);
const props = defineProps({
child: {
type: Object,
required: true
},
item: {
type: Object,
required: true
}
});
const commentSlideCaptchaEvent = {
confirm: async (point: any) => {
await replyReplySubmitThrottled(point);
},
close: async () => {
showSubmitCaptcha.value = false;
},
refresh: () => {
getSlideCaptchaDataThrottled();
},
};
/**
* 插入表情到回复内容
* @param emoji
*/
async function insertEmojiToReplyReplyContent(emoji: string) {
replyReplyContent.value += "[" + emoji + "]";
}
/**
* 回复提交 throttled
*/
const replyReplySubmitThrottled = useThrottleFn(replyReplySubmit, 1000);
/**
* 回复评论提交
*/
async function replyReplySubmit(point: any) {
if (replyReplyContent.value.trim() === "") {
return;
}
if (comment.imageList.length > 3) {
message.error(t('comment.maxImageCount'));
return;
}
const content = replyReplyContent.value.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
const regex = /\[((1[0-6][0-6]|[1-9]?[0-9])\.gif)]/g; // 匹配 [1.gif] 的字符串
const contentWithEmoji = content.replace(regex, (_match, p1) => {
return `<img width="30px" height="30px" loading="lazy" src="/emoji/qq/gif/${p1}" alt="emoji ${p1}" />`;
});
const replyParams: ReplyCommentParams = {
topic_id: topicId.value,
content: contentWithEmoji,
images: comment.imageList,
author: user.user.uid,
reply_to: props.child.id,
reply_id: props.item.id,
reply_user: props.child.user_id,
point: [point.x, point.y],
key: comment.slideCaptchaData.key,
};
const result: any = await replyReplySubmitApi(replyParams);
if (result.code === 200) {
const tmpData: any = {
id: result.data.id,
content: result.data.content,
images: comment.imageList,
reply_id: result.data.reply_id,
user_id: result.data.user_id,
author: result.data.author,
created_time: result.data.created_time,
browser: result.data.browser,
operating_system: result.data.operating_system,
location: result.data.location,
likes: result.data.likes,
reply_count: result.data.reply_count,
reply_user: result.data.reply_user,
nickname: user.user.nickname,
avatar: user.user.avatar,
is_liked: false,
reply_username: props.item.nickname,
reply_to: result.data.reply_to,
};
if (!comment.replyList.comments) {
comment.replyList.comments = []; // 初始化 comments 数组
}
comment.replyList.comments.unshift(tmpData);
comment.commentMap[props.item.id].reply_count++;
replyReplyContent.value = "";
await comment.clearFileList();
showSubmitCaptcha.value = false;
// await getReplyList();
comment.closeReplyInput();
message.success(t('comment.replySuccess'));
} else {
await comment.getSlideCaptchaData();
message.error(t('comment.replyError'));
}
}
const getSlideCaptchaDataThrottled = useThrottleFn(comment.getSlideCaptchaData, 1000);
/**
* 显示滑动验证码
*/
async function showSlideCaptcha() {
if (replyReplyContent.value.trim() === "") {
message.error(t('comment.commentContentNotEmpty'));
return;
}
if (comment.imageList.length > 3) {
message.error(t('comment.maxImageCount'));
return;
}
const res = await comment.getSlideCaptchaData();
if (res) {
showSubmitCaptcha.value = true;
}
}
// /**
// * 获取回复列表
// */
// async function getReplyList() {
// const params: any = {
// topic_id: topicId.value,
// page: 1,
// size: 5,
// comment_id: props.item.id,
// user_id: user.user.uid,
// };
// await comment.getReplyList(params);
// }
</script>
<style scoped lang="scss" src="./index.scss">
</style>