feat: 分享页评论回复删除

This commit is contained in:
rd
2025-08-11 15:13:46 +08:00
parent 010b9a3ac7
commit 2f3a4560fc
9 changed files with 173 additions and 198 deletions

View File

@ -4,7 +4,8 @@ import TextOverTips from '@/components/text-over-tips';
import SvgIcon from '@/components/svg-icon/index.vue';
import DeleteCommentModal from './delete-comment-modal.vue';
import { RESULT_LIST, ENUM_OPINION, formatRelativeTime } from '../../constants';
import { RESULT_LIST } from '@/views/creative-generation-workshop/manuscript/check/components/content-card/constants.ts';
import { ENUM_OPINION, formatRelativeTime } from '../../constants';
import { postShareWorksComments, deleteShareWorksComments } from '@/api/all/generationWorkshop.ts';
import { exactFormatTime } from '@/utils/tools.ts';
import { useUserStore } from '@/stores';
@ -14,47 +15,12 @@ import icon2 from '@/assets/img/creative-generation-workshop/icon-avatar-default
import icon3 from '@/assets/img/error-img.png';
const _iconMap = new Map([
[1, { icon: <icon-check-circle-fill size={16} class="color-#25C883 flex-shrink-0" /> }],
// [3, { icon: <icon-check-circle-fill size={16} class="color-#25C883 flex-shrink-0" /> }],
[2, { icon: <icon-exclamation-circle-fill size={16} class="color-#F64B31 flex-shrink-0" /> }],
[3, { icon: <icon-exclamation-circle-fill size={16} class="color-#FFAE00 flex-shrink-0" /> }],
[4, { icon: <icon-exclamation-circle-fill size={16} class="color-#939499 flex-shrink-0" /> }],
[1, { icon: <icon-exclamation-circle-fill size={16} class="color-#FFAE00 flex-shrink-0" /> }],
[0, { icon: <icon-check-circle-fill size={16} class="color-#25C883 flex-shrink-0" /> }],
]);
const data1 = [
{
label: '色情检测',
level: 1,
},
{
label: '涉政检测',
level: 2,
},
{
label: '涉政正负向检测',
level: 3,
},
{
label: '涉政正负向检测',
level: 3,
},
{
label: '涉政正负向检测',
level: 3,
},
{
label: '涉政正负向检测',
level: 3,
},
{
label: '暴恐检测',
level: 4,
},
{
label: '暴恐检测',
level: 4,
},
];
export default {
props: {
isExpand: {
@ -76,6 +42,10 @@ export default {
const isReplay = ref(false);
const replayTarget = ref({});
const deleteCommentModalRef = ref(null);
const textAreaRef = ref(null);
const aiReview = computed(() => props.dataSource.ai_review);
const violationItems = computed(() => props.dataSource?.ai_review?.violation_items ?? []);
const closeReplay = () => {
isReplay.value = false;
@ -88,13 +58,15 @@ export default {
};
const onComment = async () => {
console.log(textAreaRef.value.focus());
const { code, data } = await postShareWorksComments(props.dataSource.id, route.params.shareCode, {
content: comment.value,
comment_id: replayTarget.value.id,
});
if (code === 200) {
emit('updateComment');
// comment.value = '';
comment.value = '';
textAreaRef.value.focus();
}
};
@ -153,6 +125,7 @@ export default {
)}
<Textarea
ref={textAreaRef}
auto-size
class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`}
size="large"
@ -179,49 +152,109 @@ export default {
const renderCommentBox = () => {
return (
<div class="comment-box">
<p class="my-16px">
<p class="mb-16px">
<span class="cts bold cm !text-16px !lh-24px mr-8px">评论</span>
{props.dataSource.comments?.length > 0 && (
<span class="cts !text-16px !lh-24px bold">{props.dataSource.comments?.length}</span>
)}
</p>
<div class="comment-list flex flex-col my-16px rounded-8px">
{props.dataSource.comments?.map((item) => (
<div class="comment-item flex px-12px py-8px group" key={item.id}>
<Image
src={item.commenter_id === 0 ? icon2 : item.commenter?.head_image}
width={40}
height={40}
preview={false}
fit="cover"
class="rounded-50% mr-13px"
v-slots={{
error: () => <img src={icon3} class="w-40 h-40 rounded-50%" />,
}}
/>
{props.dataSource.comments?.length > 0 && (
<div class="comment-list flex flex-col my-16px rounded-8px">
{props.dataSource.comments?.map((item) => (
<div class="comment-item flex px-12px py-8px group" key={item.id}>
<Image
src={item.commenter_id === 0 ? icon2 : item.commenter?.head_image}
width={40}
height={40}
preview={false}
fit="cover"
class="rounded-50% mr-13px"
v-slots={{
error: () => <img src={icon3} class="w-40 h-40 rounded-50%" />,
}}
/>
<div class="flex flex-col flex-1">
<div class="flex justify-between">
<p class="mb-4px">
<span class="cts !color-#211F24 mr-8px">{getCommentName(item)}</span>
<span class="cts !color-#939499">{formatRelativeTime(item.created_at)}</span>
</p>
<div class="items-center hidden group-hover:flex">
<SvgIcon
onClick={() => onReplay(item)}
name="svg-comment"
size={16}
class="color-#55585F cursor-pointer hover:color-#6D4CFE"
/>
{renderDeleteBtn(item)}
<div class="flex flex-col flex-1 overflow-hidden">
<div class="flex justify-between">
<p class="mb-4px">
<span class="cts !color-#211F24 mr-8px">{getCommentName(item)}</span>
<span class="cts !color-#939499">{formatRelativeTime(item.created_at)}</span>
</p>
<div class="items-center hidden group-hover:flex">
<SvgIcon
onClick={() => onReplay(item)}
name="svg-comment"
size={16}
class="color-#55585F cursor-pointer hover:color-#6D4CFE"
/>
{renderDeleteBtn(item)}
</div>
</div>
{item.reply_comment && (
<div class="flex items-center">
<div class="w-2px h-12px bg-#B1B2B5"></div>
<span class="mx-4px cts !color-#939499 flex-shrink-0">回复</span>
<TextOverTips
context={`${getCommentName(item.reply_comment)}${item.reply_comment.content}`}
class="cts !color-#939499"
/>
</div>
)}
<p class="cts !color-#211F24">{item.content}</p>
</div>
<p class="cts !color-#211F24">{item.content}</p>
</div>
))}
</div>
)}
</div>
);
};
const renderAiSuggest = () => {
if (isEmpty(aiReview.value)) return null;
return (
<>
<div class="result-box p-16px rounded-8px">
<div class="flex items-center justify-between mb-16px">
<p class="cts bold !color-#000 !text-16px">审核结果</p>
<Button
type="text"
class="!color-#6D4CFE hover:!color-#8A70FE"
onClick={() => (isCollapse.value = !isCollapse.value)}
>
{isCollapse.value ? '展开详情' : '收起详情'}
</Button>
</div>
<div class="flex items-center">
{RESULT_LIST.map((item, index) => (
<div class="flex flex-col justify-center items-center flex-1 result-item" key={index}>
<span class="s1" style={{ color: item.color }}>{`${aiReview.value?.[item.value]}${
item.suffix || ''
}`}</span>{' '}
<span class="cts mt-4px !lh-20px !text-12px !color-#737478">{item.label}</span>
</div>
))}
</div>
</div>
<div class={`collapse-box mb-16px overflow-hidden ${isCollapse.value ? 'h-0 ' : 'h-auto'}`}>
{violationItems.value.length > 0 && (
<div class="result-box p-16px rounded-8px mt-16px">
<p class="cts bold !color-#000 !text-16px mb-16px">敏感词检测</p>
<div class="grid grid-cols-3 gap-x-24px gap-y-8px">
{violationItems.value.map((item, index) => (
<div class="audit-item" key={index}>
<div class="flex items-center h-20px">
{_iconMap.get(item.risk_level)?.icon}
<TextOverTips context={item.word} class="cts ml-4px !color-#000" />
</div>
</div>
))}
</div>
</div>
))}
)}
</div>
</div>
</>
);
};
@ -245,81 +278,23 @@ export default {
return () => (
<section class="py-16px absolute right-16px w-440px h-full overflow-hidden">
<div class="ai-suggest-box relative py-24px flex flex-col">
<div class="mb-16px w-full flex justify-between px-24px">
<div class="relative w-fit">
{!isEmpty(aiReview.value) && (
<div class="relative w-fit ml-24px mb-16px">
<span class="ai-text">AI 智能审核</span>
<img src={icon1} class="w-80px h-10.8px absolute bottom-1px left--9px" />
</div>
<icon-menu-unfold
size={20}
class="color-#55585F cursor-pointer hover:color-#6D4CFE"
onClick={() => emit('toggle', false)}
/>
</div>
)}
<icon-menu-unfold
size={20}
class="color-#55585F cursor-pointer hover:color-#6D4CFE absolute top-24px right-24px"
onClick={() => emit('toggle', false)}
/>
{/**主体 */}
<div class="flex-1 overflow-y-auto px-24px">
<div class="result-box p-16px rounded-8px">
<div class="flex items-center justify-between mb-16px">
<p class="cts bold !color-#000 !text-16px">审核结果</p>
<Button
type="text"
class="!color-#6D4CFE hover:!color-#8A70FE"
onClick={() => (isCollapse.value = !isCollapse.value)}
>
{isCollapse.value ? '展开详情' : '收起详情'}
</Button>
</div>
<div class="flex items-center">
{RESULT_LIST.map((item, index) => (
<div class="flex flex-col justify-center items-center flex-1 result-item" key={index}>
<span class={`s1 ${item.class}`}>30</span>
<span class="cts mt-4px !lh-20px !text-12px !color-#737478">{item.label}</span>
</div>
))}
</div>
</div>
{/**敏感词检测 */}
<div class={`collapse-box overflow-hidden ${isCollapse.value ? 'h-0 ' : 'h-auto'}`}>
<div class="result-box p-16px rounded-8px mt-16px">
<p class="cts bold !color-#000 !text-16px mb-16px">敏感词检测</p>
<div class="grid grid-cols-3 gap-x-24px gap-y-8px">
{data1.map((item, index) => (
<div class="audit-item" key={index}>
<div class="flex items-center h-20px">
{_iconMap.get(item.level)?.icon}
<TextOverTips context={item.label} class="cts ml-4px !color-#000" />
</div>
</div>
))}
</div>
</div>
<div class="result-box p-16px rounded-8px mt-16px">
<p class="cts bold !color-#000 !text-16px mb-16px">图片内容违规检测</p>
<div class="grid grid-cols-3 gap-x-24px gap-y-8px mb-16px">
{data1.map((item, index) => (
<div class="audit-item" key={index}>
<div class="flex items-center h-20px">
{_iconMap.get(item.level)?.icon}
<TextOverTips context={item.label} class="cts ml-4px !color-#000" />
</div>
</div>
))}
</div>
<p class="cts bold !color-#000 !text-16px mb-16px">图片文字违规检测</p>
<div class="grid grid-cols-3 gap-x-24px gap-y-8px">
{data1.map((item, index) => (
<div class="audit-item" key={index}>
<div class="flex items-center h-20px">
{_iconMap.get(item.level)?.icon}
<TextOverTips context={item.label} class="cts ml-4px !color-#000" />
</div>
</div>
))}
</div>
</div>
</div>
{/* AI审核结果 */}
{renderAiSuggest()}
{/* 评论与回复 */}
{renderCommentBox()}
</div>

View File

@ -39,6 +39,9 @@
&:hover {
border-color: #6d4cfe !important;
}
&.arco-textarea-focus {
border-color: #6d4cfe !important;
}
}
.result-box {
background: rgba(255, 255, 255, 0.8);

View File

@ -1,26 +1,3 @@
export const RESULT_LIST = [
{
label: '合规程度',
value: 'compliance_degree',
class: '!color-#6d4cfe',
},
{
label: '检验项',
value: 'compliance',
class: '!color-#211F24',
},
{
label: '高风险',
value: 'high_risk',
class: '!color-#F64B31',
},
{
label: '中风险',
value: 'medium_risk',
class: '!color-#FFAE00',
},
];
export const ENUM_OPINION = {
wait: 0, // 待确认
confirm: 1, // 已确认

View File

@ -5,7 +5,7 @@ import AiSuggest from './components/ai-suggest/';
import { getShareWorksList, getShareWorksDetail, patchShareWorksConfirm } from '@/api/all/generationWorkshop.ts';
import { convertVideoUrlToCoverUrl, exactFormatTime } from '@/utils/tools.ts';
import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants.ts';
import { RESULT_LIST, ENUM_OPINION } from './constants';
import { ENUM_OPINION } from './constants';
import { handleUserHome } from '@/utils/user.ts';
import { useUserStore } from '@/stores';

View File

@ -39,12 +39,7 @@
</div>
</template>
<template v-if="column.dataIndex === 'compliance_degree'" #cell="{ record }">
<span class="cts num !color-#6D4CFE">{{
record.compliance_degree ? `${record.compliance_degree}%` : '-'
}}</span>
</template>
<template v-else-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<template v-if="column.dataIndex === 'customer_opinion'" #cell="{ record }">
<p
class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
@ -55,7 +50,11 @@
</p>
</template>
<template v-else-if="column.dataIndex === 'platform'" #cell="{ record }">
<img width="24" height="24" :src="PLATFORMS.find((item) => item.value === record.platform)?.icon" />
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img v-else width="24" height="24" :src="PLATFORMS.find((item) => item.value === record.platform)?.icon" />
</template>
<template v-else-if="column.dataIndex === 'compliance_level'" #cell="{ record }">
<span class="cts num !color-#6D4CFE">{{ record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-' }}</span>
</template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />

View File

@ -88,14 +88,10 @@ export const TABLE_COLUMNS2 = [
},
{
title: '合规程度',
dataIndex: 'compliance_degree',
dataIndex: 'compliance_level',
suffix: '%',
width: 120,
},
// {
// title: '合规程度',
// dataIndex: 'platform',
// width: 120,
// },
{
title: '稿件类型',
dataIndex: 'type',
@ -151,7 +147,7 @@ export const TABLE_COLUMNS3 = [
},
{
title: '客户意见',
dataIndex: 'title',
dataIndex: 'customer_opinion',
width: 200,
},
{

View File

@ -48,6 +48,9 @@ export default {
const router = useRouter();
const { copy } = useClipboard({ source: formData.value.link });
const rules = {
receiver: [{ required: true, message: '请输入分享对象' }],
};
const reset = () => {
loading.value = false;
@ -61,23 +64,27 @@ export default {
reset();
};
const onGenerateLink = async () => {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
const onGenerateLink = () => {
formRef.value.validate().then(async (errors) => {
if (!errors) {
try {
loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value);
if (code === 200) {
onClose();
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
AMessage.success('复制成功!');
emit('close');
const url = router.resolve({
path: `/explore/list/${data.code}`,
}).href;
copy(generateFullUrl(url));
AMessage.success('复制成功!');
emit('close');
}
} finally {
loading.value = false;
}
}
} finally {
loading.value = false;
}
});
};
const open = (workIds) => {
formData.value.work_ids = workIds;
@ -107,7 +114,7 @@ export default {
),
}}
>
<Form ref={formRef} model={formData.value} auto-label-width>
<Form ref={formRef} rules={rules} model={formData.value} auto-label-width>
<FormItem label="有效期" prop="days">
<CommonSelect
v-model={formData.value.days}
@ -119,7 +126,7 @@ export default {
allClear={false}
/>
</FormItem>
<FormItem label="分享对象" prop="receiver">
<FormItem label="分享对象" prop="receiver" required>
<Input v-model={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
</FormItem>
</Form>

View File

@ -12,7 +12,7 @@ import {
Textarea,
} from '@arco-design/web-vue';
import { useClipboard } from '@vueuse/core';
import { getWriterLinksGenerate } from '@/api/all/generationWorkshop';
import { getWriterLinksGenerate, getTemplateUrl } from '@/api/all/generationWorkshop';
import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/media-account/icon-feedback-fail.png';
@ -192,7 +192,10 @@ export default {
// 下载模板
const handleDownloadTemplate = async () => {
AMessage.info('下载模板功能开发中...');
const { code, data } = await getTemplateUrl();
if (code === 200) {
window.open(data.download_url, '_blank');
}
};
// 渲染链接上传表单