Commit 6f5ecf40 authored by 黄同智's avatar 黄同智

任务协作-拆解表的弹窗表单,创建数据分析的路由

parent 3f60d8d5
...@@ -141,6 +141,25 @@ const menus = [ ...@@ -141,6 +141,25 @@ const menus = [
}, },
], ],
}, },
{
name: '数据分析',
path: '/analysis',
icon: 'redraw',
children: [
{
name: '视频数据分析',
path: '/video',
},
{
name: '广告账户分析',
path: '/ad',
},
{
name: '部门分析',
path: '/department',
}
],
},
]; ];
</script> </script>
......
...@@ -102,8 +102,30 @@ const routes = [ ...@@ -102,8 +102,30 @@ const routes = [
}, },
] ]
}, },
{
path: '/analysis',
component: Layout,
children: [
{
path: 'video',
name: 'VideoData',
component: ()=> import('@/views/analysis/video.vue'),
meta: { title: '视频数据分析', icon: '', affix: true }
},
{
path: 'ad',
name: 'AdData',
component: ()=> import('@/views/analysis/ad.vue'),
meta: { title: '广告账户分析', icon: '', affix: true }
},
{
path: 'department',
name: 'DepartmentData',
component: ()=> import('@/views/analysis/department.vue'),
meta: { title: '部门分析', icon: '', affix: true }
},
]
},
{ {
......
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>
\ No newline at end of file
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>
\ No newline at end of file
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
</style>
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<el-dialog v-model="visible" title="Shipping address" width="1200" modal-class="xxxxx" align-center <el-dialog v-model="visible" title="Shipping address" width="1200" modal-class="xxxxx" align-center
:before-close="handleClose"> :before-close="handleClose">
<template #title> <template #title>
<div class="fs-18 font-bold py-10 pl-20">视频上传</div> <div class="fs-18 font-bold py-10 pl-20">上传脚本</div>
</template> </template>
<div class="h-700 over-auto px-20 pb-20"> <div class="h-700 over-auto px-20 pb-20">
<el-form ref="formRef" :model="form" label-width="100"> <el-form ref="formRef" :model="form" label-width="100">
......
<template>
<Dialog v-model="visible" bg-color="#fff" width="1160">
<div class="bg-fff round-10 hidden breakdown-dialog">
<div class="h-58 px-24 flex-items-center dialog-header">
<span class="w-4 h-16 round-10 bg-main mr-10"></span>
<span class="fs-16 font-bold color-000">拆解表</span>
</div>
<div class="px-24 py-14 flex-items-center dialog-toolbar">
<div class="border-eee round-4 mr-12">
<el-input v-model="form.breakdownId" class="w-210" placeholder="请输入拆解表ID" />
</div>
<el-button type="default" plain @click="copyBreakdownId">复制拆解表ID</el-button>
<el-button type="primary" plain @click="recognize">识别</el-button>
</div>
<div class="p-24 breakdown-body">
<section class="round-6 hidden breakdown-table">
<div class="grid-row">
<div class="table-side row-span-2 flex-center font-bold">对接环节</div>
<div class="table-head">拍摄人员</div>
<div class="table-head">剪辑人员</div>
<div class="table-head">框架审核</div>
<div class="table-head">技术审核</div>
<div class="table-head">创意模特</div>
<div class="table-head">创意声源</div>
<div class="table-cell text-center"><el-input v-model="form.photographer"
placeholder="请输入拍摄人员" /></div>
<div class="table-cell text-center"><el-input v-model="form.editor" placeholder="请输入剪辑人员" />
</div>
<div class="table-cell text-center"><el-input v-model="form.frameReviewer"
placeholder="请输入框架审核人" /></div>
<div class="table-cell text-center"><el-input v-model="form.techReviewer"
placeholder="请输入技术审核人" /></div>
<div class="table-cell text-center"><el-input v-model="form.model" placeholder="请输入创意模特" />
</div>
<div class="table-cell text-center"><el-input v-model="form.voice" placeholder="请输入创意声源" />
</div>
</div>
<div class="grid-row connect-row">
<div class="table-side row-span-5 flex-center font-bold">对接环节</div>
<div class="table-label">创意场景</div>
<div class="table-cell col-span-2">
<el-input v-model="form.scene" placeholder="请输入创意场景,如:室内浴室/洗手台" />
</div>
<div class="table-label">创意灯光</div>
<div class="table-cell col-span-2">
<el-input v-model="form.light" placeholder="请输入创意灯光,如:柔和明亮暖光" />
</div>
<div class="table-label">模特服饰</div>
<div class="table-cell col-span-2">
<el-input v-model="form.clothes" placeholder="请输入模特服饰" />
</div>
<div class="table-label">模特道具</div>
<div class="table-cell col-span-2 flex-items-center gap-18">
<el-checkbox v-model="form.props.earring">耳环</el-checkbox>
<el-checkbox v-model="form.props.necklace">项链</el-checkbox>
</div>
<div class="table-label double-line">编导开拍前需<br />准备道具列表</div>
<div class="table-cell col-span-5">
<div class="flex-items-center flex-wrap">
<el-checkbox v-for="item in toolOptions" :key="item.value"
v-model="form.tools[item.value]">
{{ item.label }}
</el-checkbox>
</div>
<div class="flex-items-center gap-8 mt-8">
<span class="color-666 w-40">其他</span>
<el-input v-model="form.otherTools" placeholder="输入其他需要准备的道具"
style="border-bottom: 1px solid #eee;" />
</div>
</div>
<div class="table-label">特殊需求</div>
<div class="table-cell col-span-5">
<el-input v-model="form.specialNeed" placeholder="请输入特殊需求,如:脚本、服装、动作注意事项" />
</div>
</div>
<div class="grid-row remark-row">
<div class="table-side flex-center font-bold">备注</div>
<div class="table-cell col-span-6">
<el-input v-model="form.remark" placeholder="请输入备注说明" />
</div>
</div>
<div class="grid-row shooting-row">
<div class="table-side flex-center font-bold shooting-side">拍摄拆解</div>
<div class="table-head required col-span-2">脚本分镜</div>
<div class="table-head required col-span-2">对话过程</div>
<div class="table-head">分镜头例图</div>
<div class="table-head">注意点</div>
<template v-for="(item, index) in form.shots" :key="item.id">
<div class="table-cell col-span-2">
<el-input v-model="item.script" type="textarea" :rows="3" resize="none"
placeholder="请输入脚本分镜,如:开场画面、镜头动作、产品特写" />
</div>
<div class="table-cell col-span-2">
<el-input v-model="item.dialog" type="textarea" :rows="3" resize="none"
placeholder="请输入对话过程/口播台词" />
</div>
<div class="table-cell">
<Upload :width="50" />
</div>
<div class="table-cell col-span-1 relative">
<el-input v-model="item.notice" type="textarea" :rows="3" resize="none"
placeholder="请输入注意点,如:拍摄重点、光线、构图、节奏" />
<div class="flex justify-end mt-8">
<el-button v-if="form.shots.length > 1" class="color-888" link size="small"
@click="removeShot(index)">
删除行
</el-button>
</div>
</div>
</template>
<div class="table-cell col-span-6 text-center">
<el-button type="primary" link class="font-bold" @click="addShot">+ 新增一行</el-button>
</div>
<div class="table-head">视频格式</div>
<div class="table-head">视频尺寸</div>
<div class="table-head">字幕类型</div>
<div class="table-head">视频画质</div>
<div class="table-head col-span-2">BGM</div>
<div class="table-cell text-center"><el-input v-model="form.videoFormat"
placeholder="请输入视频格式" /></div>
<div class="table-cell text-center"><el-input v-model="form.videoSize" placeholder="请输入视频尺寸" />
</div>
<div class="table-cell text-center"><el-input v-model="form.subtitle" placeholder="请输入字幕类型" />
</div>
<div class="table-cell text-center"><el-input v-model="form.quality" placeholder="请输入视频画质" />
</div>
<div class="table-cell col-span-2 text-center"><el-input v-model="form.bgm"
placeholder="请输入BGM要求" /></div>
</div>
<div class="grid-row spec-row">
<div class="table-side row-span-2 flex-center font-bold">过审必备</div>
<div class="table-head col-span-3">视频下方文案</div>
<div class="table-head col-span-3">其他文案</div>
<div class="table-cell col-span-3">
<el-input v-model="form.bottomCopy" type="textarea" :rows="3" resize="none"
placeholder="请输入视频下方文案" />
</div>
<div class="table-cell col-span-3">
<el-input v-model="form.otherCopy" type="textarea" :rows="3" resize="none"
placeholder="请输入其他文案或禁用词说明" />
</div>
</div>
</section>
</div>
<div class="px-24 py-14 flex justify-end gap-10 dialog-footer">
<el-button class="minw-64" @click="visible = false">取消</el-button>
<el-button type="primary" class="minw-90" @click="save">保存拆解表</el-button>
</div>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import Upload from '@/components/Upload/index.vue'
type ShotItem = {
id: number;
script: string;
dialog: string;
notice: string;
};
const props = withDefaults(
defineProps<{
breakdownId?: string;
}>(),
{
breakdownId: 'CJB-06131146256-7133',
}
);
const visible = defineModel<boolean>('visible', { required: true });
const emit = defineEmits<{
(event: 'save', form: any): void;
}>();
const toolOptions = [
{ label: '洗发露/护发素', value: 'shampoo' },
{ label: '精油', value: 'oil' },
{ label: '头皮喷雾', value: 'spray' },
{ label: '毛巾', value: 'towel' },
{ label: '镜子', value: 'mirror' },
{ label: '发箍', value: 'hairBand' },
{ label: '梳子', value: 'comb' },
{ label: '吹风机', value: 'dryer' },
{ label: '热水', value: 'hotWater' },
];
const form = reactive({
breakdownId: props.breakdownId,
photographer: '张华',
editor: '李明',
frameReviewer: '王强',
techReviewer: '赵雷',
model: 'Anna',
voice: '解说-男声1',
scene: '室内浴室/洗手台',
light: '柔和明亮暖光',
clothes: '简约居家服',
props: {
earring: true,
necklace: false,
},
tools: {
shampoo: true,
oil: false,
spray: false,
towel: true,
mirror: true,
hairBand: false,
comb: false,
dryer: false,
hotWater: false,
} as Record<string, boolean>,
otherTools: '',
specialNeed: '例如:淡水脚本,提醒模特自带服装',
remark: '请注意控制拍摄时长在15秒内,重点突出前后效果对比',
shots: [
{
id: 1,
script: '开场前3秒:模特拿洗发露特写,展示受损发质与修复效果对比',
dialog: '“头发干枯毛躁?别再用传统洗发水了!”',
notice: '抓取强对比特写,前3秒背景音效要吸引人',
},
{
id: 2,
script: '挤出产品质地特写,泡沫丰富绵密',
dialog: '“看看这绵密的云朵泡沫,蕴含多种植物精油”',
notice: '特写镜头需光线充足,突出泡沫光泽感',
},
] as ShotItem[],
videoFormat: 'MP4 / H.264',
videoSize: '9:16 竖屏 (1080x1920)',
subtitle: '双语花字字幕',
quality: '1080P / 60fps',
bgm: '轻快卡点轻音乐',
bottomCopy: '【特惠活动】点击下方链接,买一送一限量抢购!',
otherCopy: '请勿包含违禁词及夸大宣传词汇',
});
watch(
() => props.breakdownId,
(value) => {
form.breakdownId = value || 'CJB-06131146256-7133';
},
{ immediate: true }
);
const addShot = () => {
form.shots.push({
id: Date.now(),
script: '',
dialog: '',
notice: '',
});
};
const removeShot = (index: number) => {
form.shots.splice(index, 1);
};
const copyBreakdownId = () => {
ElMessage.success(`已复制:${form.breakdownId}`);
};
const recognize = () => {
ElMessage.success('识别完成');
};
const save = () => {
emit('save', form);
visible.value = false;
};
</script>
<style scoped lang="scss">
.dialog-header,
.dialog-toolbar,
.dialog-footer {
border-bottom: 1px solid #edf0f5;
}
.dialog-footer {
border-top: 1px solid #edf0f5;
border-bottom: none;
}
.breakdown-body {
max-height: calc(100vh - 210px);
overflow: auto;
}
.breakdown-table {
border: 1px solid #dce5f0;
}
.grid-row {
display: grid;
grid-template-columns: 96px repeat(6, 1fr);
}
.table-side,
.table-head,
.table-label,
.table-cell {
min-height: 34px;
padding: 8px 10px;
border-right: 1px solid #dce5f0;
border-bottom: 1px solid #dce5f0;
}
.table-side,
.table-head,
.table-label {
background-color: #f8fbff;
color: #40556f;
font-weight: bold;
}
.table-cell {
background-color: #fff;
}
.row-span-2 {
grid-row: span 2;
}
.row-span-5 {
grid-row: span 5;
}
.col-span-2 {
grid-column: span 2;
}
.col-span-3 {
grid-column: span 3;
}
.col-span-5 {
grid-column: span 5;
}
.col-span-6 {
grid-column: span 6;
}
.double-line {
line-height: 18px;
}
.required::before {
content: '*';
color: #f03b65;
}
.shooting-side {
grid-row: span 7;
}
.upload-box {
border: 1px dashed #d7e1ee;
background-color: #fbfdff;
}
.breakdown-dialog {
:deep(.el-textarea__inner) {
box-shadow: 0 0 0 1px #dce4f0 inset;
}
:deep(.el-input__wrapper) {
box-shadow: none;
}
:deep(.el-textarea__inner) {
min-height: 82px !important;
}
}
</style>
...@@ -8,13 +8,12 @@ ...@@ -8,13 +8,12 @@
</div> </div>
</div> </div>
<div class="flex-items-center gap-10"> <div class="flex-items-center gap-10">
<el-date-picker v-model="publishRange" type="daterange" start-placeholder="2026-06-01" <el-date-picker v-model="publishRange" type="daterange" start-placeholder="发布开始日期"
end-placeholder="2026-06-30" range-separator="至" style="width: 220px;" /> end-placeholder="发布开始日期" range-separator="至" style="width: 240px;" />
<el-date-picker v-model="outputRange" type="daterange" start-placeholder="出片开始日期" <el-date-picker v-model="outputRange" type="daterange" start-placeholder="出片开始日期"
end-placeholder="出片结束日期" range-separator="至" style="width: 240px;" /> end-placeholder="出片结束日期" range-separator="至" style="width: 240px;" />
</div> </div>
</section> </section>
<section class="bg-fff round-6 p-14 mt-16 filter-card"> <section class="bg-fff round-6 p-14 mt-16 filter-card">
<div class="flex flex-wrap gap-10"> <div class="flex flex-wrap gap-10">
<div class="flex filter-combo"> <div class="flex filter-combo">
...@@ -22,7 +21,7 @@ ...@@ -22,7 +21,7 @@
<el-option label="发布人" value="publisher" /> <el-option label="发布人" value="publisher" />
<el-option label="部门" value="department" /> <el-option label="部门" value="department" />
</el-select> </el-select>
<el-select v-model="filters.publisher" filterable clearable class="w-210" placeholder="请选择(支持输入搜索)"> <el-select v-model="filters.publisher" filterable clearable class="w-210" placeholder="请选择发布人">
<el-option v-for="item in publisherOptions" :key="item.value" :label="item.label" <el-option v-for="item in publisherOptions" :key="item.value" :label="item.label"
:value="item.value" /> :value="item.value" />
</el-select> </el-select>
...@@ -32,8 +31,7 @@ ...@@ -32,8 +31,7 @@
<el-option label="出片人" value="output" /> <el-option label="出片人" value="output" />
<el-option label="小组" value="group" /> <el-option label="小组" value="group" />
</el-select> </el-select>
<el-select v-model="filters.outputUser" filterable clearable class="w-210" <el-select v-model="filters.outputUser" filterable clearable class="w-210" placeholder="请选择出片人">
placeholder="请选择出片人(支持输入搜索)">
<el-option v-for="item in outputUserOptions" :key="item.value" :label="item.label" <el-option v-for="item in outputUserOptions" :key="item.value" :label="item.label"
:value="item.value" /> :value="item.value" />
</el-select> </el-select>
...@@ -173,28 +171,31 @@ ...@@ -173,28 +171,31 @@
</el-table-column> </el-table-column>
<el-table-column prop="product" width="150"> <el-table-column prop="product" width="150">
<template #header> <template #header>
<span>产品</span> <span>产品</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="scriptType" width="150"> <el-table-column prop="scriptType" width="150">
<template #header> <template #header>
<span>脚本类型</span> <span>脚本类型</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="splitStatus" label="脚本拆解表" width="140" fixed="right"> <el-table-column prop="splitStatus" label="脚本拆解表" width="140" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="showAction(row, '填写拆解表')">{{ row.splitStatus <el-button type="primary" link @click="openBreakdownDialog(row)">{{ row.splitStatus
}}</el-button> }}</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="px-16 py-12 flex-between table-footer"> <div class="px-16 py-12 flex-between table-footer">
<span class="color-000">共 {{ filteredList.length }} 条</span> <span class="color-000">共 {{ filteredList.length }} 条</span>
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[50, 100]" <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
background layout="sizes, prev, pager, next, jumper" :total="filteredList.length" /> :page-sizes="[10, 20, 30, 50, 100]" background layout="sizes, prev, pager, next, jumper"
:total="filteredList.length" />
</div> </div>
</section> </section>
<TaskPublishDialog v-model:visible="visiblePublish" @confirm="handlePublishConfirm" /> <TaskPublishDialog v-model:visible="visiblePublish" @confirm="handlePublishConfirm" />
<ScriptBreakdownDialog v-model:visible="visibleBreakdown" :breakdown-id="currentBreakdownId"
@save="handleBreakdownSave" />
</div> </div>
</template> </template>
...@@ -202,6 +203,7 @@ ...@@ -202,6 +203,7 @@
import { computed, reactive, ref } from 'vue'; import { computed, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import TaskPublishDialog from '@/components/TaskPublishDialog/index.vue'; import TaskPublishDialog from '@/components/TaskPublishDialog/index.vue';
import ScriptBreakdownDialog from './components/BreakdownDialog.vue';
type TaskItem = { type TaskItem = {
id: number; id: number;
...@@ -226,6 +228,8 @@ type TaskItem = { ...@@ -226,6 +228,8 @@ type TaskItem = {
const activeTab = ref('all'); const activeTab = ref('all');
const activeStatus = ref('all'); const activeStatus = ref('all');
const visiblePublish = ref(false); const visiblePublish = ref(false);
const visibleBreakdown = ref(false);
const currentBreakdownId = ref('CJB-06131146256-7133');
const sortType = ref('publishTime'); const sortType = ref('publishTime');
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(50); const pageSize = ref(50);
...@@ -508,6 +512,15 @@ const showAction = (row: TaskItem, action: string) => { ...@@ -508,6 +512,15 @@ const showAction = (row: TaskItem, action: string) => {
ElMessage.success(`${action}: ${row.taskId}`); ElMessage.success(`${action}: ${row.taskId}`);
}; };
const openBreakdownDialog = (row: TaskItem) => {
currentBreakdownId.value = `CJB-${row.taskId}-7133`;
visibleBreakdown.value = true;
};
const handleBreakdownSave = (form: any) => {
ElMessage.success(`已保存拆解表:${form.breakdownId}`);
};
const publishTask = () => { const publishTask = () => {
visiblePublish.value = true; visiblePublish.value = true;
}; };
......
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