feat: 内容稿件审核

This commit is contained in:
rd
2025-08-06 18:13:17 +08:00
parent 86f01e7ea5
commit 3da0aef676
4 changed files with 268 additions and 44 deletions

View File

@ -1,7 +1,9 @@
<script lang="jsx">
import axios from 'axios';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { IconLoading } from '@arco-design/web-vue/es/icon';
import {
Image,
Form,
FormItem,
Input,
@ -15,11 +17,17 @@ import {
} from '@arco-design/web-vue';
import TextOverTips from '@/components/text-over-tips';
import 'swiper/css';
import 'swiper/css/navigation';
import { Navigation } from 'swiper/modules';
import { FORM_RULES, enumTab, TAB_LIST, RESULT_LIST } from './constants';
import { getImagePreSignedUrl } from '@/api/all/common';
import icon1 from '@/assets/img/creative-generation-workshop/icon-magic.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-line.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-success.png';
import icon4 from '@/assets/img/error-img.png';
import icon5 from '@/assets/img/creative-generation-workshop/icon-lf2.png';
export default {
props: {
@ -31,16 +39,24 @@ export default {
type: Object,
default: {},
},
selectedImageInfo: {
type: Object,
default: {},
},
checkLoading: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'filesChange', 'againCheck'],
emits: ['update:modelValue', 'filesChange', 'againCheck', 'imageClick'],
setup(props, { emit, expose }) {
const activeTab = ref(enumTab.TEXT);
const aiCheckLoading = ref(false);
const formRef = ref(null);
const uploadRef = ref(null);
const modules = [Navigation];
const isTextTab = computed(() => activeTab.value === enumTab.TEXT);
const onAiCheck = () => {
if (aiCheckLoading.value) return;
@ -51,7 +67,18 @@ export default {
}, 2000);
};
const onAgainCheck = () => {
emit('againCheck');
if (isTextTab.value) {
emit('againCheck');
} else {
if (!props.modelValue.files?.length) {
AMessage.warning('请先上传需审核图片');
return;
}
emit('againCheck');
}
};
const onReplaceImage = () => {
uploadRef.value?.upload?.();
};
const handleTabClick = (key) => {
activeTab.value = key;
@ -71,6 +98,62 @@ export default {
formRef.value?.resetFields?.();
formRef.value?.clearValidate?.();
};
const getFileExtension = (filename) => {
const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : '';
};
const handleSelectImage = (item) => {
emit('imageClick', item);
};
const renderUpload = (UploadBtn) => {
return (
<Upload
ref={uploadRef}
action="/"
draggable
class="w-fit"
custom-request={uploadImage}
accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false}
multiple
>
{{
'upload-button': () => <UploadBtn />,
}}
</Upload>
);
};
const uploadImage = async (option) => {
const {
fileItem: { file },
} = option;
// 验证文件数量
if (formData.value.files?.length >= 18) {
AMessage.error('最多只能上传18张图片');
return;
}
const { name, size, type } = file;
const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) });
const { file_name, upload_url, file_url } = response?.data;
const blob = new Blob([file], { type });
await axios.put(upload_url, blob, {
headers: { 'Content-Type': type },
});
const _files = [
...props.modelValue.files,
{
url: file_url,
name: file_name,
size,
},
];
emits('filesChange', _files);
};
const renderFooterRow = () => {
return (
@ -78,7 +161,7 @@ export default {
<Button class="mr-12px" size="medium" onClick={onAgainCheck}>
再次审核
</Button>
{activeTab.value === enumTab.TEXT ? (
{isTextTab.value ? (
<Button size="medium" type="outline" class="w-123px" onClick={onAiCheck}>
{aiCheckLoading.value ? (
<>
@ -93,39 +176,122 @@ export default {
)}
</Button>
) : (
<Button size="medium" type="outline">
图片替换
</Button>
<div class="w-88px">
{renderUpload(
<Button size="medium" type="outline">
图片替换
</Button>,
)}
</div>
)}
</>
);
};
const renderLeftBoxContent = () => {
if (activeTab.value === enumTab.TEXT) {
const renderTextForm = () => {
return (
<Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<Input
v-model={props.modelValue.title}
placeholder="请输入标题"
size="large"
maxLength={30}
show-word-limit
disabled={props.checkLoading}
/>
</FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item">
<Textarea
v-model={props.modelValue.content}
placeholder="请输入作品描述"
size="large"
show-word-limit
maxLength={1000}
show-word-limit
disabled={props.checkLoading}
/>
</FormItem>
</Form>
);
};
const renderImageForm = () => {
if (props.modelValue.files?.length > 0) {
return (
<Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required>
<Input
v-model={props.modelValue.title}
placeholder="请输入标题"
size="large"
maxLength={30}
show-word-limit
disabled={props.checkLoading}
<div class="w-full h-full py-16px flex justify-center">
<div class="w-380px flex flex-col justify-center">
<Image
src={props.selectedImageInfo.url}
width={370}
height={370}
class="flex items-center justify-center mb-16px"
fit="contain"
v-slots={{
error: () => <img src={icon4} class="w-full h-full" />,
}}
/>
</FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item">
<Textarea
v-model={props.modelValue.content}
placeholder="请输入作品描述"
size="large"
show-word-limit
maxLength={1000}
show-word-limit
disabled={props.checkLoading}
/>
</FormItem>
</Form>
<div class="swiper-wrap h-60px">
<Swiper
spaceBetween={16}
modules={modules}
slidesPerView="auto"
navigation={{
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}}
>
{props.modelValue.files.map((item) => (
<SwiperSlide
key={item.id}
onClick={() => handleSelectImage(item)}
class={`swiper-item !h-48px !w-48px bg-#F7F8FA cursor-pointer rounded-4px overflow-hidden !flex items-center ${
item.id === props.selectedImageInfo.id ? 'active' : ''
}`}
>
<div class="group relative">
<Image
width={48}
height={48}
src={item.url}
class="!rounded-4px"
fit="cover"
preview={false}
v-slots={{
error: () => <img src={icon4} class="w-full h-full" />,
}}
/>
<icon-close-circle-fill
size={16}
class="absolute top--8px right--8px hidden cursor-pointer color-#939499 group-hover:block z-50"
/>
</div>
</SwiperSlide>
))}
<div class="swiper-box swiper-button-prev">
<img src={icon5} class="w-8px h-17px" />
</div>
<div class="swiper-box swiper-button-next">
<img src={icon5} class="w-8px h-17px rotate-180" />
</div>
</Swiper>
</div>
</div>
</div>
);
} else {
return (
<div class="w-full h-full flex flex-col items-center justify-center">
<div class="flex justify-center mb-16px">
{renderUpload(
<div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span>
</div>,
)}
</div>
<span class="cts">上传要审核的图片素材</span>
</div>
);
}
};
@ -151,9 +317,7 @@ export default {
<div class="flex flex-col items-center h-138px justify-center">
<img src={icon3} width={72} height={72} class="mb-12px" />
<span class="cts !color-#25C883">
{
activeTab.value === enumTab.TEXT ? '恭喜,您的文案中没有检测出违禁词' : '恭喜,您的图片中没有检测出违禁内容'
}
{isTextTab.value ? '恭喜,您的文案中没有检测出违禁词' : '恭喜,您的图片中没有检测出违禁内容'}
</span>
</div>
</div>
@ -256,7 +420,7 @@ export default {
<TabPane key={item.value} title={item.label}></TabPane>
))}
</Tabs>
<div class="flex-1 px-16px">{renderLeftBoxContent()}</div>
<div class="flex-1 px-16px">{isTextTab.value ? renderTextForm() : renderImageForm()}</div>
</div>
<div class="flex items-center justify-end">{renderFooterRow()}</div>
</div>

View File

@ -21,7 +21,7 @@
.arco-tabs-nav {
.arco-tabs-tab {
height: 40px;
// padding: 0 8px;
// padding: 0 8px;
margin: 0 16px;
}
&::before {
@ -58,6 +58,62 @@
}
}
}
.upload-box {
display: flex;
width: 100px;
height: 100px;
cursor: pointer;
transition: all 0.3s ease;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 8px;
border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5);
&:hover {
background: var(--Primary-1, #e6e6e8);
}
}
.swiper-wrap {
.swiper-item {
transition: all;
&.active {
width: 60px;
height: 60px;
border: 2px solid var(--Brand-6, #6d4cfe);
background: url(<path-to-image>) lightgray 50% / cover no-repeat;
}
}
.swiper-box {
position: absolute;
margin-top: 0 !important;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.4);
transition: all;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
transform: translateY(-50%);
&:hover {
background: rgba(0, 0, 0, 0.6);
}
&.swiper-button-prev {
left: 16px;
}
&.swiper-button-next {
right: 16px;
}
&::after {
display: none;
}
&.swiper-button-disabled {
display: none;
}
}
}
}
.right-box {
.s1 {

View File

@ -21,7 +21,6 @@ export default {
const route = useRoute();
const workIds = ref([]);
const selectCardInfo = ref({});
const isSaved = ref(false);
const dataSource = ref([]);
const remoteDataSource = ref([]);
@ -32,6 +31,9 @@ export default {
const checkLoading = ref(false);
const checkResult = ref({});
const selectCardInfo = ref({});
const selectedImageInfo = ref(null);
const onBack = () => {
router.push({ name: 'ManuscriptCheckList' });
};
@ -43,6 +45,7 @@ export default {
checkLoading.value = false;
checkResult.value = {};
selectCardInfo.value = cloneDeep(item);
selectedImageInfo.value = cloneDeep(item.files?.[0] ?? {});
};
const onCardClick = async (item) => {
@ -54,11 +57,16 @@ export default {
}
};
const onImageClick = (item) => {
selectedImageInfo.value = cloneDeep(item);
};
const getWorkAudits = async () => {
const { code, data } = await getWorkAuditsBatchDetail({ ids: workIds.value });
if (code === 200) {
dataSource.value = data ?? [];
selectCardInfo.value = cloneDeep(data?.[0] ?? {});
selectedImageInfo.value = data?.[0].files?.[0] ?? {};
}
};
@ -96,12 +104,7 @@ export default {
dataSource.value = dataSource.value.filter((v) => v.id != _id);
slsWithCatch('manuscriptCheckIds', workIds.value.join(','));
if (dataSource.value.length) {
const _target = dataSource.value[0] ?? {};
selectCardInfo.value = cloneDeep(_target);
} else {
selectCardInfo.value = {};
}
onChangeCard(dataSource.value.length ? dataSource.value[0] : {});
}
};
const onSubmit = async () => {
@ -142,8 +145,7 @@ export default {
);
};
onMounted(() => {
const manuscriptCheckIds = glsWithCatch('manuscriptCheckIds');
workIds.value = manuscriptCheckIds.split(',');
workIds.value = glsWithCatch('manuscriptCheckIds')?.split(',') ?? [];
getWorkAudits();
});
onUnmounted(() => {
@ -171,6 +173,8 @@ export default {
onFilesChange={onFilesChange}
onAgainCheck={onAgainCheck}
checkLoading={checkLoading.value}
selectedImageInfo={selectedImageInfo.value}
onImageClick={onImageClick}
/>
</section>
</div>