feat: 审核列表抽屉

This commit is contained in:
rd
2025-08-07 10:49:17 +08:00
parent 91f5888fa7
commit a164ec9fec
6 changed files with 224 additions and 87 deletions

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -43,22 +43,19 @@ export default {
type: Object, type: Object,
default: {}, default: {},
}, },
checkLoading: {
type: Boolean,
default: false,
}, },
}, emits: ['update:modelValue', 'filesChange', 'selectImage'],
emits: ['update:modelValue', 'filesChange', 'againCheck', 'imageClick'],
setup(props, { emit, expose }) { setup(props, { emit, expose }) {
const activeTab = ref(enumTab.TEXT); const activeTab = ref(enumTab.TEXT);
const aiCheckLoading = ref(false); const aiCheckLoading = ref(false);
const formRef = ref(null); const formRef = ref(null);
const uploadRef = ref(null); const uploadRef = ref(null);
const modules = [Navigation]; const modules = [Navigation];
const checkLoading = ref(false);
const isTextTab = computed(() => activeTab.value === enumTab.TEXT); const isTextTab = computed(() => activeTab.value === enumTab.TEXT);
const onAiCheck = () => { const onAiReplace = () => {
if (aiCheckLoading.value) return; if (aiCheckLoading.value) return;
aiCheckLoading.value = true; aiCheckLoading.value = true;
@ -66,16 +63,16 @@ export default {
aiCheckLoading.value = false; aiCheckLoading.value = false;
}, 2000); }, 2000);
}; };
const onAgainCheck = () => {
if (isTextTab.value) { const onCheck = () => {
emit('againCheck'); if (!isTextTab.value && !props.modelValue.files?.length) {
} else {
if (!props.modelValue.files?.length) {
AMessage.warning('请先上传需审核图片'); AMessage.warning('请先上传需审核图片');
return; return;
} }
emit('againCheck'); checkLoading.value = true;
} setTimeout(() => {
checkLoading.value = false;
}, 2000);
}; };
const onReplaceImage = () => { const onReplaceImage = () => {
uploadRef.value?.upload?.(); uploadRef.value?.upload?.();
@ -94,26 +91,28 @@ export default {
}); });
}); });
}; };
const resetForm = () => { const reset = () => {
formRef.value?.resetFields?.(); formRef.value?.resetFields?.();
formRef.value?.clearValidate?.(); formRef.value?.clearValidate?.();
aiCheckLoading.value = false;
checkLoading.value = false;
}; };
const getFileExtension = (filename) => { const getFileExtension = (filename) => {
const match = filename.match(/\.([^.]+)$/); const match = filename.match(/\.([^.]+)$/);
return match ? match[1].toLowerCase() : ''; return match ? match[1].toLowerCase() : '';
}; };
const handleSelectImage = (item) => { const handleSelectImage = (item) => {
emit('imageClick', item); emit('selectImage', item);
}; };
const renderUpload = (UploadBtn) => { const renderUpload = (UploadBtn, action = 'upload') => {
return ( return (
<Upload <Upload
ref={uploadRef} ref={uploadRef}
action="/" action="/"
draggable draggable
class="w-fit" class="w-fit"
custom-request={uploadImage} custom-request={(option) => uploadImage(option, action)}
accept=".jpg,.jpeg,.png,.gif,.webp" accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false} show-file-list={false}
multiple multiple
@ -125,16 +124,11 @@ export default {
); );
}; };
const uploadImage = async (option) => { const uploadImage = async (option, action = 'upload') => {
const { const {
fileItem: { file }, fileItem: { file },
} = option; } = option;
// 验证文件数量
if (formData.value.files?.length >= 18) {
AMessage.error('最多只能上传18张图片');
return;
}
const { name, size, type } = file; const { name, size, type } = file;
const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) }); const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) });
const { file_name, upload_url, file_url } = response?.data; const { file_name, upload_url, file_url } = response?.data;
@ -144,25 +138,34 @@ export default {
headers: { 'Content-Type': type }, headers: { 'Content-Type': type },
}); });
const _files = [ const _file = {
...props.modelValue.files,
{
url: file_url, url: file_url,
name: file_name, name: file_name,
size, size,
}, };
];
emits('filesChange', _files); 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 = () => { const renderFooterRow = () => {
return ( return (
<> <>
<Button class="mr-12px" size="medium" onClick={onAgainCheck}> <Button class="mr-12px" size="medium" onClick={onCheck}>
再次审核 再次审核
</Button> </Button>
{isTextTab.value ? ( {isTextTab.value ? (
<Button size="medium" type="outline" class="w-123px" onClick={onAiCheck}> <Button size="medium" type="outline" class="w-123px" onClick={onAiReplace}>
{aiCheckLoading.value ? ( {aiCheckLoading.value ? (
<> <>
<IconLoading size={14} /> <IconLoading size={14} />
@ -181,6 +184,7 @@ export default {
<Button size="medium" type="outline"> <Button size="medium" type="outline">
图片替换 图片替换
</Button>, </Button>,
'replaceImage',
)} )}
</div> </div>
)} )}
@ -197,7 +201,7 @@ export default {
size="large" size="large"
maxLength={30} maxLength={30}
show-word-limit show-word-limit
disabled={props.checkLoading} disabled={checkLoading.value}
/> />
</FormItem> </FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item"> <FormItem label="作品描述" field="content" class="flex-1 content-form-item">
@ -208,7 +212,7 @@ export default {
show-word-limit show-word-limit
maxLength={1000} maxLength={1000}
show-word-limit show-word-limit
disabled={props.checkLoading} disabled={checkLoading.value}
/> />
</FormItem> </FormItem>
</Form> </Form>
@ -223,6 +227,7 @@ export default {
src={props.selectedImageInfo.url} src={props.selectedImageInfo.url}
width={370} width={370}
height={370} height={370}
preview={false}
class="flex items-center justify-center mb-16px" class="flex items-center justify-center mb-16px"
fit="contain" fit="contain"
v-slots={{ v-slots={{
@ -297,6 +302,14 @@ export default {
}; };
const renderRightBox = () => { const renderRightBox = () => {
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={`${isTextTab.value ? '文本' : '图片'}检测中`} size={72} class="" />
</div>
);
} else {
if (isEmpty(props.checkResult)) { if (isEmpty(props.checkResult)) {
return ( 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"> <div class="right-box flex-1 h-372px rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto">
@ -325,14 +338,6 @@ export default {
); );
} }
if (props.checkLoading) {
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="" />
</div>
);
} else {
return ( return (
<div class="right-box flex-1 rounded-8px border-1px border-#E6E6E8 border-solid p-16px flex flex-col overflow-y-auto"> <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> <p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
@ -377,7 +382,7 @@ export default {
expose({ expose({
validate, validate,
resetForm, reset,
}); });
const descs = [ const descs = [

View File

@ -59,6 +59,7 @@ export default {
<Image <Image
width={48} width={48}
height={48} height={48}
preview={false}
src={item.cover} src={item.cover}
class="!rounded-4px mr-8px" class="!rounded-4px mr-8px"
fit="cover" fit="cover"

View File

@ -4,6 +4,7 @@ import CancelCheckModal from './cancel-check-modal.vue';
import CheckSuccessModal from './check-success-modal.vue'; import CheckSuccessModal from './check-success-modal.vue';
import HeaderCard from './components/header-card'; import HeaderCard from './components/header-card';
import ContentCard from './components/content-card'; import ContentCard from './components/content-card';
import CheckListDrawer from './components/check-list-drawer';
import { slsWithCatch, rlsWithCatch, glsWithCatch } from '@/utils/stroage.ts'; import { slsWithCatch, rlsWithCatch, glsWithCatch } from '@/utils/stroage.ts';
import { import {
@ -28,7 +29,7 @@ export default {
const checkSuccessModalRef = ref(null); const checkSuccessModalRef = ref(null);
const submitLoading = ref(false); const submitLoading = ref(false);
const contentCardRef = ref(null); const contentCardRef = ref(null);
const checkLoading = ref(false); const checkListDrawerRef = ref(null);
const checkResult = ref({}); const checkResult = ref({});
const selectCardInfo = ref({}); const selectCardInfo = ref({});
@ -39,10 +40,9 @@ export default {
}; };
const onChangeCard = (item) => { const onChangeCard = (item) => {
contentCardRef.value.resetForm(); contentCardRef.value.reset();
isSaved.value = false; isSaved.value = false;
submitLoading.value = false; submitLoading.value = false;
checkLoading.value = false;
checkResult.value = {}; checkResult.value = {};
selectCardInfo.value = cloneDeep(item); selectCardInfo.value = cloneDeep(item);
selectedImageInfo.value = cloneDeep(item.files?.[0] ?? {}); selectedImageInfo.value = cloneDeep(item.files?.[0] ?? {});
@ -57,7 +57,7 @@ export default {
} }
}; };
const onImageClick = (item) => { const onSelectImage = (item) => {
selectedImageInfo.value = cloneDeep(item); selectedImageInfo.value = cloneDeep(item);
}; };
@ -65,6 +65,7 @@ export default {
const { code, data } = await getWorkAuditsBatchDetail({ ids: workIds.value }); const { code, data } = await getWorkAuditsBatchDetail({ ids: workIds.value });
if (code === 200) { if (code === 200) {
dataSource.value = data ?? []; dataSource.value = data ?? [];
remoteDataSource.value = cloneDeep(data ?? []);
selectCardInfo.value = cloneDeep(data?.[0] ?? {}); selectCardInfo.value = cloneDeep(data?.[0] ?? {});
selectedImageInfo.value = data?.[0].files?.[0] ?? {}; selectedImageInfo.value = data?.[0].files?.[0] ?? {};
} }
@ -72,9 +73,8 @@ export default {
const isSelectCardModified = () => { const isSelectCardModified = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
const _a = dataSource.value.find((item) => item.id === selectCardInfo.value); const _item = remoteDataSource.value.find((item) => item.id === selectCardInfo.value.id);
const _b = remoteDataSource.value.find((item) => item.id === selectCardInfo.value); resolve(!isEqual(selectCardInfo.value, _item) && !isSaved.value);
resolve(!isEqual(_a, _b) && !isSaved.value);
}); });
}; };
@ -87,6 +87,10 @@ export default {
} }
}; };
const onSave = async () => { const onSave = async () => {
if (!selectCardInfo.value.title) {
AMessage.warning('标题不能为空');
}
contentCardRef.value?.validate().then(async () => { contentCardRef.value?.validate().then(async () => {
const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value); const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value);
if (code === 200) { if (code === 200) {
@ -123,12 +127,6 @@ export default {
const onFilesChange = (files) => { const onFilesChange = (files) => {
selectCardInfo.value.files = cloneDeep(files); selectCardInfo.value.files = cloneDeep(files);
}; };
const onAgainCheck = () => {
checkLoading.value = true;
setTimeout(() => {
checkLoading.value = false;
}, 2000);
};
const renderFooterRow = () => { const renderFooterRow = () => {
return ( return (
<> <>
@ -155,13 +153,23 @@ export default {
return () => ( return () => (
<> <>
<div class="manuscript-check-wrap flex flex-col"> <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 class="cts color-#4E5969 cursor-pointer" onClick={onExit}>
内容稿件审核 内容稿件审核
</span> </span>
<icon-oblique-line size="12" class="color-#C9CDD4 mx-4px" /> <icon-oblique-line size="12" class="color-#C9CDD4 mx-4px" />
<span class="cts bold !color-#1D2129">{`${workIds.value.length > 0 ? '批量' : ''}审核内容稿件`}</span> <span class="cts bold !color-#1D2129">{`${workIds.value.length > 0 ? '批量' : ''}审核内容稿件`}</span>
</div> </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"> <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} /> <HeaderCard dataSource={dataSource.value} selectCardInfo={selectCardInfo.value} onCardClick={onCardClick} />
<section class="flex-1 overflow-hidden"> <section class="flex-1 overflow-hidden">
@ -171,10 +179,8 @@ export default {
selectCardInfo={selectCardInfo.value} selectCardInfo={selectCardInfo.value}
checkResult={checkResult.value} checkResult={checkResult.value}
onFilesChange={onFilesChange} onFilesChange={onFilesChange}
onAgainCheck={onAgainCheck}
checkLoading={checkLoading.value}
selectedImageInfo={selectedImageInfo.value} selectedImageInfo={selectedImageInfo.value}
onImageClick={onImageClick} onSelectImage={onSelectImage}
/> />
</section> </section>
</div> </div>
@ -185,6 +191,7 @@ export default {
<CancelCheckModal ref={cancelCheckModalRef} onSelectCard={onChangeCard} /> <CancelCheckModal ref={cancelCheckModalRef} onSelectCard={onChangeCard} />
<CheckSuccessModal ref={checkSuccessModalRef} /> <CheckSuccessModal ref={checkSuccessModalRef} />
<CheckListDrawer ref={checkListDrawerRef} />
</> </>
); );
}, },

View File

@ -13,6 +13,21 @@ $footer-height: 68px;
font-family: $font-family-medium; 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 { .footer-row {
position: fixed; position: fixed;