Commit 4ebe9c71 authored by 黄同智's avatar 黄同智

商品脚本原料的弹窗

parent 9190171f
......@@ -2,7 +2,7 @@
<Dialog v-model="dialogVisible" bg-color="#fff" width="1152">
<div class="flex-col bg-fff round-8 hidden">
<header class="dialog-header flex-items-center h-56 px-20">
<h3 class="fs-14 fw-700 color-000">添加商品图</h3>
<h3 class="fs-14 fw-700 color-000">{{ title }}</h3>
</header>
<section class="dialog-body flex-1 flex-col hidden px-20 py-14">
......@@ -122,8 +122,6 @@ import UploadFile from '@/components/Upload/index.vue';
import productImg from '@/static/image/huaban.png';
import peopleImg from '@/static/image/meinv.png';
type TabValue = 'library' | 'local';
type ImageRow = {
id: string;
name: string;
......@@ -138,6 +136,7 @@ type ImageRow = {
const props = defineProps<{
modelValue: boolean;
dataType: String
}>();
const emit = defineEmits<{
......@@ -150,10 +149,21 @@ const dialogVisible = computed({
set: (value) => emit('update:modelValue', value),
});
const tabs: { label: string; value: TabValue }[] = [
{ label: '图片管理', value: 'library' },
{ label: '本地上传', value: 'local' },
];
const title = ref(props.dataType === 'goods' ?'添加商品图':'添加原料')
const tabs = computed(()=>{
if(props.dataType === 'goods') {
return [
{ label: '图片管理', value: 'library' },
{ label: '本地上传', value: 'local' },
]
} else {
return [
{ label: '资源库', value: 'library' },
{ label: '本地上传', value: 'local' },
]
}
})
const firstCategories = ['全部一级分类', '美妆护肤', '服饰内衣', '日用百货', '食品饮料'];
const secondCategories = ['全部二级分类', '商品主图', '模特展示', '效果对比', '资质证明'];
......@@ -161,7 +171,7 @@ const tags = ['全部标签', '产品实拍', '成分党', '清凉感', '对比
const statuses = ['全部状态', '审核通过', '待审核', '未审核'];
const uploaders = ['全部上传人', '致上互娱', '汤小真', '徐振', '美妆设计组'];
const activeTab = ref<TabValue>('library');
const activeTab = ref<any>('library');
const firstCategory = ref('全部一级分类');
const secondCategory = ref('全部二级分类');
const tagValue = ref('全部标签');
......@@ -335,7 +345,6 @@ const confirmImages = () => {
.dialog-footer {
flex: 0 0 58px;
border-top: 1px solid #edf2f7;
background: #f8fafc;
}
.tab-row {
......
......@@ -88,7 +88,6 @@ const confirmProductInfo = () => {
.dialog-footer {
border-top: 1px solid #edf2f7;
border-bottom: 0;
background: #f8fafc;
}
:deep(.el-input__wrapper) {
......
......@@ -16,7 +16,7 @@
</el-icon>
<span>输入商品信息</span>
</div>
<div class="flex-items-center gap-6 h-30 border-none pointer hover" @click="openProductImageDialog">
<div class="flex-items-center gap-6 h-30 border-none pointer hover" @click="openProductImageDialog('goods')">
<el-icon>
<component :is="Link" />
</el-icon>
......@@ -25,7 +25,8 @@
</div>
</el-popover>
<el-popover placement="bottom" :width="150" trigger="hover" :hide-after="0">
<el-popover v-model:visible="referencePopoverVisible" placement="bottom" :width="150" trigger="hover"
:hide-after="0">
<template #reference>
<el-button class="m-2 h-30 border-none px-6">
<el-icon>
......@@ -35,7 +36,7 @@
</el-button>
</template>
<div>
<div class="flex-items-center gap-6 h-30 border-none pointer hover">
<div class="flex-items-center gap-6 h-30 border-none pointer hover" @click="openReferenceVideoDialog">
<el-icon>
<component :is="Clock" />
</el-icon>
......@@ -50,7 +51,8 @@
</div>
</el-popover>
<el-popover placement="bottom" :width="120" trigger="hover" popper-class="agent-prompt-tools-popper" :hide-after="0">
<el-popover v-model:visible="scriptPopoverVisible" placement="bottom" :width="120" trigger="hover"
popper-class="agent-prompt-tools-popper" :hide-after="0">
<template #reference>
<el-button class="m-2 h-30 border-none px-6">
<el-icon>
......@@ -60,13 +62,13 @@
</el-button>
</template>
<div>
<div class="flex-items-center gap-6 h-30 border-none pointer hover">
<div class="flex-items-center gap-6 h-30 border-none pointer hover" @click="openScriptDialog">
<el-icon>
<component :is="Document" />
</el-icon>
<span>添加脚本</span>
</div>
<div class="flex-items-center gap-6 h-30 border-none pointer hover">
<div class="flex-items-center gap-6 h-30 border-none pointer hover" @click="openProductImageDialog('source')">
<el-icon>
<component :is="Film" />
</el-icon>
......@@ -260,7 +262,9 @@
</div>
</el-popover>
<AgentProductInfoDialog v-model="productInfoVisible" />
<AgentProductImageDialog v-model="productImageVisible" />
<AgentProductImageDialog v-model="productImageVisible" :dataType="dataType" />
<AgentReferenceVideoDialog v-model="referenceVideoVisible" />
<AgentScriptDialog v-model="scriptVisible" />
</div>
</template>
......@@ -284,6 +288,8 @@ import {
} from '@element-plus/icons-vue';
import AgentProductImageDialog from './AgentProductImageDialog.vue';
import AgentProductInfoDialog from './AgentProductInfoDialog.vue';
import AgentReferenceVideoDialog from './AgentReferenceVideoDialog.vue';
import AgentScriptDialog from './AgentScriptDialog.vue';
type MediaType = 'image' | 'video';
type ParamTab = 'frame' | 'output';
......@@ -320,9 +326,14 @@ const withWatermark = ref(false);
const productPopoverVisible = ref(false);
const productInfoVisible = ref(false);
const productImageVisible = ref(false);
const referencePopoverVisible = ref(false);
const referenceVideoVisible = ref(false);
const scriptPopoverVisible = ref(false);
const scriptVisible = ref(false);
const activeStyleTab = ref<StyleTab>('style');
const checkedStyleValues = ref(['english', 'revenge']);
const checkedElementValues = ref(['contrast']);
const dataType = ref('')
const setMediaType = (value: MediaType) => {
mediaType.value = value;
......@@ -337,9 +348,20 @@ const openProductInfoDialog = () => {
productInfoVisible.value = true;
};
const openProductImageDialog = () => {
const openProductImageDialog = (str: string) => {
productPopoverVisible.value = false;
productImageVisible.value = true;
dataType.value = str
};
const openReferenceVideoDialog = () => {
referencePopoverVisible.value = false;
referenceVideoVisible.value = true;
};
const openScriptDialog = () => {
scriptPopoverVisible.value = false;
scriptVisible.value = true;
};
const mediaTypes: IconSwitchOption<MediaType>[] = [
......
<template>
<Dialog v-model="dialogVisible" bg-color="#fff" width="800">
<div class="reference-video-dialog flex-col bg-fff round-8 hidden">
<header class="dialog-header flex-items-center h-56 px-20">
<h3 class="fs-14 fw-700 color-000">选择参考视频</h3>
</header>
<section class="dialog-body flex-1 flex-col px-20 py-20">
<el-input v-model="keyword" class="search-input mb-16" placeholder="搜索历史投放素材">
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
<div class="video-list flex flex-wrap gap-12">
<div v-for="item in filteredVideos" :key="item.id" class="video-card round-6 hidden pointer bg-fff"
:class="{ active: selectedVideoId === item.id }" @click="selectedVideoId = item.id">
<div class="video-cover relative hidden">
<img :src="item.cover" :alt="item.name" class="w-full h-full cover" />
<span
class="duration-tag absolute right-8 bottom-8 h-20 lh-20 px-6 round-4 fs-10 color-fff">
{{ item.duration }}
</span>
<span v-if="item.canDelete"
class="delete-btn absolute top-8 right-8 flex-center wh-22 round pointer">
<el-icon class="fs-14">
<Delete />
</el-icon>
</span>
</div>
<div class="px-6 py-4">
<div class="fs-12 color-333 ellipsis">{{ item.name }}</div>
<div class="fs-10 color-999">{{ item.size }}</div>
</div>
</div>
</div>
</section>
<footer class="dialog-footer flex justify-end gap-8 px-20 py-12">
<el-button class="px-18" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" class="px-20 border-none" @click="confirmVideo">
确认选择
</el-button>
</footer>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Delete, Search } from '@element-plus/icons-vue';
import Dialog from '@/components/Dialog/index.vue';
import peopleImg from '@/static/image/meinv.png';
import productImg from '@/static/image/huaban.png';
import longImg from '@/static/image/long.png';
type ReferenceVideo = {
id: string;
name: string;
size: string;
duration: string;
cover: string;
canDelete?: boolean;
};
const props = defineProps<{
modelValue: boolean;
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean];
confirm: [id: string];
}>();
const keyword = ref('');
const selectedVideoId = ref('');
const videos: ReferenceVideo[] = [
{
id: 'video-1',
name: 'r_434f2d996de64f6bb6c5af85311481...',
size: '7.3 MB',
duration: '00:30',
cover: productImg,
canDelete: true,
},
{
id: 'video-2',
name: '夏日防晒实测高转化素材.mp4',
size: '24.6 MB',
duration: '00:28',
cover: peopleImg,
},
{
id: 'video-3',
name: '通勤穿搭口播投放素材.mp4',
size: '38.1 MB',
duration: '00:42',
cover: longImg,
},
{
id: 'video-4',
name: '厨房清洁前后对比素材.mp4',
size: '31.8 MB',
duration: '00:35',
cover: productImg,
},
{
id: 'video-5',
name: '新品卖点快速展示素材.mp4',
size: '18.2 MB',
duration: '00:26',
cover: peopleImg,
},
{
id: 'video-6',
name: '产品使用过程参考素材.mp4',
size: '21.4 MB',
duration: '00:32',
cover: longImg,
},
];
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
const filteredVideos = computed(() => {
const value = keyword.value.trim();
if (!value) {
return videos;
}
return videos.filter((item) => item.name.includes(value));
});
const confirmVideo = () => {
emit('confirm', selectedVideoId.value);
dialogVisible.value = false;
};
</script>
<style scoped lang="scss">
.reference-video-dialog {
height: 600px;
}
.dialog-header {
flex: 0 0 56px;
border-bottom: 1px solid #edf2f7;
}
.dialog-body {
min-height: 0;
}
.dialog-footer {
flex: 0 0 58px;
border-top: 1px solid #edf2f7;
}
.video-card {
flex: 0 0 calc((100% - 48px) / 5);
border: 1px solid #dce6f2;
transition: border-color .2s, box-shadow .2s;
&:hover,
&.active {
border-color: #7d28ff;
box-shadow: 0 0 0 1px rgba(125, 40, 255, .5);
}
}
.video-cover {
height: 82px;
background: #f3f6fb;
}
.duration-tag {
background: rgba(0, 0, 0, .68);
}
.delete-btn {
color: #fff;
background: rgba(0, 0, 0, .62);
}
:deep(.search-input .el-input__wrapper) {
height: 38px;
border-radius: 6px;
box-shadow: 0 0 0 1px #dce6f2 inset;
}
:deep(.search-input .el-input__inner) {
font-size: 12px;
}
</style>
<template>
<Dialog v-model="dialogVisible" bg-color="#fff" width="800">
<div class="script-dialog flex-col bg-fff round-8 hidden">
<header class="dialog-header flex-items-center h-56 px-20">
<h3 class="fs-14 fw-700 color-000">添加脚本</h3>
</header>
<section class="dialog-body flex-1 flex-col px-20 pt-18">
<div class="tab-row flex-items-center mb-20">
<div v-for="item in tabs" :key="item.value" class="tab-item h-34 lh-34 px-16 fs-12 fw-700 pointer"
:class="{ active: activeTab === item.value }" @click="activeTab = item.value">
{{ item.label }}
</div>
</div>
<div v-if="activeTab === 'input'" class="script-input-wrap flex-1">
<el-input v-model="scriptContent" type="textarea" resize="none" :maxlength="1000"
show-word-limit placeholder="请输入脚本内容" class="script-textarea h-full" />
</div>
<div v-else class="script-manage flex-1 flex-col hidden">
<div class="filter-row flex-items-center gap-8 mb-16">
<el-input v-model="keyword" class="script-search flex-1" placeholder="搜索脚本名称或 ID">
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
<el-select v-model="categoryValue" class="filter-select" placeholder="全部分类">
<el-option v-for="item in categories" :key="item" :label="item" :value="item" />
</el-select>
<el-select v-model="statusValue" class="filter-select" placeholder="全部状态">
<el-option v-for="item in statuses" :key="item" :label="item" :value="item" />
</el-select>
</div>
<SeTable class="script-table flex-1" :data-list="currentPageScripts" size="small"
:total="filteredScripts.length" :default-page-size="pageSize" :page-sizes="[3, 5, 10]"
:height="tableHeight" row-key="id" psize="small" @update:page="handleScriptPageChange">
<el-table-column width="52">
<template #default="{ row }">
<el-radio :model-value="selectedScriptId" :value="row.id"
@change="selectedScriptId = row.id" />
</template>
</el-table-column>
<el-table-column label="脚本名称 / ID" min-width="230">
<template #default="{ row }">
<div class="flex-col">
<span class="fs-12 fw-700 color-333 ellipsis">{{ row.name }}</span>
<span class="fs-10 color-999">{{ row.id }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="category" label="分类" min-width="180" />
<el-table-column label="状态" width="110">
<template #default="{ row }">
<span class="status-tag h-22 lh-22 px-10 round-4 fs-10">{{ row.status }}</span>
</template>
</el-table-column>
<el-table-column prop="uploader" label="上传人" width="110" />
<el-table-column prop="updatedAt" label="更新时间" width="150" />
</SeTable>
</div>
</section>
<footer class="dialog-footer flex justify-end gap-8 px-20 py-12">
<el-button class="h-34 px-18" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" class="confirm-btn h-34 px-20 border-none" @click="confirmScript">
确认
</el-button>
</footer>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { Search } from '@element-plus/icons-vue';
import Dialog from '@/components/Dialog/index.vue';
type TabValue = 'input' | 'manage';
type ScriptRow = {
id: string;
name: string;
category: string;
status: string;
uploader: string;
updatedAt: string;
};
const props = defineProps<{
modelValue: boolean;
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean];
confirm: [value: string];
}>();
const tabs: { label: string; value: TabValue }[] = [
{ label: '输入脚本', value: 'input' },
{ label: '脚本管理', value: 'manage' },
];
const categories = ['全部分类', '个护家清 / 洗发护发', '汽车用品 / 清洁养护', '服饰内衣 / 女士内衣'];
const statuses = ['全部状态', '待审核', '审核通过', '审核驳回'];
const scriptRows: ScriptRow[] = [
{
id: 'S-10291',
name: '脚本 1 - 口播温和洁面破圈案',
category: '个护家清 / 洗发护发',
status: '待审核',
uploader: '致上编导',
updatedAt: '2026-08-04 14:20',
},
{
id: 'S-10292',
name: '玻璃油膜擦雨天实测分镜',
category: '汽车用品 / 清洁养护',
status: '审核通过',
uploader: '徐振',
updatedAt: '2026-08-18 10:32',
},
{
id: 'S-10293',
name: '高腰塑身裤痛点反转脚本',
category: '服饰内衣 / 女士内衣',
status: '审核通过',
uploader: '汤小真',
updatedAt: '2026-08-20 16:08',
},
{
id: 'S-10294',
name: '防晒喷雾户外实测脚本',
category: '个护家清 / 洗发护发',
status: '待审核',
uploader: '致上编导',
updatedAt: '2026-08-21 09:26',
},
{
id: 'S-10295',
name: '汽车玻璃水卖点拆解脚本',
category: '汽车用品 / 清洁养护',
status: '审核通过',
uploader: '徐振',
updatedAt: '2026-08-22 11:18',
},
{
id: 'S-10296',
name: '无痕内衣场景种草脚本',
category: '服饰内衣 / 女士内衣',
status: '审核驳回',
uploader: '汤小真',
updatedAt: '2026-08-23 17:42',
},
];
const activeTab = ref<TabValue>('input');
const scriptContent = ref('');
const keyword = ref('');
const categoryValue = ref('全部分类');
const statusValue = ref('全部状态');
const selectedScriptId = ref('');
const currentPage = ref(1);
const pageSize = ref(3);
const tableHeight = 370;
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
const filteredScripts = computed(() => {
const searchValue = keyword.value.trim();
return scriptRows.filter((item) => {
const matchKeyword = !searchValue || item.name.includes(searchValue) || item.id.includes(searchValue);
const matchCategory = categoryValue.value === '全部分类' || item.category === categoryValue.value;
const matchStatus = statusValue.value === '全部状态' || item.status === statusValue.value;
return matchKeyword && matchCategory && matchStatus;
});
});
const currentPageScripts = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
return filteredScripts.value.slice(start, start + pageSize.value);
});
const handleScriptPageChange = (page: number, size: number) => {
currentPage.value = page;
pageSize.value = size;
};
watch([keyword, categoryValue, statusValue], () => {
currentPage.value = 1;
});
const confirmScript = () => {
emit('confirm', activeTab.value === 'input' ? scriptContent.value : selectedScriptId.value);
dialogVisible.value = false;
};
</script>
<style scoped lang="scss">
.script-dialog {
height: 668px;
}
.dialog-header {
flex: 0 0 56px;
border-bottom: 1px solid #edf2f7;
}
.dialog-body,
.script-manage,
.script-table {
min-height: 0;
}
.dialog-footer {
flex: 0 0 58px;
border-top: 1px solid #edf2f7;
background: #f8fafc;
}
.tab-row {
border-bottom: 1px solid #dfe7f2;
}
.tab-item {
color: #536883;
border-bottom: 2px solid transparent;
&.active {
color: #7d28ff;
border-bottom-color: #7d28ff;
}
}
.script-input-wrap {
min-height: 0;
}
.filter-select {
width: 160px;
}
.status-tag {
color: #00a870;
background: #e8fff4;
}
.confirm-btn {
background: #b78cff;
}
:deep(.script-textarea .el-textarea__inner) {
height: 100%;
min-height: 0 !important;
padding: 18px;
border-radius: 6px;
box-shadow: 0 0 0 1px #dce6f2 inset;
}
:deep(.script-textarea .el-input__count) {
right: 12px;
bottom: 8px;
color: #8aa0bf;
background: transparent;
}
:deep(.script-search .el-input__wrapper),
:deep(.filter-select .el-select__wrapper) {
height: 36px;
border-radius: 4px;
box-shadow: 0 0 0 1px #dce6f2 inset;
}
:deep(.el-radio__label) {
display: none;
}
:deep(.el-radio__input.is-checked .el-radio__inner) {
border-color: #7d28ff;
background: #7d28ff;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment