feat: 客户分享链接列表
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<a-config-provider :locale="zhCN" size="small" :theme="redTheme">
|
||||
<router-view v-if="$route.path === '/login'" />
|
||||
<router-view v-if="$route.path === '/login' || ['ExploreList', 'ExploreDetail'].includes($route.name)" />
|
||||
<LayoutBasic v-else />
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
@ -77,3 +77,46 @@ export const putWorkAuditsAuditPass = (params = {}) => {
|
||||
const { id: auditId, ...rest } = params as { id: string; [key: string]: any };
|
||||
return Http.put(`/v1/work-audits/${auditId}/audit-pass`, rest);
|
||||
};
|
||||
|
||||
// 内容稿件-列表(客户)
|
||||
export const getShareWorksList = (shareCode: string) => {
|
||||
return Http.get(
|
||||
'/v1/share/works/list',
|
||||
{},
|
||||
{
|
||||
headers: { 'share-code': shareCode },
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 内容稿件-详情(客户)
|
||||
export const getShareWorksDetail = (id: string, shareCode: string) => {
|
||||
return Http.get(
|
||||
`/v1/share/works/${id}`,
|
||||
{},
|
||||
{
|
||||
headers: { 'share-code': shareCode },
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 内容稿件-确认(客户)
|
||||
export const patchShareWorksConfirm = (id: string, params = {}, shareCode: string) => {
|
||||
return Http.patch(`/v1/share/works/${id}/confirm`, params, {
|
||||
headers: { 'share-code': shareCode },
|
||||
});
|
||||
};
|
||||
|
||||
// 内容稿件-评论(客户)
|
||||
export const postShareWorksComments = (id: string, params = {}, shareCode: string) => {
|
||||
return Http.post(`/v1/share/works/${id}/comments`, params, {
|
||||
headers: { 'share-code': shareCode },
|
||||
});
|
||||
};
|
||||
|
||||
// 内容稿件-删除评论(客户)
|
||||
export const deleteShareWorksComments = (id: string, commentId: string, shareCode: string) => {
|
||||
return Http.delete(`/v1/share/works/${id}/comments/${commentId}`, {
|
||||
headers: { 'share-code': shareCode },
|
||||
});
|
||||
};
|
||||
|
||||
BIN
src/assets/img/creative-generation-workshop/icon-confirm.png
Normal file
BIN
src/assets/img/creative-generation-workshop/icon-confirm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* @Author: RenXiaoDong
|
||||
* @Date: 2025-06-19 01:45:53
|
||||
*/
|
||||
import { appRoutes } from '../routes';
|
||||
|
||||
const mixinRoutes = [...appRoutes];
|
||||
|
||||
const appClientMenus = mixinRoutes.map((el) => {
|
||||
const { name, path, meta, redirect, children } = el;
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
meta,
|
||||
redirect,
|
||||
children,
|
||||
};
|
||||
});
|
||||
|
||||
export default mixinRoutes;
|
||||
@ -4,7 +4,6 @@
|
||||
*/
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { appRoutes } from './routes';
|
||||
import { NOT_FOUND_ROUTE } from './routes/base';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { MENU_GROUP_IDS } from './constants';
|
||||
@ -35,27 +34,18 @@ export const router = createRouter({
|
||||
id: MENU_GROUP_IDS.WORK_BENCH_ID,
|
||||
},
|
||||
},
|
||||
|
||||
...appRoutes,
|
||||
{
|
||||
path: '/permission',
|
||||
name: 'permission',
|
||||
component: () => import('@/views/components/permission/choose-enterprise.vue'),
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('@/layouts/NotFound.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
requireLogin: true,
|
||||
hideInMenu: true,
|
||||
hideSidebar: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: '/auth',
|
||||
// name: 'auth',
|
||||
// component: () => import('@/views/components/permission/auth.vue'),
|
||||
// meta: {
|
||||
// requiresAuth: false,
|
||||
// requireLogin: true,
|
||||
// },
|
||||
// },
|
||||
...appRoutes,
|
||||
// REDIRECT_MAIN,
|
||||
NOT_FOUND_ROUTE,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { REDIRECT_ROUTE_NAME } from '@/router/constants';
|
||||
|
||||
// export const REDIRECT_MAIN: RouteRecordRaw = {
|
||||
// path: '/redirect',
|
||||
// name: 'redirect',
|
||||
// meta: {
|
||||
// requiresAuth: false,
|
||||
// requireLogin: false,
|
||||
// hideInMenu: true,
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: '/redirect/:path',
|
||||
// name: REDIRECT_ROUTE_NAME,
|
||||
// component: () => import('@/layouts/Basic.vue'),
|
||||
// meta: {
|
||||
// requiresAuth: false,
|
||||
// requireLogin: false,
|
||||
// hideInMenu: true,
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('@/layouts/NotFound.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
hideInMenu: true,
|
||||
hideSidebar: true,
|
||||
},
|
||||
};
|
||||
@ -111,6 +111,28 @@ const COMPONENTS: AppRouteRecordRaw[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/explore/list/:shareCode',
|
||||
name: 'ExploreList',
|
||||
meta: {
|
||||
locale: '分享链接列表',
|
||||
requiresAuth: false,
|
||||
requireLogin: false,
|
||||
roles: ['*'],
|
||||
},
|
||||
component: () => import('@/views/creative-generation-workshop/explore/list/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/explore/detail/:shareCode/:id',
|
||||
name: 'ExploreDetail',
|
||||
meta: {
|
||||
locale: '分享链接详情',
|
||||
requiresAuth: false,
|
||||
requireLogin: false,
|
||||
roles: ['*'],
|
||||
},
|
||||
component: () => import('@/views/creative-generation-workshop/explore/detail/index.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
export default COMPONENTS;
|
||||
|
||||
@ -166,7 +166,7 @@ export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame
|
||||
hasResolved = true;
|
||||
resolve({
|
||||
duration: video.duration,
|
||||
firstFrame: canvas.toDataURL('image/jpeg', 0.9) // 提高质量
|
||||
firstFrame: canvas.toDataURL('image/jpeg', 0.9), // 提高质量
|
||||
});
|
||||
};
|
||||
|
||||
@ -189,7 +189,7 @@ export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame
|
||||
if (imageData) {
|
||||
let isAllBlack = true;
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
if (imageData.data[i] > 10 || imageData.data[i+1] > 10 || imageData.data[i+2] > 10) {
|
||||
if (imageData.data[i] > 10 || imageData.data[i + 1] > 10 || imageData.data[i + 2] > 10) {
|
||||
isAllBlack = false;
|
||||
break;
|
||||
}
|
||||
@ -204,7 +204,7 @@ export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame
|
||||
hasResolved = true;
|
||||
resolve({
|
||||
duration: video.duration,
|
||||
firstFrame: canvas.toDataURL('image/jpeg', 0.9)
|
||||
firstFrame: canvas.toDataURL('image/jpeg', 0.9),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -225,7 +225,7 @@ export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame
|
||||
document.body.removeChild(video);
|
||||
resolve({
|
||||
duration: 0,
|
||||
firstFrame: ''
|
||||
firstFrame: '',
|
||||
});
|
||||
}
|
||||
}, 5000); // 5秒超时
|
||||
@ -266,7 +266,7 @@ export const formatUploadSpeed = (bytesPerSecond: number): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export function convertVideoUrlToCoverUrl(videoUrl: string): string {
|
||||
export function convertVideoUrlToCoverUrl(videoUrl: string): string {
|
||||
if (!videoUrl || typeof videoUrl !== 'string') {
|
||||
console.error('Invalid video URL');
|
||||
return '';
|
||||
@ -281,3 +281,20 @@ export const formatUploadSpeed = (bytesPerSecond: number): string => {
|
||||
|
||||
return urlWithCovers + '.jpg';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成包含协议、域名和参数的完整URL
|
||||
*/
|
||||
export const generateFullUrl = (pathTemplate: string, params: Record<string, string | number> = {}): string => {
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
const port = window.location.port ? `:${window.location.port}` : '';
|
||||
const baseUrl = `${protocol}//${hostname}${port}`;
|
||||
|
||||
let path = pathTemplate;
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
path = path.replace(`:${key}`, String(value));
|
||||
});
|
||||
|
||||
return `${baseUrl}${path}`;
|
||||
};
|
||||
|
||||
213
src/views/creative-generation-workshop/explore/detail/index.vue
Normal file
213
src/views/creative-generation-workshop/explore/detail/index.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<script lang="jsx">
|
||||
import { Image, Spin, Button } from '@arco-design/web-vue';
|
||||
|
||||
import { getShareWorksDetail } from '@/api/all/generationWorkshop.ts';
|
||||
import { convertVideoUrlToCoverUrl, exactFormatTime } from '@/utils/tools.ts';
|
||||
import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants.ts';
|
||||
|
||||
import icon1 from '@/assets/logo.svg';
|
||||
import icon2 from '@/assets/img/creative-generation-workshop/icon-confirm.png';
|
||||
import icon3 from '@/assets/img/creative-generation-workshop/icon-line.png';
|
||||
|
||||
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 default {
|
||||
setup(props, { emit, expose }) {
|
||||
const route = useRoute();
|
||||
const dataSource = ref({});
|
||||
const isExpand = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const isPlaying = ref(false);
|
||||
const videoRef = ref(null);
|
||||
const videoUrl = ref('');
|
||||
const coverImageUrl = ref('');
|
||||
const isVideoLoaded = ref(false);
|
||||
const images = ref([]);
|
||||
|
||||
const isVideo = computed(() => dataSource.value.type === EnumManuscriptType.Video);
|
||||
|
||||
const initData = () => {
|
||||
const [fileOne, ...fileOthers] = dataSource.value.files ?? [];
|
||||
if (isVideo.value) {
|
||||
videoUrl.value = fileOne.url;
|
||||
coverImageUrl.value = convertVideoUrlToCoverUrl(fileOne.url);
|
||||
} else {
|
||||
coverImageUrl.value = fileOne.url;
|
||||
images.value = fileOthers;
|
||||
}
|
||||
};
|
||||
|
||||
const getDetail = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { id, shareCode } = route.params;
|
||||
const { code, data } = await getShareWorksDetail(id, shareCode);
|
||||
if (code === 200) {
|
||||
dataSource.value = data;
|
||||
initData();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const renderMainImg = () => {
|
||||
if (!coverImageUrl.value) return null;
|
||||
|
||||
if (isVideo.value) {
|
||||
return (
|
||||
<div class="main-video-box mb-16px relative overflow-hidden cursor-pointer" onClick={togglePlay}>
|
||||
<video ref={videoRef} class="w-100% h-100% object-cover" onEnded={onVideoEnded}></video>
|
||||
{!isPlaying.value && (
|
||||
<>
|
||||
<img src={coverImageUrl.value} class="w-100% h-100% object-cover absolute z-0 top-0 left-0" />
|
||||
<div v-show={!isPlaying.value} class="play-icon"></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div class="main-img-box mb-16px relative overflow-hidden cursor-pointer">
|
||||
<img src={coverImageUrl.value} class="w-100% h-100% object-cover absolute z-0 top-0 left-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDetail();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.pause();
|
||||
videoRef.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class="explore-page">
|
||||
<header class="page-header">
|
||||
<div class="content w-full px-24px flex items-center bg-#fff">
|
||||
<img src={icon1} alt="" width={130} />
|
||||
</div>
|
||||
</header>
|
||||
{loading.value ? (
|
||||
<Spin spinning={loading.value} class="h-500px w-full flex justify-center items-center" size={60} />
|
||||
) : (
|
||||
<div class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}>
|
||||
<div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}>
|
||||
<icon-menu-fold size={20} class="color-#55585F hover:color-#6D4CFE" />
|
||||
</div>
|
||||
<section class="explore-detail-wrap pt-32px pb-52px flex flex-col items-center">
|
||||
<div class="flex justify-start flex-col w-full relative">
|
||||
<p class="title mb-8px">{dataSource.value.title}</p>
|
||||
<p class="cts color-#737478 mb-32px">
|
||||
{exactFormatTime(dataSource.value.last_modified_at, 'YYYY年MM月DD日')}修改
|
||||
</p>
|
||||
{dataSource.value.customer_opinion === 1 && (
|
||||
<img src={icon2} width={92} height={92} class="absolute right-0 bottom-0" />
|
||||
)}
|
||||
</div>
|
||||
{renderMainImg()}
|
||||
<p class="cts !color-#211F24">{dataSource.value.content}</p>
|
||||
|
||||
{/* 仅图片类型显示图片列表 */}
|
||||
{!isVideo.value && (
|
||||
<div class="desc-img-wrap mt-40px">
|
||||
{images.value.map((item, index) => (
|
||||
<div class="desc-img-box" key={index}>
|
||||
<img src={item.url} class="w-100% h-100% object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
{isExpand.value && (
|
||||
<section class="py-16px absolute right-16px w-440px h-full">
|
||||
<div class="ai-suggest-box p-24px">
|
||||
<div class="mb-16px w-full flex justify-between">
|
||||
<div class="mb-24px relative w-fit">
|
||||
<span class="ai-text">AI 审核建议</span>
|
||||
<img src={icon3} class="w-80px h-10.8px absolute bottom-1px left--9px" />
|
||||
</div>
|
||||
<icon-menu-unfold
|
||||
size={20}
|
||||
class="color-#55585F cursor-pointer hover:color-#6D4CFE"
|
||||
onClick={() => (isExpand.value = false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="result-box p-16px rounded-8px mb-16px">
|
||||
<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">
|
||||
展开详情
|
||||
</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>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './style.scss';
|
||||
</style>
|
||||
151
src/views/creative-generation-workshop/explore/detail/style.scss
Normal file
151
src/views/creative-generation-workshop/explore/detail/style.scss
Normal file
@ -0,0 +1,151 @@
|
||||
.explore-page {
|
||||
position: relative;
|
||||
padding-top: $navbar-height;
|
||||
min-width: 1200px;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.page-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
min-width: 1200px;
|
||||
.content {
|
||||
height: $navbar-height;
|
||||
border-bottom: 1px solid var(--Border-1, #d7d7d9);
|
||||
}
|
||||
}
|
||||
.cts {
|
||||
color: #939499;
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
&.bold {
|
||||
font-family: $font-family-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.page-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.explore-detail-wrap {
|
||||
min-height: 500px;
|
||||
width: 684px;
|
||||
.title {
|
||||
color: var(--Text-1, #211f24);
|
||||
font-family: $font-family-medium;
|
||||
font-size: 28px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 40px; /* 142.857% */
|
||||
}
|
||||
}
|
||||
.fold-box {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 30px;
|
||||
border: 1px solid var(--Border-1, #d7d7d9);
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.main-video-box {
|
||||
width: 320px;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
}
|
||||
.main-img-box {
|
||||
width: 347px;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
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;
|
||||
}
|
||||
}
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 222;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-image: url('@/assets/img/creative-generation-workshop/icon-play.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: background-image 0.3s ease;
|
||||
}
|
||||
|
||||
.play-icon:hover {
|
||||
background-image: url('@/assets/img/creative-generation-workshop/icon-play-hover.png');
|
||||
}
|
||||
.ai-suggest-box {
|
||||
width: 440px;
|
||||
height: fit-content;
|
||||
max-height: 100%;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(126deg, #eef2fd 8.36%, #f5ebfe 49.44%, #fdebf3 90.52%);
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1);
|
||||
.ai-text {
|
||||
font-family: $font-family-medium;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
background: linear-gradient(85deg, #7d419d 4.56%, #31353d 94.75%);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.result-box {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(4px);
|
||||
.result-item {
|
||||
.s1 {
|
||||
color: var(--Brand-6, #6d4cfe);
|
||||
font-family: $font-family-manrope-regular;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 32px; /* 133.333% */
|
||||
}
|
||||
&:first-child {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0;
|
||||
width: 1px;
|
||||
height: 32px;
|
||||
background: var(--Border-1, #d7d7d9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/views/creative-generation-workshop/explore/list/index.vue
Normal file
102
src/views/creative-generation-workshop/explore/list/index.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<script lang="jsx">
|
||||
import TextOverTips from '@/components/text-over-tips';
|
||||
import { Image, Spin } from '@arco-design/web-vue';
|
||||
|
||||
import { exactFormatTime } from '@/utils/tools';
|
||||
import { getShareWorksList } from '@/api/all/generationWorkshop';
|
||||
|
||||
import icon1 from '@/assets/img/error-img.png';
|
||||
import icon2 from '@/assets/logo.svg';
|
||||
|
||||
export default {
|
||||
setup(props, { emit, expose }) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const dataSource = ref({});
|
||||
const loading = ref(false);
|
||||
const works = computed(() => dataSource.value.works ?? []);
|
||||
const shareCode = computed(() => route.params.shareCode);
|
||||
|
||||
const onClickItem = (item) => {
|
||||
router.push({
|
||||
path: `/explore/detail/${shareCode.value}/${item.id}`,
|
||||
});
|
||||
};
|
||||
|
||||
const getShareWorks = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { code, data } = await getShareWorksList(shareCode.value);
|
||||
if (code === 200) {
|
||||
dataSource.value = data;
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
getShareWorks();
|
||||
});
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class="explore-page">
|
||||
<header class="page-header">
|
||||
<div class="content w-full px-24px flex items-center bg-#fff">
|
||||
<img src={icon2} alt="" width={130} />
|
||||
</div>
|
||||
</header>
|
||||
{loading.value ? (
|
||||
<Spin spinning={loading.value} class="h-500px w-full flex justify-center items-center" size={60} />
|
||||
) : (
|
||||
<section class="page-wrapper flex justify-center">
|
||||
<div class="explore-container">
|
||||
<div class="explore-list-wrap pt-24px pb-28px">
|
||||
<TextOverTips context={`${works.value[0]?.title}等${works.value.length}个文件`} class="mb-8px" />
|
||||
<p class="cts !color-#939499 mb-24px">
|
||||
{`分享时间:${exactFormatTime(dataSource.value.created_at, 'YYYY-MM-DD HH:mm:ss')} 有效期${
|
||||
dataSource.value.days
|
||||
}天`}
|
||||
</p>
|
||||
<div class="card-container">
|
||||
{works.value.map((item) => {
|
||||
return (
|
||||
<div
|
||||
class="card-item rounded-8px overflow-hidden"
|
||||
key={item.id}
|
||||
onClick={() => onClickItem(item)}
|
||||
>
|
||||
<Image
|
||||
src={item.cover}
|
||||
width={'100%'}
|
||||
height={300}
|
||||
preview={false}
|
||||
fit="cover"
|
||||
v-slots={{
|
||||
error: () => <img src={icon1} class="w-full h-full" />,
|
||||
}}
|
||||
/>
|
||||
<div class="p-16px">
|
||||
<TextOverTips context={item.title} class=" !lh-24px !text-16px mb-8px bold" />
|
||||
<p class="cts color-#737478">
|
||||
{exactFormatTime(item.last_modified_at, 'YYYY年MM月DD日')}修改
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './style.scss';
|
||||
</style>
|
||||
@ -0,0 +1,59 @@
|
||||
.explore-page {
|
||||
position: relative;
|
||||
padding-top: $navbar-height;
|
||||
min-width: 1200px;
|
||||
background: #fff;
|
||||
.page-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
min-width: 1200px;
|
||||
.content {
|
||||
height: $navbar-height;
|
||||
border-bottom: 1px solid var(--Border-1, #d7d7d9);
|
||||
}
|
||||
}
|
||||
.page-wrapper {
|
||||
min-height: 500px;
|
||||
.explore-container {
|
||||
width: 1200px;
|
||||
.explore-list-wrap {
|
||||
.cts {
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
:deep(.overflow-text) {
|
||||
color: var(--Text-1, #211f24);
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
&.bold {
|
||||
font-family: $font-family-medium;
|
||||
}
|
||||
}
|
||||
.card-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
.card-item {
|
||||
border: 1px solid var(--Border-1, #d7d7d9);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&:hover {
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
|
||||
border: 1.01px solid var(--Border-1, #d7d7d9);
|
||||
border-radius: 8.08px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ export default {
|
||||
<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} />
|
||||
<icon-menu-unfold size={16} class="color-##55585F cursor-pointer hover:color-#6D4CFE" onClick={onClose} />
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto px-24px">
|
||||
{dataSource.value.map((item) => (
|
||||
|
||||
@ -22,6 +22,7 @@ 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 { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants';
|
||||
|
||||
import icon1 from '@/assets/img/creative-generation-workshop/icon-magic.png';
|
||||
import icon2 from '@/assets/img/creative-generation-workshop/icon-line.png';
|
||||
@ -53,6 +54,12 @@ export default {
|
||||
const modules = [Navigation];
|
||||
const checkLoading = ref(false);
|
||||
|
||||
const tabList = computed(() => {
|
||||
if (props.modelValue.type === EnumManuscriptType.Image) {
|
||||
return TAB_LIST;
|
||||
}
|
||||
return TAB_LIST.filter((item) => item.value !== enumTab.IMAGE);
|
||||
});
|
||||
const isTextTab = computed(() => activeTab.value === enumTab.TEXT);
|
||||
|
||||
const onAiReplace = () => {
|
||||
@ -336,7 +343,7 @@ export default {
|
||||
))}
|
||||
</div>
|
||||
<div class="mb-16px suggestion-box p-12px rounded-8px bg-#F7F8FA flex flex-col">
|
||||
<div class=" mb-24px relative w-fit">
|
||||
<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>
|
||||
@ -434,7 +441,7 @@ export default {
|
||||
<div class="flex-2 left-box mr-24px flex flex-col">
|
||||
<div class="flex-1 mb-12px rounded-8px border-1px pt-8px flex flex-col pb-16px bg-#F7F8FA border-#E6E6E8 border-solid">
|
||||
<Tabs v-model={activeTab.value} onTabClick={handleTabClick} class="mb-16px">
|
||||
{TAB_LIST.map((item) => (
|
||||
{tabList.value.map((item) => (
|
||||
<TabPane key={item.value} title={item.label}></TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
@ -17,18 +17,13 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
selectCardInfo: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'cardClick'],
|
||||
emits: ['cardClick', 'platformChange'],
|
||||
setup(props, { emit, expose }) {
|
||||
const selectedPlatform = ref(1);
|
||||
const modules = [Navigation];
|
||||
const handleCardClick = (item) => {
|
||||
// emit('update:modelValue', item);
|
||||
@ -94,15 +89,15 @@ export default {
|
||||
<div
|
||||
key={item.value}
|
||||
onClick={() => {
|
||||
selectedPlatform.value = item.value;
|
||||
emit('platformChange', item.value);
|
||||
}}
|
||||
class={`w-100px flex items-center mr-16px py-8px px-12px flex border-1px border-solid border-transparent transition-all
|
||||
items-center rounded-8px cursor-pointer bg-#F2F3F5 hover:bg-#E6E6E8 ${
|
||||
selectedPlatform.value === item.value ? '!bg-#F0EDFF !border-#6D4CFE' : ''
|
||||
props.selectCardInfo.platform === item.value ? '!bg-#F0EDFF !border-#6D4CFE' : ''
|
||||
}`}
|
||||
>
|
||||
<img src={item.icon} alt="" width={20} height={20} class="mr-4px" />
|
||||
<span class={`cts !color-#211F24 ${selectedPlatform.value === item.value ? 'bold' : ''}`}>
|
||||
<span class={`cts !color-#211F24 ${props.selectCardInfo.platform === item.value ? 'bold' : ''}`}>
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -64,10 +64,15 @@ export default {
|
||||
const getWorkAudits = async () => {
|
||||
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] ?? {};
|
||||
const _data = (data ?? []).map((item) => ({
|
||||
...item,
|
||||
platform: item.platform === 0 ? 1 : item.platform,
|
||||
}));
|
||||
|
||||
dataSource.value = _data;
|
||||
remoteDataSource.value = cloneDeep(_data);
|
||||
selectCardInfo.value = cloneDeep(_data?.[0] ?? {});
|
||||
selectedImageInfo.value = cloneDeep(_data?.[0].files?.[0] ?? {});
|
||||
}
|
||||
};
|
||||
|
||||
@ -77,6 +82,9 @@ export default {
|
||||
resolve(!isEqual(selectCardInfo.value, _item) && !isSaved.value);
|
||||
});
|
||||
};
|
||||
const onPlatformChange = (platform) => {
|
||||
selectCardInfo.value.platform = platform;
|
||||
};
|
||||
|
||||
const onExit = async () => {
|
||||
const isModified = await isSelectCardModified();
|
||||
@ -165,13 +173,18 @@ export default {
|
||||
class="check-list-icon"
|
||||
onClick={() => checkListDrawerRef.value.open(dataSource.value, selectCardInfo.value)}
|
||||
>
|
||||
<icon-menu-fold size={16} class="color-#55585F mr-4px" />
|
||||
<icon-menu-fold size={16} class="color-#55585F mr-4px hover:color-#6D4CFE" />
|
||||
<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} />
|
||||
<HeaderCard
|
||||
dataSource={dataSource.value}
|
||||
selectCardInfo={selectCardInfo.value}
|
||||
onCardClick={onCardClick}
|
||||
onPlatformChange={onPlatformChange}
|
||||
/>
|
||||
<section class="flex-1 overflow-hidden">
|
||||
<ContentCard
|
||||
ref={contentCardRef}
|
||||
|
||||
@ -4,6 +4,7 @@ import CommonSelect from '@/components/common-select';
|
||||
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { postShareLinksGenerate } from '@/api/all/generationWorkshop';
|
||||
import { generateFullUrl } from '@/utils/tools';
|
||||
|
||||
const INITIAL_FORM = {
|
||||
work_ids: [],
|
||||
@ -44,6 +45,7 @@ export default {
|
||||
const formRef = ref(null);
|
||||
const formData = ref(cloneDeep(INITIAL_FORM));
|
||||
const loading = ref(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { copy } = useClipboard({ source: formData.value.link });
|
||||
|
||||
@ -63,9 +65,13 @@ export default {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { code, data } = await postShareLinksGenerate(formData.value);
|
||||
if (!code) {
|
||||
if (code === 200) {
|
||||
onClose();
|
||||
copy(data.code);
|
||||
|
||||
const url = router.resolve({
|
||||
path: `/explore/list/${data.code}`,
|
||||
}).href;
|
||||
copy(generateFullUrl(url));
|
||||
AMessage.success('复制成功!');
|
||||
emit('close');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user