✨ updated comment
This commit is contained in:
14
components.d.ts
vendored
14
components.d.ts
vendored
@@ -9,7 +9,6 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
@@ -20,17 +19,16 @@ declare module 'vue' {
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
@@ -40,40 +38,30 @@ declare module 'vue' {
|
||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||
BoxDog: typeof import('./src/components/BoxDog/BoxDog.vue')['default']
|
||||
Card3D: typeof import('./src/components/Card3D/Card3D.vue')['default']
|
||||
ChromeOutlined: typeof import('@ant-design/icons-vue')['ChromeOutlined']
|
||||
ClockCircleOutlined: typeof import('@ant-design/icons-vue')['ClockCircleOutlined']
|
||||
CloseCircleOutlined: typeof import('@ant-design/icons-vue')['CloseCircleOutlined']
|
||||
Clouds: typeof import('./src/components/Clouds/Clouds.vue')['default']
|
||||
CommentReply: typeof import('./src/components/CommentReply/CommentReply.vue')['default']
|
||||
CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined']
|
||||
DeleteOutlined: typeof import('@ant-design/icons-vue')['DeleteOutlined']
|
||||
DislikeOutlined: typeof import('@ant-design/icons-vue')['DislikeOutlined']
|
||||
DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined']
|
||||
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
|
||||
EffectsCard: typeof import('./src/components/EffectsCard/EffectsCard.vue')['default']
|
||||
EllipsisOutlined: typeof import('@ant-design/icons-vue')['EllipsisOutlined']
|
||||
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
|
||||
GithubOutlined: typeof import('@ant-design/icons-vue')['GithubOutlined']
|
||||
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
|
||||
LikeOutlined: typeof import('@ant-design/icons-vue')['LikeOutlined']
|
||||
LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
|
||||
LoginFooter: typeof import('./src/views/Login/LoginFooter.vue')['default']
|
||||
LoginPage: typeof import('./src/views/Login/LoginPage.vue')['default']
|
||||
MainPage: typeof import('./src/views/Main/MainPage.vue')['default']
|
||||
NotFound: typeof import('./src/views/404/NotFound.vue')['default']
|
||||
PictureOutlined: typeof import('@ant-design/icons-vue')['PictureOutlined']
|
||||
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
|
||||
QqOutlined: typeof import('@ant-design/icons-vue')['QqOutlined']
|
||||
QRLogin: typeof import('./src/views/QRLogin/QRLogin.vue')['default']
|
||||
QRLoginFooter: typeof import('./src/views/QRLogin/QRLoginFooter.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SafetyOutlined: typeof import('@ant-design/icons-vue')['SafetyOutlined']
|
||||
SmileOutlined: typeof import('@ant-design/icons-vue')['SmileOutlined']
|
||||
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
|
||||
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
|
||||
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
|
||||
WechatOutlined: typeof import('@ant-design/icons-vue')['WechatOutlined']
|
||||
WindowsOutlined: typeof import('@ant-design/icons-vue')['WindowsOutlined']
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ export const commentSubmitApi = (params: any) => {
|
||||
author: params.author,
|
||||
},
|
||||
{
|
||||
name: 'comment-submit',
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
},
|
||||
@@ -34,6 +35,7 @@ export const replySubmitApi = (params: any) => {
|
||||
author: params.author,
|
||||
},
|
||||
{
|
||||
name: 'reply-submit',
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
},
|
||||
@@ -52,7 +54,59 @@ export const commentListApi = (params: any) => {
|
||||
topic_id: params.topic_id,
|
||||
},
|
||||
{
|
||||
cacheFor: 1000 * 60 * 60 * 24 * 7, // 7天缓存
|
||||
cacheFor: {
|
||||
expire: 60 * 60 * 24 * 7,
|
||||
mode: "restore",
|
||||
}, // 7天缓存
|
||||
hitSource: "comment-submit",
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
/**
|
||||
* @description 评论列表
|
||||
* @param params
|
||||
*/
|
||||
export const replyListApi = (params: any) => {
|
||||
return service.Post('/api/auth/reply/list', {
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
comment_id: params.comment_id,
|
||||
topic_id: params.topic_id,
|
||||
},
|
||||
{
|
||||
cacheFor: {
|
||||
expire: 60 * 60 * 24 * 7,
|
||||
mode: "restore",
|
||||
}, // 7天缓存
|
||||
hitSource: ["reply-submit", "reply-reply-submit"],
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 回复的回复提交
|
||||
* @param params
|
||||
*/
|
||||
export const replyReplySubmitApi = (params: any) => {
|
||||
return service.Post('/api/auth/reply/reply/submit', {
|
||||
user_id: params.user_id,
|
||||
content: params.content,
|
||||
images: params.images,
|
||||
topic_id: params.topic_id,
|
||||
reply_to_user: params.reply_to_user,
|
||||
reply_to: params.reply_to,
|
||||
reply_id: params.reply_id,
|
||||
reply_user: params.reply_user,
|
||||
author: params.author,
|
||||
},
|
||||
{
|
||||
name: 'reply-reply-submit',
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
},
|
||||
|
@@ -89,54 +89,63 @@
|
||||
<ASegmented v-model:value="segmentedValue" :options="data" class="reply-header-sort"/>
|
||||
</AFlex>
|
||||
</div>
|
||||
<div class="reply-list">
|
||||
<div class="reply-item">
|
||||
<AFlex :vertical="false">
|
||||
<ASkeleton :loading="commentLoading" avatar active :paragraph="{ rows: 4 }"
|
||||
>
|
||||
<div class="reply-list" v-if="commentList?.comments">
|
||||
<div class="reply-item" v-for="(item, index) in commentList?.comments" :key="index">
|
||||
<AFlex :vertical="false" style="margin-top: 5px">
|
||||
<!-- 评论头像 -->
|
||||
<AFlex :vertical="true" class="reply-avatar">
|
||||
<AAvatar :size="50" shape="circle" src="https://api.multiavatar.com/landaiqing.svg"/>
|
||||
<AFlex :vertical="true" class="reply-avatar" v-if="item.avatar">
|
||||
<AAvatar :size="50" shape="circle" :src="item.avatar"/>
|
||||
</AFlex>
|
||||
<!-- 评论内容 -->
|
||||
<AFlex :vertical="true" class="reply-content">
|
||||
<AFlex :vertical="true">
|
||||
<AFlex :vertical="false" align="flex-start">
|
||||
<span class="reply-name">张立国</span>
|
||||
<span class="reply-name">{{ item.nickname }}</span>
|
||||
<a-tag color="cyan" class="reply-tag" size="small">Lv.5</a-tag>
|
||||
<a-tag color="red" class="reply-tag" size="small">UP</a-tag>
|
||||
<a-tag color="red" class="reply-tag" size="small" v-if="item.author===1">UP</a-tag>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="flex-end" justify="space-between">
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="reply-ip"> 成都市 </span>
|
||||
<span class="reply-ip" style="margin-left: 10px;">IP: 192.168.1.100 </span>
|
||||
<span class="reply-ip"> {{ item.location }} </span>
|
||||
</AFlex>
|
||||
<span class="reply-time">{{ new Date().toLocaleString() }}</span>
|
||||
<span class="reply-time">{{ formatTimeAgo(item.created_time) }}</span>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center">
|
||||
|
||||
<ACard class="reply-card" :body-style="{padding: '10px'}">
|
||||
<span class="reply-text">
|
||||
床前明月光,疑是地上霜。<br>
|
||||
举头望明月,低头思故乡。
|
||||
</span>
|
||||
<div class="reply-text" v-html="item.content">
|
||||
</div>
|
||||
<AFlex :vertical="false" align="center" class="reply-images" v-if="item.images">
|
||||
<AAvatar shape="square" size="large"
|
||||
v-for="(image, index) in item.images" :key="index">
|
||||
<template #icon>
|
||||
<AImage :width="40" :height="40" :src="image"/>
|
||||
</template>
|
||||
</AAvatar>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" justify="space-between" align="center">
|
||||
<!--评论操作按钮 -->
|
||||
<AFlex :vertical="false" align="center" justify="space-between" class="reply-action-item">
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton type="text" size="small" :icon="h(LikeOutlined)" class="reply-action-btn">
|
||||
10
|
||||
{{ item.likes }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton type="text" size="small" :icon="h(DislikeOutlined)" class="reply-action-btn">
|
||||
1
|
||||
{{ item.dislikes }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
<AButton type="text" size="small" :icon="h(MessageOutlined)" class="reply-action-btn">
|
||||
11
|
||||
<AButton @click="replyListThrottled(item.id)" type="text" size="small"
|
||||
:icon="h(MessageOutlined)"
|
||||
class="reply-action-btn">
|
||||
{{ item.reply_count }}
|
||||
</AButton>
|
||||
<AButton
|
||||
@click="replyInputVisible === true? (replyInputVisible = false) : (replyInputVisible = true) "
|
||||
@click="handleShowReplyInput(item.id)"
|
||||
type="text" size="small" :icon="h(CommentOutlined)"
|
||||
class="reply-action-btn">
|
||||
{{ t('comment.reply') }}
|
||||
@@ -145,10 +154,10 @@
|
||||
<!-- 评论操作系统信息-->
|
||||
<AFlex :vertical="false" align="center" justify="flex-end" class="reply-action-item-right">
|
||||
<AButton type="text" disabled size="small" :icon="h(WindowsOutlined)" class="reply-action-info">
|
||||
windows 10
|
||||
{{ item.operating_system }}
|
||||
</AButton>
|
||||
<AButton type="text" disabled size="small" :icon="h(ChromeOutlined)" class="reply-action-info">
|
||||
chrome
|
||||
{{ item.browser }}
|
||||
</AButton>
|
||||
<!-- 评论操作按钮 -->
|
||||
<ADropdown trigger="click">
|
||||
@@ -178,11 +187,11 @@
|
||||
|
||||
</AFlex>
|
||||
<!-- 回复输入框 -->
|
||||
<AFlex :vertical="true" class="reply-input-main" v-show="replyInputVisible">
|
||||
<AFlex :vertical="true" class="reply-input-main" v-if="showReplyInput && item.id === showReplyInput">
|
||||
<AFlex :vertical="false" align="center" class="reply-input-header">
|
||||
<span class="reply-input-title">{{ t('comment.reply') + ':' }}</span>
|
||||
<span class="reply-input-author">张立国</span>
|
||||
<AButton @click="replyInputVisible = false" type="dashed" size="small" :icon="h(CloseOutlined )"
|
||||
<span class="reply-input-author">{{ item.nickname }}</span>
|
||||
<AButton @click="closeReplyInput" type="dashed" size="small" :icon="h(CloseOutlined )"
|
||||
class="reply-input-cancel">
|
||||
{{ t('comment.cancelReply') }}
|
||||
</AButton>
|
||||
@@ -195,15 +204,35 @@
|
||||
<!-- 评论输入框 -->
|
||||
<AFlex :vertical="true" class="reply-input-content-text">
|
||||
<ATextarea :rows="3" class="comment-text-reply"
|
||||
v-model:value="replyContent"
|
||||
@keyup.ctrl.enter="()=>{
|
||||
const params: any ={
|
||||
reply_id: item.id,
|
||||
reply_user: item.user_id
|
||||
}
|
||||
replySubmitThrottled(params);
|
||||
}"
|
||||
:placeholder="commentTextAreaPlaceholder" allow-clear :showCount="false"/>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" class="comment-actions-reply"
|
||||
>
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AFlex :vertical="false" align="center" class="comment-action-item-reply">
|
||||
<APopover trigger="click" placement="bottom">
|
||||
<template #content>
|
||||
<div style="width: 170px;height: 200px;overflow: auto;">
|
||||
<template v-for="(emoji) in EMOJI" :key="emoji">
|
||||
<AButton @click="insertEmojiToReplyContent(emoji)" type="text" size="large">{{
|
||||
emoji
|
||||
}}
|
||||
</AButton>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<AButton type="text" size="small" :icon="h(SmileOutlined)"
|
||||
class="comment-action-icon-reply">
|
||||
{{ t('comment.emoji') }}
|
||||
</AButton>
|
||||
</APopover>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" class="comment-action-item-reply">
|
||||
<AUpload
|
||||
@@ -240,7 +269,13 @@
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton @click="replySubmitThrottled" type="primary" size="middle"
|
||||
<AButton @click="()=>{
|
||||
const params: any ={
|
||||
reply_id: item.id,
|
||||
reply_user: item.user_id
|
||||
}
|
||||
replySubmitThrottled(params);
|
||||
}" type="primary" size="middle"
|
||||
:disabled="replyContent.trim().length === 0"
|
||||
class="comment-action-btn-reply">
|
||||
{{ t('comment.sendComment') }}
|
||||
@@ -252,51 +287,61 @@
|
||||
</AFlex>
|
||||
|
||||
<!-- 子回复列表 -->
|
||||
<AFlex :vertical="false" class="reply-item-child">
|
||||
|
||||
<AFlex :vertical="true" class="reply-item-child"
|
||||
v-if="replyList.comments && showReplyComment && showReplyComment === item.id">
|
||||
<ASpin :spinning="replyLoading">
|
||||
<AFlex :vertical="false" style="margin-top: 5px" v-for="(child, index) in replyList.comments"
|
||||
:key="index">
|
||||
<AFlex :vertical="true" class="reply-item-child-avatar">
|
||||
<AAvatar :size="40" shape="circle" src="https://api.multiavatar.com/landaiqing.svg"/>
|
||||
<AAvatar :size="40" shape="circle" :src="child.avatar"/>
|
||||
</AFlex>
|
||||
<AFlex :vertical="true" class="reply-item-child-content">
|
||||
<AFlex :vertical="true">
|
||||
<AFlex :vertical="false" align="center">
|
||||
<span class="reply-name-child">沈建明</span> <span class="reply-at">@张立国</span>
|
||||
<span class="reply-name-child">{{ child.nickname }}</span>
|
||||
<span
|
||||
class="reply-at">@{{ child.reply_username }}</span>
|
||||
<a-tag color="cyan" class="reply-tag-child" size="small">Lv.5</a-tag>
|
||||
<!-- <a-tag color="red" class="reply-tag" size="small">UP</a-tag>-->
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="flex-end" justify="space-between">
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="reply-ip-child"> 成都市 </span>
|
||||
<span class="reply-ip-child" style="margin-left: 10px;">IP: 192.168.1.100 </span>
|
||||
<span class="reply-ip-child"> {{ child.location }} </span>
|
||||
</AFlex>
|
||||
<span class="reply-time-child">{{ new Date().toLocaleString() }}</span>
|
||||
<span class="reply-time-child">{{ formatTimeAgo(child.created_time) }}</span>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<AFlex :vertical="true" align="center">
|
||||
<ACard class="reply-card-child" :body-style="{padding: '10px'}">
|
||||
<span class="reply-text-child">
|
||||
床前明月光,疑是地上霜。<br>
|
||||
举头望明月,低头思故乡。
|
||||
</span>
|
||||
<div class="reply-text-child" v-html="child.content">
|
||||
</div>
|
||||
<AFlex :vertical="false" align="center" class="reply-images" v-if="child.images">
|
||||
<AAvatar shape="square" size="large"
|
||||
v-for="(image, index) in child.images" :key="index">
|
||||
<template #icon>
|
||||
<AImage :width="40" :height="40" :src="image"/>
|
||||
</template>
|
||||
</AAvatar>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" justify="space-between" align="center">
|
||||
<!--评论操作按钮 -->
|
||||
<AFlex :vertical="false" align="center" justify="space-between"
|
||||
class="reply-action-item-child">
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton type="text" size="small" :icon="h(LikeOutlined)" class="reply-action-btn-child">
|
||||
10
|
||||
<AButton type="text" size="small" :icon="h(LikeOutlined)"
|
||||
class="reply-action-btn-child">
|
||||
{{ child.likes }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton type="text" size="small" :icon="h(DislikeOutlined)"
|
||||
class="reply-action-btn-child">
|
||||
1
|
||||
{{ child.dislikes }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
<AButton type="text" size="small" :icon="h(MessageOutlined)" class="reply-action-btn-child">
|
||||
11
|
||||
</AButton>
|
||||
<AButton
|
||||
@click="replyInputVisible === true? (replyInputVisible = false) : (replyInputVisible = true) "
|
||||
@click="handleShowReplyInput(child.id)"
|
||||
type="text" size="small" :icon="h(CommentOutlined)"
|
||||
class="reply-action-btn-child">
|
||||
{{ t('comment.reply') }}
|
||||
@@ -307,11 +352,11 @@
|
||||
class="reply-action-item-right-child">
|
||||
<AButton type="text" disabled size="small" :icon="h(WindowsOutlined)"
|
||||
class="reply-action-info-child">
|
||||
windows 10
|
||||
{{ child.operating_system }}
|
||||
</AButton>
|
||||
<AButton type="text" disabled size="small" :icon="h(ChromeOutlined)"
|
||||
class="reply-action-info-child">
|
||||
chrome
|
||||
{{ child.browser }}
|
||||
</AButton>
|
||||
<!-- 评论操作按钮 -->
|
||||
<ADropdown trigger="click">
|
||||
@@ -338,11 +383,12 @@
|
||||
</ACard>
|
||||
|
||||
<!-- 子评论回复输入框 -->
|
||||
<AFlex :vertical="true" class="reply-input-main-child" v-show="replyInputVisible">
|
||||
<AFlex :vertical="true" class="reply-input-main-child"
|
||||
v-if="showReplyInput && child.id === 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">沈建明</span>
|
||||
<AButton @click="replyInputVisible = false" type="dashed" size="small"
|
||||
<span class="reply-input-author-child">{{ child.nickname }}</span>
|
||||
<AButton @click="closeReplyInput" type="dashed" size="small"
|
||||
:icon="h(CloseOutlined )"
|
||||
class="reply-input-cancel-child">
|
||||
{{ t('comment.cancelReply') }}
|
||||
@@ -356,26 +402,87 @@
|
||||
<!-- 评论输入框 -->
|
||||
<AFlex :vertical="true" class="reply-input-content-text-child">
|
||||
<ATextarea :rows="3" class="comment-text-reply-child"
|
||||
v-model:value="replyReplyContent"
|
||||
@keyup.ctrl.enter="()=>{
|
||||
const params: any ={
|
||||
reply_to: child.id,
|
||||
reply_id: item.id,
|
||||
reply_user: child.user_id
|
||||
};
|
||||
replyReplySubmitThrottled(params);
|
||||
}"
|
||||
:placeholder="commentTextAreaPlaceholder" 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: 170px;height: 200px;overflow: auto;">
|
||||
<template v-for="(emoji) in EMOJI" :key="emoji">
|
||||
<AButton @click="insertEmojiToReplyReplyContent(emoji)" type="text"
|
||||
size="large">{{
|
||||
emoji
|
||||
}}
|
||||
</AButton>
|
||||
</template>
|
||||
</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, image/gif, image/svg+xml, image/webp'"
|
||||
name="images"
|
||||
:max-count="3"
|
||||
:multiple="true"
|
||||
method="post"
|
||||
:directory="false"
|
||||
:show-upload-list="false"
|
||||
:custom-request="customUploadRequest"
|
||||
:before-upload="beforeUpload"
|
||||
:disabled="imageList.length >= 3"
|
||||
>
|
||||
<ABadge :count="imageList.length">
|
||||
<AButton type="text" size="small" :icon="h(PictureOutlined)"
|
||||
class="comment-action-icon-reply-child">
|
||||
{{ t('comment.picture') }}
|
||||
</AButton>
|
||||
</ABadge>
|
||||
</AUpload>
|
||||
<template v-if="imageList.length > 0">
|
||||
<ABadge style="margin-left: 10px;" v-for="(item, index) in imageList"
|
||||
:key="index">
|
||||
<template #count>
|
||||
<CloseCircleOutlined @click="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>
|
||||
</template>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center">
|
||||
<AButton type="primary" size="middle" class="comment-action-btn-reply-child">
|
||||
<AButton
|
||||
@click="()=>{
|
||||
const params: any ={
|
||||
reply_to: child.id,
|
||||
reply_id: item.id,
|
||||
reply_user: child.user_id
|
||||
};
|
||||
replyReplySubmitThrottled(params);
|
||||
}"
|
||||
:disabled="replyReplyContent.trim().length === 0" type="primary" size="middle"
|
||||
class="comment-action-btn-reply-child">
|
||||
{{ t('comment.sendComment') }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
@@ -386,18 +493,28 @@
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</ASpin>
|
||||
</AFlex>
|
||||
|
||||
|
||||
</AFlex>
|
||||
|
||||
|
||||
</AFlex>
|
||||
</div>
|
||||
<APagination class="reply-pagination" @change="(page, pageSize)=>{
|
||||
console.log(page, pageSize)
|
||||
}" v-model:current="commentList.current" :size="commentList.size.toString()" :total="commentList.total"
|
||||
show-less-items/>
|
||||
</div>
|
||||
</ASkeleton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {h, reactive, ref} from "vue";
|
||||
import {h, onMounted, reactive, ref} from "vue";
|
||||
import {
|
||||
ChromeOutlined,
|
||||
CloseOutlined,
|
||||
@@ -414,20 +531,27 @@ import EMOJI from "@/constant/emoji.ts";
|
||||
import imageCompression from "browser-image-compression";
|
||||
import {message} from "ant-design-vue";
|
||||
import {useThrottleFn} from "@vueuse/core";
|
||||
import {commentListApi, commentSubmitApi, replySubmitApi} from "@/api/comment";
|
||||
import {commentListApi, commentSubmitApi, replyListApi, replyReplySubmitApi, replySubmitApi} from "@/api/comment";
|
||||
import useStore from "@/store";
|
||||
import {Comment, ReplyCommentParams} from "@/types/comment";
|
||||
|
||||
const {t} = useI18n();
|
||||
const showCommentActions = ref<boolean>(false);
|
||||
const commentTextAreaPlaceholder = ref<string>(t('comment.placeholder'));
|
||||
const data = reactive([t('comment.latest'), t('comment.hot')]);
|
||||
const segmentedValue = ref<string>(data[0]);
|
||||
const replyInputVisible = ref<boolean>(false);
|
||||
const commentContent = ref<string>("");
|
||||
const replyContent = ref<string>("");
|
||||
const replyReplyContent = ref<string>("");
|
||||
const fileList = ref<any[]>([]);
|
||||
const imageList = ref<any[]>([]);
|
||||
const user = useStore().user;
|
||||
const commentList = ref<Comment>({} as Comment);
|
||||
const showReplyInput = ref<number | null>(null);
|
||||
const showReplyComment = ref<number | null>(null);
|
||||
const replyList = ref<Comment>({} as Comment);
|
||||
const commentLoading = ref<boolean>(true);
|
||||
const replyLoading = ref<boolean>(true);
|
||||
|
||||
/**
|
||||
* 聚焦事件
|
||||
@@ -444,6 +568,22 @@ async function insertEmoji(emoji: string) {
|
||||
commentContent.value += emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入表情到回复内容
|
||||
* @param emoji
|
||||
*/
|
||||
async function insertEmojiToReplyContent(emoji: string) {
|
||||
replyContent.value += emoji;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入表情到回复内容
|
||||
* @param emoji
|
||||
*/
|
||||
async function insertEmojiToReplyReplyContent(emoji: string) {
|
||||
replyReplyContent.value += emoji;
|
||||
}
|
||||
|
||||
// 压缩图片配置
|
||||
const options = {
|
||||
maxSizeMB: 0.4,
|
||||
@@ -518,6 +658,7 @@ async function commentSubmit() {
|
||||
commentContent.value = "";
|
||||
fileList.value = [];
|
||||
imageList.value = [];
|
||||
await getCommentList();
|
||||
} else {
|
||||
message.error("评论失败");
|
||||
}
|
||||
@@ -531,8 +672,8 @@ const replySubmitThrottled = useThrottleFn(replySubmit, 1000);
|
||||
/**
|
||||
* 回复提交
|
||||
*/
|
||||
async function replySubmit() {
|
||||
if (commentContent.value.trim() === "") {
|
||||
async function replySubmit(data: ReplyCommentParams) {
|
||||
if (replyContent.value.trim() === "") {
|
||||
message.error("回复内容不能为空");
|
||||
return;
|
||||
}
|
||||
@@ -540,21 +681,21 @@ async function replySubmit() {
|
||||
message.error("最多只能上传3张图片");
|
||||
return;
|
||||
}
|
||||
const content = commentContent.value.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
|
||||
const content = replyContent.value.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
|
||||
|
||||
const replyParams: object = {
|
||||
const replyParams: ReplyCommentParams = {
|
||||
user_id: user.user.uid,
|
||||
topic_id: "123",
|
||||
content: content,
|
||||
images: imageList.value,
|
||||
author: user.user.uid,
|
||||
reply_id: "5",
|
||||
reply_user: user.user.uid,
|
||||
reply_id: data.reply_id,
|
||||
reply_user: data.reply_user,
|
||||
};
|
||||
const result: any = await replySubmitApi(replyParams);
|
||||
if (result.code === 200 && result.success) {
|
||||
message.success("回复成功");
|
||||
commentContent.value = "";
|
||||
replyContent.value = "";
|
||||
fileList.value = [];
|
||||
imageList.value = [];
|
||||
} else {
|
||||
@@ -569,14 +710,132 @@ async function getCommentList() {
|
||||
const params = {
|
||||
topic_id: "123",
|
||||
page: 1,
|
||||
size: 10,
|
||||
size: 5,
|
||||
};
|
||||
// 获取评论列表
|
||||
const result: any = await commentListApi(params);
|
||||
console.log(result);
|
||||
if (result.code === 200 && result.success && result.data) {
|
||||
commentList.value = result.data;
|
||||
commentLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
getCommentList();
|
||||
/**
|
||||
* 格式化时间
|
||||
* @param dateString
|
||||
*/
|
||||
function formatTimeAgo(dateString) {
|
||||
const now: any = new Date();
|
||||
const date: any = new Date(dateString);
|
||||
const seconds = Math.floor((now - date) / 1000);
|
||||
|
||||
const intervals = [
|
||||
{label: '年', seconds: 31536000},
|
||||
{label: '个月', seconds: 2592000},
|
||||
{label: '天', seconds: 86400},
|
||||
{label: '小时', seconds: 3600},
|
||||
{label: '分钟', seconds: 60}
|
||||
];
|
||||
|
||||
for (const interval of intervals) {
|
||||
const count = Math.floor(seconds / interval.seconds);
|
||||
if (count > 0) {
|
||||
return `${count} ${interval.label}前`;
|
||||
}
|
||||
}
|
||||
|
||||
return `${seconds} 秒前`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示回复输入框
|
||||
*/
|
||||
const handleShowReplyInput = (index: any) => {
|
||||
showReplyInput.value = showReplyInput.value === index ? null : index;
|
||||
};
|
||||
/**
|
||||
* 显示回复
|
||||
*/
|
||||
const handleShowReplyComment = (index: any) => {
|
||||
showReplyComment.value = showReplyComment.value === index ? null : index;
|
||||
};
|
||||
/**
|
||||
* 关闭回复输入框
|
||||
*/
|
||||
const closeReplyInput = () => {
|
||||
showReplyInput.value = null;
|
||||
};
|
||||
/**
|
||||
* 获取回复列表 throttled
|
||||
*/
|
||||
const replyListThrottled = useThrottleFn(getReplyList, 500);
|
||||
|
||||
/**
|
||||
* 获取回复列表
|
||||
* @param reply_id
|
||||
*/
|
||||
async function getReplyList(reply_id: number) {
|
||||
const params: any = {
|
||||
topic_id: "123",
|
||||
page: 1,
|
||||
size: 5,
|
||||
comment_id: reply_id,
|
||||
};
|
||||
// 获取评论列表
|
||||
const result: any = await replyListApi(params);
|
||||
if (result.code === 200 && result.success && result.data) {
|
||||
replyList.value = result.data;
|
||||
handleShowReplyComment(reply_id);
|
||||
replyLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回复提交 throttled
|
||||
*/
|
||||
const replyReplySubmitThrottled = useThrottleFn(replyReplySubmit, 1000);
|
||||
|
||||
/**
|
||||
* 回复评论提交
|
||||
* @param data
|
||||
*/
|
||||
async function replyReplySubmit(data: any) {
|
||||
if (replyReplyContent.value.trim() === "") {
|
||||
message.error("回复内容不能为空");
|
||||
return;
|
||||
}
|
||||
if (imageList.value.length > 3) {
|
||||
message.error("最多只能上传3张图片");
|
||||
return;
|
||||
}
|
||||
const content = replyReplyContent.value.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>').replace(/\s/g, ' ');
|
||||
|
||||
const replyParams: ReplyCommentParams = {
|
||||
user_id: user.user.uid,
|
||||
topic_id: "123",
|
||||
content: content,
|
||||
images: imageList.value,
|
||||
author: user.user.uid,
|
||||
reply_to: data.reply_to,
|
||||
reply_id: data.reply_id,
|
||||
reply_user: data.reply_user,
|
||||
};
|
||||
const result: any = await replyReplySubmitApi(replyParams);
|
||||
if (result.code === 200 && result.success) {
|
||||
message.success("回复成功");
|
||||
replyReplyContent.value = "";
|
||||
fileList.value = [];
|
||||
imageList.value = [];
|
||||
} else {
|
||||
message.error("回复失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getCommentList();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style src="./index.scss" lang="scss" scoped>
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
flex-direction: column;
|
||||
border: 1px solid #ccc;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
width: 650px;
|
||||
padding: 50px;
|
||||
|
||||
.comment-header-title {
|
||||
@@ -19,6 +19,7 @@
|
||||
.comment-text {
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
margin-top: 10px;
|
||||
|
||||
@@ -61,6 +62,12 @@
|
||||
.reply-list {
|
||||
margin-top: 30px;
|
||||
|
||||
.reply-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
margin-left: 20px;
|
||||
|
||||
@@ -91,9 +98,12 @@
|
||||
width: 600px;
|
||||
//margin-top: 5px;
|
||||
|
||||
.reply-images {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.reply-action-item {
|
||||
margin-top: 10px;
|
||||
width: 200px;
|
||||
|
||||
.reply-action-btn {
|
||||
font-size: 13px;
|
||||
@@ -253,7 +263,6 @@
|
||||
|
||||
.reply-action-item-child {
|
||||
margin-top: 10px;
|
||||
width: 200px;
|
||||
|
||||
.reply-action-btn-child {
|
||||
font-size: 13px;
|
||||
|
38
src/types/comment.d.ts
vendored
38
src/types/comment.d.ts
vendored
@@ -1 +1,39 @@
|
||||
export interface Comment {
|
||||
comments: CommentContent[];
|
||||
current: number;
|
||||
total: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface CommentContent {
|
||||
author: number;
|
||||
browser: string;
|
||||
content: string;
|
||||
created_time: string;
|
||||
dislikes: number;
|
||||
id: number;
|
||||
likes: number;
|
||||
location: string;
|
||||
operating_system: string;
|
||||
reply_count: number;
|
||||
reply_username: string;
|
||||
reply_id: string;
|
||||
reply_user: string;
|
||||
topic_id: string;
|
||||
user_id: string;
|
||||
avatar: string;
|
||||
nickname: string;
|
||||
level?: number;
|
||||
images: string[];
|
||||
}
|
||||
|
||||
interface ReplyCommentParams {
|
||||
user_id: string,
|
||||
topic_id: string,
|
||||
content: string,
|
||||
images: string[],
|
||||
author: string,
|
||||
reply_id: number,
|
||||
reply_user: string,
|
||||
reply_to: number,
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ export const service = createAlova({
|
||||
if (!method.meta?.ignoreToken) {
|
||||
const user = useStore().user;
|
||||
method.config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${user.user.accessToken}`;
|
||||
method.config.headers['X-UID'] = user.user.uid;
|
||||
}
|
||||
const lang = useStore().lang;
|
||||
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
||||
|
@@ -54,7 +54,7 @@
|
||||
</AButton>
|
||||
<AButton v-if="state.showCountDown" disabled style="margin-left: 10px" size="large">
|
||||
{{
|
||||
state.countDownTime
|
||||
currentCountDownTime
|
||||
}}s{{ t("login.reSendCaptcha") }}
|
||||
</AButton>
|
||||
</AFlex>
|
||||
@@ -187,7 +187,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {Rule} from "ant-design-vue/lib/form";
|
||||
import {onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||
import {computed, onMounted, reactive, ref, UnwrapRef} from "vue";
|
||||
import {AccountLogin, PhoneLogin} from "@/types/user";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import BoxDog from "@/components/BoxDog/BoxDog.vue";
|
||||
@@ -272,11 +272,16 @@ const rules: Record<string, Rule[]> = {
|
||||
]
|
||||
};
|
||||
|
||||
const state: any = reactive<any>({
|
||||
interface State {
|
||||
countDownTime: number;
|
||||
showCountDown: boolean;
|
||||
}
|
||||
|
||||
const state = reactive<State>({
|
||||
countDownTime: 60,
|
||||
showCountDown: false,
|
||||
});
|
||||
|
||||
} as State);
|
||||
const currentCountDownTime = computed(() => state.countDownTime);
|
||||
/**
|
||||
* 验证码发送倒计时
|
||||
*/
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<h1>Welcome to Main Page</h1>
|
||||
<AButton @click="handleClick">获取登录用户角色</AButton>
|
||||
{{ data }}
|
||||
|
Reference in New Issue
Block a user