feat: 稿件详情

This commit is contained in:
rd
2025-07-31 16:22:37 +08:00
parent 9668f2a56d
commit ed1050313c
8 changed files with 163 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -63,6 +63,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
locale: '稿件详情', locale: '稿件详情',
requiresAuth: false, requiresAuth: false,
requireLogin: false, requireLogin: false,
hideFooter: true,
roles: ['*'], roles: ['*'],
hideInMenu: true, hideInMenu: true,
activeMenu: 'ManuscriptList', activeMenu: 'ManuscriptList',
@ -84,9 +85,10 @@ const COMPONENTS: AppRouteRecordRaw[] = [
path: 'check/:id', path: 'check/:id',
name: 'ManuscriptCheck', name: 'ManuscriptCheck',
meta: { meta: {
locale: '内容稿件审核', locale: '稿件审核',
requiresAuth: false, requiresAuth: false,
requireLogin: false, requireLogin: false,
hideFooter: true,
roles: ['*'], roles: ['*'],
hideInMenu: true, hideInMenu: true,
activeMenu: 'ManuscriptCheckList', activeMenu: 'ManuscriptCheckList',

View File

@ -1,5 +1,8 @@
<script lang="jsx"> <script lang="jsx">
import { Button, Message as AMessage } from '@arco-design/web-vue'; import { Button, Message as AMessage } from '@arco-design/web-vue';
import icon1 from '@/assets/img/creative-generation-workshop/icon-play.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-play-hover.png';
import { useRouter, useRoute } from 'vue-router';
export default { export default {
setup(props, { emit, expose }) { setup(props, { emit, expose }) {
@ -7,9 +10,61 @@ export default {
const route = useRoute(); const route = useRoute();
const workId = ref(route.params.id); const workId = ref(route.params.id);
// 视频播放相关状态
const isPlaying = ref(false);
const isHovered = ref(false);
const videoRef = ref(null);
const videoUrl = ref(''); // 视频URL实际项目中可能从API获取
const coverImageUrl = ref(''); // 封面图URL实际项目中可能从API获取
const isVideoLoaded = ref(false); // 视频是否已加载
const onBack = () => { const onBack = () => {
router.push({ name: 'ManuscriptList' }); router.push({ name: 'ManuscriptList' });
}; };
const togglePlay = () => {
if (!videoRef.value) return;
if (isPlaying.value) {
videoRef.value.pause();
} else {
// 首次播放时才加载视频
if (!isVideoLoaded.value) {
videoRef.value.src = videoUrl.value;
isVideoLoaded.value = true;
}
videoRef.value.play();
}
isPlaying.value = !isPlaying.value;
};
const onVideoEnded = () => {
isPlaying.value = false;
};
const onMouseEnter = () => {
isHovered.value = true;
};
const onMouseLeave = () => {
isHovered.value = false;
};
let containerRef = null;
onMounted(() => {
containerRef = document.querySelector('.main-img-box');
if (containerRef) {
containerRef.addEventListener('mouseenter', onMouseEnter);
containerRef.addEventListener('mouseleave', onMouseLeave);
}
});
onUnmounted(() => {
if (containerRef) {
containerRef.removeEventListener('mouseenter', onMouseEnter);
containerRef.removeEventListener('mouseleave', onMouseLeave);
containerRef = null;
}
});
const renderFooterRow = () => { const renderFooterRow = () => {
return ( return (
<> <>
@ -30,9 +85,10 @@ export default {
</> </>
); );
}; };
return () => ( return () => (
<> <>
<div class="manuscript-check-wrap"> <div class="manuscript-detail-wrap">
<div class="flex items-center mb-8px"> <div class="flex items-center mb-8px">
<span class="cts color-#4E5969 cursor-pointer" onClick={onBack}> <span class="cts color-#4E5969 cursor-pointer" onClick={onBack}>
内容稿件列表 内容稿件列表
@ -40,6 +96,46 @@ export default {
<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">内容稿件详情</span> <span class="cts bold !color-#1D2129">内容稿件详情</span>
</div> </div>
<div class="flex-1 overflow-y-auto bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid py-32px">
<div class="w-684px mx-auto flex flex-col items-center">
<p class="mb-8px cts bold !text-28px !lh-40px !color-#211F24">
挖到宝了这个平价好物让我素颜出门都自信挖到宝了这个平价
</p>
<p class="cts !text-12px !color-#737478 mb-32px w-full text-left">6月26日修改</p>
<div class="main-img-box mb-16px relative overflow-hidden cursor-pointer" onClick={togglePlay}>
<video ref={videoRef} class="w-100% h-100% object-cover" onEnded={onVideoEnded}></video>
<img
v-show={!isPlaying.value}
src={coverImageUrl.value}
class="w-100% h-100% object-cover absolute inset-0"
/>
<div v-show={!isPlaying.value} class="absolute inset-0 flex items-center justify-center">
<img src={isHovered.value ? icon2 : icon1} class="w-64px h-64px transition-all duration-300" />
</div>
</div>
<p class="cts !color-#211F24 mb-40px">
谁懂啊作为混干皮每天素颜出门总被说 气色好差直到被闺蜜按头安利这个 30 块的素颜霜
质地像冰淇淋一样推开就化完全不卡粉带一点自然提亮黄黑皮涂完像天生好皮肤连遮瑕都省了
最绝的是它还带轻微防晒值早上洗完脸涂一层就能冲出门懒人狂喜我已经空罐 3
瓶了现在同事都以为我每天早起化妆其实我多睡了 20 分钟 hhh
PS油痘肌姐妹建议局部薄涂后续补妆更服帖
</p>
<div class="desc-img-wrap">
{new Array(5).fill(0).map((item, index) => (
<div class="desc-img-box" key={index}>
<img src="" class="w-100% h-100%" />
</div>
))}
<div class="desc-img-box">
<img src="" class="w-100% h-100%" />
</div>
</div>
</div>
</div>
</div> </div>
<div class="flex justify-end items-center px-16px py-20px w-full bg-#fff footer-row">{renderFooterRow()}</div> <div class="flex justify-end items-center px-16px py-20px w-full bg-#fff footer-row">{renderFooterRow()}</div>
</> </>

View File

@ -14,6 +14,25 @@ $footer-height: 68px;
font-family: $font-family-medium; font-family: $font-family-medium;
} }
} }
.main-img-box {
width: 320px;
height: 472px;
background: #fff;
object-fit: contain;
aspect-ratio: 3/4;
}
.desc-img-wrap {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
.desc-img-box {
width: 212px;
height: 283px;
background: #fff;
object-fit: contain;
aspect-ratio: 3/4;
}
}
} }
.footer-row { .footer-row {
position: fixed; position: fixed;
@ -22,4 +41,4 @@ $footer-height: 68px;
width: calc(100% - $sidebar-width); width: calc(100% - $sidebar-width);
border-top: 1px solid #e6e6e8; border-top: 1px solid #e6e6e8;
height: $footer-height; height: $footer-height;
} }

View File

@ -46,6 +46,8 @@ export default {
if (check) { if (check) {
router.push({ name: 'ManuscriptCheck', params: { id: workId.value } }); router.push({ name: 'ManuscriptCheck', params: { id: workId.value } });
} else {
onBack()
} }
} }
}); });

View File

@ -6,6 +6,7 @@ import CancelUploadModal from './cancel-upload-modal.vue';
import UploadSuccessModal from './upload-success-modal.vue'; import UploadSuccessModal from './upload-success-modal.vue';
import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants'; import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants';
import { postWorksBatch } from '@/api/all/generationWorkshop.ts';
import icon1 from '@/assets/img/creative-generation-workshop/icon-photo.png'; import icon1 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-video.png'; import icon2 from '@/assets/img/creative-generation-workshop/icon-video.png';
@ -27,20 +28,22 @@ export default {
}, },
setup(props, { emit, expose }) { setup(props, { emit, expose }) {
const formRef = ref(null); const formRef = ref(null);
const route = useRoute();
const cancelUploadModal = ref(null); const cancelUploadModal = ref(null);
const uploadSuccessModal = ref(null); const uploadSuccessModal = ref(null);
const dataSource = ref([]); const works = ref([]);
const selectCardInfo = ref({}); const selectCardInfo = ref({});
const errorDataCards = ref([]); const errorDataCards = ref([]);
const uploadLoading = ref(false); const uploadLoading = ref(false);
const workId = route.params.id;
const onCancel = () => { const onCancel = () => {
cancelUploadModal.value?.open(dataSource.value.length); cancelUploadModal.value?.open(works.value.length);
}; };
const validateDataSource = () => { const validateDataSource = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
dataSource.value.forEach((item) => { works.value.forEach((item) => {
if (!item.title?.trim()) { if (!item.title?.trim()) {
errorDataCards.value.push(item); errorDataCards.value.push(item);
} }
@ -50,8 +53,8 @@ export default {
} }
if (errorDataCards.value.length > 0) { if (errorDataCards.value.length > 0) {
AMessage.error(`${errorDataCards.value.length} 个必填信息未填写,请检查`); AMessage.warning(`${errorDataCards.value.length} 个必填信息未填写,请检查`);
setTimeout(() => { setTimeout(() => {
const el = document.getElementById(`card-${errorDataCards.value[0]?.id}`); const el = document.getElementById(`card-${errorDataCards.value[0]?.id}`);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
@ -61,13 +64,21 @@ export default {
}); });
}; };
const onSubmit = async () => { const onUpload = async (e, check = false) => {
console.log('onUpload', works.value);
formRef.value formRef.value
?.validate() ?.validate()
.then(() => { .then(() => {
validateDataSource().then((res) => { validateDataSource().then(async (res) => {
console.log('success'); const { code, data } = await postWorksBatch({ works: works.value });
uploadSuccessModal.value?.open(dataSource.value); if (code === 200) {
uploadLoading.value = false;
if (check) {
uploadSuccessModal.value?.open(workId);
} else {
router.push({ name: 'ManuscriptList' });
}
}
}); });
}) })
.catch((err) => { .catch((err) => {
@ -75,8 +86,8 @@ export default {
}); });
}; };
const onSubmitAndCheck = () => { const onUploadAndCheck = (e) => {
console.log('onSubmitAndCheck'); onUpload(e, true);
}; };
const handleReValidate = () => { const handleReValidate = () => {
@ -95,12 +106,12 @@ export default {
const onDelete = (e, item, index) => { const onDelete = (e, item, index) => {
e.stopPropagation(); e.stopPropagation();
dataSource.value.splice(index, 1); works.value.splice(index, 1);
deleteErrorCard(item); deleteErrorCard(item);
if (item.id === selectCardInfo.value.id) { if (item.id === selectCardInfo.value.id) {
if (dataSource.value.length) { if (works.value.length) {
const _target = dataSource.value[0] ?? {}; const _target = works.value[0] ?? {};
selectCardInfo.value = _target; selectCardInfo.value = _target;
} else { } else {
selectCardInfo.value = {}; selectCardInfo.value = {};
@ -109,13 +120,13 @@ export default {
}; };
const renderFooterRow = () => { const renderFooterRow = () => {
if (dataSource.value.length > 1) { if (works.value.length > 1) {
return ( return (
<> <>
<Button size="medium" type="outline" onClick={onCancel} class="mr-12px"> <Button size="medium" type="outline" onClick={onCancel} class="mr-12px">
取消上传 取消上传
</Button> </Button>
<Button type="primary" size="medium" onClick={onSubmit} loading={uploadLoading.value}> <Button type="primary" size="medium" onClick={onUpload} loading={uploadLoading.value}>
{uploadLoading.value ? '批量上传' : '批量上传'} {uploadLoading.value ? '批量上传' : '批量上传'}
</Button> </Button>
</> </>
@ -126,10 +137,10 @@ export default {
<Button size="medium" type="outline" onClick={onCancel} class="mr-12px"> <Button size="medium" type="outline" onClick={onCancel} class="mr-12px">
取消上传 取消上传
</Button> </Button>
<Button size="medium" type="outline" onClick={onSubmit} class="mr-12px"> <Button size="medium" type="outline" onClick={onUpload} class="mr-12px" loading={uploadLoading.value}>
上传 上传
</Button> </Button>
<Button type="primary" size="medium" onClick={onSubmitAndCheck} loading={uploadLoading.value}> <Button type="primary" size="medium" onClick={onUploadAndCheck} loading={uploadLoading.value}>
上传并审核 上传并审核
</Button> </Button>
</> </>
@ -137,8 +148,8 @@ export default {
} }
}; };
const getData = () => { const getData = () => {
dataSource.value = generateMockData(); works.value = generateMockData();
selectCardInfo.value = dataSource.value[0] ?? {}; selectCardInfo.value = works.value[0] ?? {};
}; };
const getCardClass = (item) => { const getCardClass = (item) => {
if (selectCardInfo.value.id === item.id) { if (selectCardInfo.value.id === item.id) {
@ -159,15 +170,15 @@ export default {
<div class="left flex-1 overflow-y-auto p-24px"> <div class="left flex-1 overflow-y-auto p-24px">
<EditForm ref={formRef} v-model={selectCardInfo.value} onReValidate={handleReValidate} /> <EditForm ref={formRef} v-model={selectCardInfo.value} onReValidate={handleReValidate} />
</div> </div>
{dataSource.value.length > 1 && ( {works.value.length > 1 && (
<div class="right pt-24px pb-12px flex flex-col"> <div class="right pt-24px pb-12px flex flex-col">
<div class="title flex items-center px-24px"> <div class="title flex items-center px-24px">
<div class="w-3px h-16px bg-#6D4CFE rounded-2px mr-8px"></div> <div class="w-3px h-16px bg-#6D4CFE rounded-2px mr-8px"></div>
<p class="cts bold !color-#211F24 !text-16px !lh-24px mr-8px">上传内容稿件列表</p> <p class="cts bold !color-#211F24 !text-16px !lh-24px mr-8px">上传内容稿件列表</p>
<p class="cts">{`${dataSource.value.length}`}</p> <p class="cts">{`${works.value.length}`}</p>
</div> </div>
<div class="flex-1 px-24px overflow-y-auto pt-24px"> <div class="flex-1 px-24px overflow-y-auto pt-24px">
{dataSource.value.map((item, index) => ( {works.value.map((item, index) => (
<div <div
key={item.id} key={item.id}
id={`card-${item.id}`} id={`card-${item.id}`}

View File

@ -19,10 +19,10 @@ import icon1 from '@/assets/img/media-account/icon-feedback-success.png';
const router = useRouter(); const router = useRouter();
const visible = ref(false); const visible = ref(false);
const data = ref([]); const workId = ref('');
const onClose = () => { const onClose = () => {
data.value = []; workId.value = '';
visible.value = false; visible.value = false;
}; };
@ -31,13 +31,13 @@ const onBack = () => {
router.push({ name: 'ManuscriptList' }); router.push({ name: 'ManuscriptList' });
}; };
const onConfirm = () => { const onConfirm = () => {
onClose(); visible.value = false;
console.log('跳转至「内容稿件审核」菜单tab进入批量审核稿件页面'); router.push({ name: 'ManuscriptCheck', params: { id: workId.value } });
router.push({ name: 'ManuscriptCheck' });
}; };
const open = (dataSource) => { const open = (id) => {
data.value = dataSource; console.log({ id });
workId.value = id;
visible.value = true; visible.value = true;
}; };