feat: 审核列表抽屉
This commit is contained in:
@ -0,0 +1,78 @@
|
||||
<script lang="jsx">
|
||||
import { Drawer, Image } from '@arco-design/web-vue';
|
||||
import TextOverTips from '@/components/text-over-tips';
|
||||
|
||||
import icon1 from '@/assets/img/error-img.png';
|
||||
|
||||
export default {
|
||||
setup(props, { emit, expose }) {
|
||||
const visible = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const selectCardInfo = ref({});
|
||||
|
||||
const open = (data, _selectCardInfo) => {
|
||||
dataSource.value = data;
|
||||
selectCardInfo.value = _selectCardInfo;
|
||||
visible.value = true;
|
||||
};
|
||||
const onClose = () => {
|
||||
dataSource.value = [];
|
||||
selectCardInfo.value = {};
|
||||
visible.value = false;
|
||||
};
|
||||
expose({
|
||||
open,
|
||||
});
|
||||
|
||||
return () => (
|
||||
<Drawer
|
||||
title="审核列表"
|
||||
visible={visible.value}
|
||||
width={420}
|
||||
class="check-list-drawer-xt"
|
||||
footer={false}
|
||||
header={false}
|
||||
>
|
||||
<div class="flex justify-between items-center h-56px px-24px">
|
||||
<div class="flex items-center">
|
||||
<div class="w-3px h-16px rounded-2px bg-#6D4CFE mr-8px"></div>
|
||||
<span class="mr-8px cts bold">批量审核列表</span>
|
||||
<span class="mr-8px cts !lh-22px">{`共${dataSource.value.length}个`}</span>
|
||||
</div>
|
||||
<icon-menu-unfold size={16} class="color-##55585F cursor-pointer" onClick={onClose} />
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto px-24px">
|
||||
{dataSource.value.map((item) => (
|
||||
<div
|
||||
class={`card-item flex rounded-8px bg-#F7F8FA p-8px ${
|
||||
selectCardInfo.value.id === item.id ? 'active' : ''
|
||||
}`}
|
||||
key={item.id}
|
||||
>
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
preview={false}
|
||||
src={item.cover}
|
||||
class="!rounded-4px mr-8px"
|
||||
fit="cover"
|
||||
v-slots={{
|
||||
error: () => <img src={icon1} class="w-full h-full" />,
|
||||
}}
|
||||
/>
|
||||
<div class="flex-1 overflow-hidden flex flex-col items-start">
|
||||
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px`} />
|
||||
<p class="cts">{`合规程度:${90}%`}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss';
|
||||
</style>
|
||||
@ -0,0 +1,31 @@
|
||||
.check-list-drawer-xt {
|
||||
.arco-drawer-body {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 0 24px;
|
||||
.cts {
|
||||
color: var(--Text-1, #939499);
|
||||
|
||||
font-family: $font-family-regular;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
&.bold {
|
||||
color: var(--Text-1, #211f24);
|
||||
font-family: $font-family-medium;
|
||||
}
|
||||
}
|
||||
.card-item {
|
||||
border: 1px solid transparent;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
&.active {
|
||||
border-color: #6d4cfe;
|
||||
background-color: #f0edff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,22 +43,19 @@ export default {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
checkLoading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'filesChange', 'againCheck', 'imageClick'],
|
||||
emits: ['update:modelValue', 'filesChange', 'selectImage'],
|
||||
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 checkLoading = ref(false);
|
||||
|
||||
const isTextTab = computed(() => activeTab.value === enumTab.TEXT);
|
||||
|
||||
const onAiCheck = () => {
|
||||
const onAiReplace = () => {
|
||||
if (aiCheckLoading.value) return;
|
||||
|
||||
aiCheckLoading.value = true;
|
||||
@ -66,16 +63,16 @@ export default {
|
||||
aiCheckLoading.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
const onAgainCheck = () => {
|
||||
if (isTextTab.value) {
|
||||
emit('againCheck');
|
||||
} else {
|
||||
if (!props.modelValue.files?.length) {
|
||||
AMessage.warning('请先上传需审核图片');
|
||||
return;
|
||||
}
|
||||
emit('againCheck');
|
||||
|
||||
const onCheck = () => {
|
||||
if (!isTextTab.value && !props.modelValue.files?.length) {
|
||||
AMessage.warning('请先上传需审核图片');
|
||||
return;
|
||||
}
|
||||
checkLoading.value = true;
|
||||
setTimeout(() => {
|
||||
checkLoading.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
const onReplaceImage = () => {
|
||||
uploadRef.value?.upload?.();
|
||||
@ -94,26 +91,28 @@ export default {
|
||||
});
|
||||
});
|
||||
};
|
||||
const resetForm = () => {
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields?.();
|
||||
formRef.value?.clearValidate?.();
|
||||
aiCheckLoading.value = false;
|
||||
checkLoading.value = false;
|
||||
};
|
||||
const getFileExtension = (filename) => {
|
||||
const match = filename.match(/\.([^.]+)$/);
|
||||
return match ? match[1].toLowerCase() : '';
|
||||
};
|
||||
const handleSelectImage = (item) => {
|
||||
emit('imageClick', item);
|
||||
emit('selectImage', item);
|
||||
};
|
||||
|
||||
const renderUpload = (UploadBtn) => {
|
||||
const renderUpload = (UploadBtn, action = 'upload') => {
|
||||
return (
|
||||
<Upload
|
||||
ref={uploadRef}
|
||||
action="/"
|
||||
draggable
|
||||
class="w-fit"
|
||||
custom-request={uploadImage}
|
||||
custom-request={(option) => uploadImage(option, action)}
|
||||
accept=".jpg,.jpeg,.png,.gif,.webp"
|
||||
show-file-list={false}
|
||||
multiple
|
||||
@ -125,16 +124,11 @@ export default {
|
||||
);
|
||||
};
|
||||
|
||||
const uploadImage = async (option) => {
|
||||
const uploadImage = async (option, action = 'upload') => {
|
||||
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;
|
||||
@ -144,25 +138,34 @@ export default {
|
||||
headers: { 'Content-Type': type },
|
||||
});
|
||||
|
||||
const _files = [
|
||||
...props.modelValue.files,
|
||||
{
|
||||
url: file_url,
|
||||
name: file_name,
|
||||
size,
|
||||
},
|
||||
];
|
||||
emits('filesChange', _files);
|
||||
const _file = {
|
||||
url: file_url,
|
||||
name: file_name,
|
||||
size,
|
||||
};
|
||||
|
||||
const _newFiles =
|
||||
action === 'replaceImage'
|
||||
? props.modelValue.files.map((item) => {
|
||||
if (item.url === props.selectedImageInfo.url) {
|
||||
return _file;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
: [...props.modelValue.files, _file];
|
||||
|
||||
emit('filesChange', _newFiles);
|
||||
emit('selectImage', _file);
|
||||
};
|
||||
|
||||
const renderFooterRow = () => {
|
||||
return (
|
||||
<>
|
||||
<Button class="mr-12px" size="medium" onClick={onAgainCheck}>
|
||||
<Button class="mr-12px" size="medium" onClick={onCheck}>
|
||||
再次审核
|
||||
</Button>
|
||||
{isTextTab.value ? (
|
||||
<Button size="medium" type="outline" class="w-123px" onClick={onAiCheck}>
|
||||
<Button size="medium" type="outline" class="w-123px" onClick={onAiReplace}>
|
||||
{aiCheckLoading.value ? (
|
||||
<>
|
||||
<IconLoading size={14} />
|
||||
@ -181,6 +184,7 @@ export default {
|
||||
<Button size="medium" type="outline">
|
||||
图片替换
|
||||
</Button>,
|
||||
'replaceImage',
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -197,7 +201,7 @@ export default {
|
||||
size="large"
|
||||
maxLength={30}
|
||||
show-word-limit
|
||||
disabled={props.checkLoading}
|
||||
disabled={checkLoading.value}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="作品描述" field="content" class="flex-1 content-form-item">
|
||||
@ -208,7 +212,7 @@ export default {
|
||||
show-word-limit
|
||||
maxLength={1000}
|
||||
show-word-limit
|
||||
disabled={props.checkLoading}
|
||||
disabled={checkLoading.value}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
@ -223,6 +227,7 @@ export default {
|
||||
src={props.selectedImageInfo.url}
|
||||
width={370}
|
||||
height={370}
|
||||
preview={false}
|
||||
class="flex items-center justify-center mb-16px"
|
||||
fit="contain"
|
||||
v-slots={{
|
||||
@ -297,42 +302,42 @@ export default {
|
||||
};
|
||||
|
||||
const renderRightBox = () => {
|
||||
if (isEmpty(props.checkResult)) {
|
||||
return (
|
||||
<div class="right-box flex-1 h-372px rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto">
|
||||
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
|
||||
<div class="flex items-center mb-16px">
|
||||
{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 class="mb-16px suggestion-box p-12px rounded-8px bg-#F7F8FA flex flex-col">
|
||||
<div class=" mb-24px relative w-fit">
|
||||
<span class="ai-text">AI 审核建议</span>
|
||||
<img src={icon2} class="w-80px h-10.8px absolute bottom-1px left-1px" />
|
||||
</div>
|
||||
<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">
|
||||
{isTextTab.value ? '恭喜,您的文案中没有检测出违禁词' : '恭喜,您的图片中没有检测出违禁内容'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.checkLoading) {
|
||||
if (checkLoading.value) {
|
||||
return (
|
||||
<div class="right-box flex-1 h-210px rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto">
|
||||
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
|
||||
<Spin loading={true} tip="文本检测中" size={72} class="" />
|
||||
<Spin loading={true} tip={`${isTextTab.value ? '文本' : '图片'}检测中`} size={72} class="" />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
if (isEmpty(props.checkResult)) {
|
||||
return (
|
||||
<div class="right-box flex-1 h-372px rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto">
|
||||
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
|
||||
<div class="flex items-center mb-16px">
|
||||
{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 class="mb-16px suggestion-box p-12px rounded-8px bg-#F7F8FA flex flex-col">
|
||||
<div class=" mb-24px relative w-fit">
|
||||
<span class="ai-text">AI 审核建议</span>
|
||||
<img src={icon2} class="w-80px h-10.8px absolute bottom-1px left-1px" />
|
||||
</div>
|
||||
<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">
|
||||
{isTextTab.value ? '恭喜,您的文案中没有检测出违禁词' : '恭喜,您的图片中没有检测出违禁内容'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="right-box flex-1 rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto">
|
||||
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
|
||||
@ -377,7 +382,7 @@ export default {
|
||||
|
||||
expose({
|
||||
validate,
|
||||
resetForm,
|
||||
reset,
|
||||
});
|
||||
|
||||
const descs = [
|
||||
|
||||
@ -59,6 +59,7 @@ export default {
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
preview={false}
|
||||
src={item.cover}
|
||||
class="!rounded-4px mr-8px"
|
||||
fit="cover"
|
||||
|
||||
@ -4,6 +4,7 @@ import CancelCheckModal from './cancel-check-modal.vue';
|
||||
import CheckSuccessModal from './check-success-modal.vue';
|
||||
import HeaderCard from './components/header-card';
|
||||
import ContentCard from './components/content-card';
|
||||
import CheckListDrawer from './components/check-list-drawer';
|
||||
|
||||
import { slsWithCatch, rlsWithCatch, glsWithCatch } from '@/utils/stroage.ts';
|
||||
import {
|
||||
@ -28,7 +29,7 @@ export default {
|
||||
const checkSuccessModalRef = ref(null);
|
||||
const submitLoading = ref(false);
|
||||
const contentCardRef = ref(null);
|
||||
const checkLoading = ref(false);
|
||||
const checkListDrawerRef = ref(null);
|
||||
const checkResult = ref({});
|
||||
|
||||
const selectCardInfo = ref({});
|
||||
@ -39,10 +40,9 @@ export default {
|
||||
};
|
||||
|
||||
const onChangeCard = (item) => {
|
||||
contentCardRef.value.resetForm();
|
||||
contentCardRef.value.reset();
|
||||
isSaved.value = false;
|
||||
submitLoading.value = false;
|
||||
checkLoading.value = false;
|
||||
checkResult.value = {};
|
||||
selectCardInfo.value = cloneDeep(item);
|
||||
selectedImageInfo.value = cloneDeep(item.files?.[0] ?? {});
|
||||
@ -57,7 +57,7 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const onImageClick = (item) => {
|
||||
const onSelectImage = (item) => {
|
||||
selectedImageInfo.value = cloneDeep(item);
|
||||
};
|
||||
|
||||
@ -65,6 +65,7 @@ export default {
|
||||
const { code, data } = await getWorkAuditsBatchDetail({ ids: workIds.value });
|
||||
if (code === 200) {
|
||||
dataSource.value = data ?? [];
|
||||
remoteDataSource.value = cloneDeep(data ?? []);
|
||||
selectCardInfo.value = cloneDeep(data?.[0] ?? {});
|
||||
selectedImageInfo.value = data?.[0].files?.[0] ?? {};
|
||||
}
|
||||
@ -72,9 +73,8 @@ export default {
|
||||
|
||||
const isSelectCardModified = () => {
|
||||
return new Promise((resolve) => {
|
||||
const _a = dataSource.value.find((item) => item.id === selectCardInfo.value);
|
||||
const _b = remoteDataSource.value.find((item) => item.id === selectCardInfo.value);
|
||||
resolve(!isEqual(_a, _b) && !isSaved.value);
|
||||
const _item = remoteDataSource.value.find((item) => item.id === selectCardInfo.value.id);
|
||||
resolve(!isEqual(selectCardInfo.value, _item) && !isSaved.value);
|
||||
});
|
||||
};
|
||||
|
||||
@ -87,6 +87,10 @@ export default {
|
||||
}
|
||||
};
|
||||
const onSave = async () => {
|
||||
if (!selectCardInfo.value.title) {
|
||||
AMessage.warning('标题不能为空');
|
||||
}
|
||||
|
||||
contentCardRef.value?.validate().then(async () => {
|
||||
const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value);
|
||||
if (code === 200) {
|
||||
@ -123,12 +127,6 @@ export default {
|
||||
const onFilesChange = (files) => {
|
||||
selectCardInfo.value.files = cloneDeep(files);
|
||||
};
|
||||
const onAgainCheck = () => {
|
||||
checkLoading.value = true;
|
||||
setTimeout(() => {
|
||||
checkLoading.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
const renderFooterRow = () => {
|
||||
return (
|
||||
<>
|
||||
@ -155,13 +153,23 @@ export default {
|
||||
return () => (
|
||||
<>
|
||||
<div class="manuscript-check-wrap flex flex-col">
|
||||
<div class="flex items-center mb-8px">
|
||||
<div class="flex items-center mb-10px">
|
||||
<span class="cts color-#4E5969 cursor-pointer" onClick={onExit}>
|
||||
内容稿件审核
|
||||
</span>
|
||||
<icon-oblique-line size="12" class="color-#C9CDD4 mx-4px" />
|
||||
<span class="cts bold !color-#1D2129">{`${workIds.value.length > 0 ? '批量' : ''}审核内容稿件`}</span>
|
||||
</div>
|
||||
{dataSource.value.length > 1 && (
|
||||
<div
|
||||
class="check-list-icon"
|
||||
onClick={() => checkListDrawerRef.value.open(dataSource.value, selectCardInfo.value)}
|
||||
>
|
||||
<icon-menu-fold size={16} class="color-#55585F mr-4px" />
|
||||
<span class="cts !color-#211F24">审核列表</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex-1 flex flex-col overflow-hidden bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid">
|
||||
<HeaderCard dataSource={dataSource.value} selectCardInfo={selectCardInfo.value} onCardClick={onCardClick} />
|
||||
<section class="flex-1 overflow-hidden">
|
||||
@ -171,10 +179,8 @@ export default {
|
||||
selectCardInfo={selectCardInfo.value}
|
||||
checkResult={checkResult.value}
|
||||
onFilesChange={onFilesChange}
|
||||
onAgainCheck={onAgainCheck}
|
||||
checkLoading={checkLoading.value}
|
||||
selectedImageInfo={selectedImageInfo.value}
|
||||
onImageClick={onImageClick}
|
||||
onSelectImage={onSelectImage}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
@ -185,6 +191,7 @@ export default {
|
||||
|
||||
<CancelCheckModal ref={cancelCheckModalRef} onSelectCard={onChangeCard} />
|
||||
<CheckSuccessModal ref={checkSuccessModalRef} />
|
||||
<CheckListDrawer ref={checkListDrawerRef} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
@ -13,6 +13,21 @@ $footer-height: 68px;
|
||||
font-family: $font-family-medium;
|
||||
}
|
||||
}
|
||||
.check-list-icon {
|
||||
// width: 92px;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
padding: 8px 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 30px 0 0 30px;
|
||||
border: 1px solid var(--Border-1, #d7d7d9);
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc($navbar-height + 8px);
|
||||
}
|
||||
}
|
||||
.footer-row {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user