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

@ -5,6 +5,21 @@ export const getWorksList = (params = {}) => {
return Http.get('/v1/works/list', params); return Http.get('/v1/works/list', params);
}; };
// 内容稿件-获取模板
export const getTemplateUrl = (params = {}) => {
return Http.get('/v1/works/template', params);
};
// 内容稿件-通过链接获取稿件
export const postWorksByLink = (params = {}) => {
return Http.post('/v1/works/by-link', params);
};
// 内容稿件-通过文档获取稿件
export const postWorksByFile = (params = {}) => {
return Http.post('/v1/works/by-file', params);
};
// 内容稿件-批量添加 // 内容稿件-批量添加
export const postWorksBatch = (params = {}) => { export const postWorksBatch = (params = {}) => {
return Http.post('/v1/works/batch', params); return Http.post('/v1/works/batch', params);

View File

@ -4,7 +4,8 @@ import TextOverTips from '@/components/text-over-tips';
import SvgIcon from '@/components/svg-icon/index.vue'; import SvgIcon from '@/components/svg-icon/index.vue';
import DeleteCommentModal from './delete-comment-modal.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 { postShareWorksComments, deleteShareWorksComments } from '@/api/all/generationWorkshop.ts';
import { exactFormatTime } from '@/utils/tools.ts'; import { exactFormatTime } from '@/utils/tools.ts';
import { useUserStore } from '@/stores'; 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'; import icon3 from '@/assets/img/error-img.png';
const _iconMap = new Map([ 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" /> }], [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" /> }], [1, { 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" /> }], [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 { export default {
props: { props: {
isExpand: { isExpand: {
@ -76,6 +42,10 @@ export default {
const isReplay = ref(false); const isReplay = ref(false);
const replayTarget = ref({}); const replayTarget = ref({});
const deleteCommentModalRef = ref(null); 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 = () => { const closeReplay = () => {
isReplay.value = false; isReplay.value = false;
@ -88,13 +58,15 @@ export default {
}; };
const onComment = async () => { const onComment = async () => {
console.log(textAreaRef.value.focus());
const { code, data } = await postShareWorksComments(props.dataSource.id, route.params.shareCode, { const { code, data } = await postShareWorksComments(props.dataSource.id, route.params.shareCode, {
content: comment.value, content: comment.value,
comment_id: replayTarget.value.id, comment_id: replayTarget.value.id,
}); });
if (code === 200) { if (code === 200) {
emit('updateComment'); emit('updateComment');
// comment.value = ''; comment.value = '';
textAreaRef.value.focus();
} }
}; };
@ -153,6 +125,7 @@ export default {
)} )}
<Textarea <Textarea
ref={textAreaRef}
auto-size auto-size
class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`} class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`}
size="large" size="large"
@ -179,49 +152,109 @@ export default {
const renderCommentBox = () => { const renderCommentBox = () => {
return ( return (
<div class="comment-box"> <div class="comment-box">
<p class="my-16px"> <p class="mb-16px">
<span class="cts bold cm !text-16px !lh-24px mr-8px">评论</span> <span class="cts bold cm !text-16px !lh-24px mr-8px">评论</span>
{props.dataSource.comments?.length > 0 && ( {props.dataSource.comments?.length > 0 && (
<span class="cts !text-16px !lh-24px bold">{props.dataSource.comments?.length}</span> <span class="cts !text-16px !lh-24px bold">{props.dataSource.comments?.length}</span>
)} )}
</p> </p>
<div class="comment-list flex flex-col my-16px rounded-8px"> {props.dataSource.comments?.length > 0 && (
{props.dataSource.comments?.map((item) => ( <div class="comment-list flex flex-col my-16px rounded-8px">
<div class="comment-item flex px-12px py-8px group" key={item.id}> {props.dataSource.comments?.map((item) => (
<Image <div class="comment-item flex px-12px py-8px group" key={item.id}>
src={item.commenter_id === 0 ? icon2 : item.commenter?.head_image} <Image
width={40} src={item.commenter_id === 0 ? icon2 : item.commenter?.head_image}
height={40} width={40}
preview={false} height={40}
fit="cover" preview={false}
class="rounded-50% mr-13px" fit="cover"
v-slots={{ class="rounded-50% mr-13px"
error: () => <img src={icon3} class="w-40 h-40 rounded-50%" />, v-slots={{
}} error: () => <img src={icon3} class="w-40 h-40 rounded-50%" />,
/> }}
/>
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1 overflow-hidden">
<div class="flex justify-between"> <div class="flex justify-between">
<p class="mb-4px"> <p class="mb-4px">
<span class="cts !color-#211F24 mr-8px">{getCommentName(item)}</span> <span class="cts !color-#211F24 mr-8px">{getCommentName(item)}</span>
<span class="cts !color-#939499">{formatRelativeTime(item.created_at)}</span> <span class="cts !color-#939499">{formatRelativeTime(item.created_at)}</span>
</p> </p>
<div class="items-center hidden group-hover:flex"> <div class="items-center hidden group-hover:flex">
<SvgIcon <SvgIcon
onClick={() => onReplay(item)} onClick={() => onReplay(item)}
name="svg-comment" name="svg-comment"
size={16} size={16}
class="color-#55585F cursor-pointer hover:color-#6D4CFE" class="color-#55585F cursor-pointer hover:color-#6D4CFE"
/> />
{renderDeleteBtn(item)} {renderDeleteBtn(item)}
</div>
</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> </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>
))} )}
</div> </div>
</div> </>
); );
}; };
@ -245,81 +278,23 @@ export default {
return () => ( return () => (
<section class="py-16px absolute right-16px w-440px h-full overflow-hidden"> <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="ai-suggest-box relative py-24px flex flex-col">
<div class="mb-16px w-full flex justify-between px-24px"> {!isEmpty(aiReview.value) && (
<div class="relative w-fit"> <div class="relative w-fit ml-24px mb-16px">
<span class="ai-text">AI 智能审核</span> <span class="ai-text">AI 智能审核</span>
<img src={icon1} class="w-80px h-10.8px absolute bottom-1px left--9px" /> <img src={icon1} class="w-80px h-10.8px absolute bottom-1px left--9px" />
</div> </div>
<icon-menu-unfold )}
size={20}
class="color-#55585F cursor-pointer hover:color-#6D4CFE" <icon-menu-unfold
onClick={() => emit('toggle', false)} size={20}
/> class="color-#55585F cursor-pointer hover:color-#6D4CFE absolute top-24px right-24px"
</div> onClick={() => emit('toggle', false)}
/>
{/**主体 */} {/**主体 */}
<div class="flex-1 overflow-y-auto px-24px"> <div class="flex-1 overflow-y-auto px-24px">
<div class="result-box p-16px rounded-8px"> {/* AI审核结果 */}
<div class="flex items-center justify-between mb-16px"> {renderAiSuggest()}
<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>
{/* 评论与回复 */} {/* 评论与回复 */}
{renderCommentBox()} {renderCommentBox()}
</div> </div>

View File

@ -39,6 +39,9 @@
&:hover { &:hover {
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
&.arco-textarea-focus {
border-color: #6d4cfe !important;
}
} }
.result-box { .result-box {
background: rgba(255, 255, 255, 0.8); 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 = { export const ENUM_OPINION = {
wait: 0, // 待确认 wait: 0, // 待确认
confirm: 1, // 已确认 confirm: 1, // 已确认

View File

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

View File

@ -39,12 +39,7 @@
</div> </div>
</template> </template>
<template v-if="column.dataIndex === 'compliance_degree'" #cell="{ record }"> <template v-if="column.dataIndex === 'customer_opinion'" #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 }">
<p <p
class="h-28px px-8px flex items-center rounded-2px w-fit" class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }" :style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
@ -55,7 +50,11 @@
</p> </p>
</template> </template>
<template v-else-if="column.dataIndex === 'platform'" #cell="{ record }"> <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>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'title'" #cell="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" /> <TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />

View File

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

View File

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

View File

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