Files
lingji-work-fe/src/views/property-marketing/assignment-management/components/draw-popup.vue
2025-09-24 18:35:09 +08:00

626 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-drawer
title="创建任务"
cancel-text="取消"
ok-text="创建任务"
placement="right"
v-model:visible="showDriwer"
@after-visible-change="showDriwerChange"
@ok="handleCreateTask"
width="480px"
class="task-drawer"
style="z-index: 999"
>
<div class="drawer-content" :style="isActive == 'ai' ? 'height: 376px;' : 'height:216px;'">
<div class="flex flex-col justify-center">
<CommonSelect
v-model="localQuery.accounts"
:options="accountList || []"
:multiple="false"
@change="(val) => handleChange('accounts', val)"
class="!w-432px select-with-tags"
placeholder="请选择账号名称"
:allowSearch="true"
:maxTagCount="999"
popup-container=".filter-popup-content"
/>
<div class="ai-content-generator">
<div class="flex mt-16px">
<Button
class="w-194px h-38px mr-8px"
:class="isActive == 'ai' ? 'active-chose' : ''"
@click="handleSelect('ai')"
>
<template #icon>
<img :src="aiIcon" class="w-16 h-16 mr-8px" />
</template>
AI生成
</Button>
<Button
class="w-194px h-38px"
:class="isActive == 'chose' ? 'active-chose' : ''"
@click="handleSelect('chose')"
>
从成品库选择</Button
>
</div>
<div v-show="isActive == 'ai'">
<!-- 任务描述区域 -->
<div class="form-section">
<div class="flex items-center w-400px mt-16px mb-8px">
<div class="section-title">任务描述</div>
<div class="font-size-12px text-[#999999]">非必填</div>
</div>
<div class="w-400px h-126px border-rounded-8px mb-8px" style="background: #fff">
<a-textarea
placeholder="描述你想让AI帮你生成的内容。未填写时AI 会参考账号历史内容的题材与行业方向,结合当下话题,自动生成文案和图片后发布"
class="task-description font-size-12px"
:rows="5"
v-model="taskDescription"
/>
</div>
</div>
<!-- 素材添加区域 -->
<div class="form-section material-section">
<div class="flex items-center"></div>
<Button class="add-material-btn" @click="handleAddMaterial">
<template #icon>
<icon-plus size="16" class="mr-8px" />
</template>
从原料库添加
</Button>
<div v-if="hasChoseMaterial" class="flex flex-col items-center w-full">
<div
v-for="item in selectedMaterials.texts"
:key="item.id"
class="flex items-center bg-#F7F8FA border-rounded-8px w-full justify-items-center pt-8px pb-8px pl-12px pr-12px mb-16px"
>
{{ item }}
</div>
<div class="flex items-center w-full">
<img
v-for="item in selectedMaterials.images"
:key="item.id"
:src="item.cover"
class="w-88 h-88 mr-8px border-rounded-8px"
/>
</div>
</div>
<div v-else class="flex flex-col items-center">
<p class="material-hint">AI会参考添加的文本图片视频等素材完成符合需求的创作</p>
</div>
</div>
</div>
<div v-show="isActive == 'chose'">
<!-- 任务描述区域 -->
<div class="form-section">
<div class="flex items-center w-400px mt-16px mb-8px">
<div class="section-title">发布内容</div>
<div class="font-size-12px text-[#999999]">必填</div>
</div>
<div class="form-section material-section" v-if="hasChoseFinishedProducts == false">
<Button @click="handleAddContent" class="add-material-btn">
<template #icon>
<icon-plus size="16" class="mr-8px" />
</template>
添加内容
</Button>
<p class="material-hint">前往成品库选择要发布的内容</p>
</div>
<div v-else class="flex flex-col items-start w-full content-center">
<div class="opt-btn">
<SwapOutlined class="bg-#00000060 p-4px rounded-4px cursor-pointer" @click="handleAddContent" />
<DeleteOutlined
style="margin-left: 16px"
class="bg-#00000060 p-4px rounded-4px cursor-pointer"
@click="handleDelte"
/>
</div>
<div class="mb-12px">{{ selectedProducts.data[0].title }}</div>
<div v-for="item in selectedProducts.images" :key="item.id">
<img v-if="item.cover" :src="item.cover" class="w-88 h-88 mr-8px border-rounded-8px mb-12px" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 发布计划区域 -->
<div class="publish-section">
<div class="flex items-center justify-between w-384px">
<div class="section-title">发布计划</div>
<CommonSelect
v-model="publishType"
:options="publishOptions"
@change="handlePublishTypeChange"
class="w-180px background-#fff publish-type-select"
:allowSearch="false"
:multiple="false"
popup-container=".filter-popup-content"
/>
</div>
<div v-if="publishType === 'timing'">
<div class="line"></div>
<div class="flex items-center justify-between mt-16px w-384px">
<div class="section-title">日期</div>
<a-date-picker
class="w-180px h-40px background-#fff"
@change="handleDateChange"
v-model="currentDate"
format="YYYY年MM月DD日周dd"
value-format="YYYY-MM-DD"
/>
</div>
<div class="line"></div>
<div class="flex items-center justify-between mt-16px w-384px">
<div class="section-title">时间</div>
<a-time-picker v-model="strValue" format="HH:mm" class="w-180px h-40px background-#fff" />
</div>
</div>
</div>
</div>
<!-- 原料库子组件使用:visible和@update:visible替代v-model -->
<RawMaterialDrawer
:visible="showDrawer2"
:query="materialQuery"
@after-visible-change="handleMaterialDrawerVisibleChange"
@confirm="handleMaterialConfirm"
@cancel="handleMaterialCancel"
/>
<!-- 成品库子组件使用:visible和@update:visible替代v-model -->
<FinishedProductDrawer
:visible="showDrawer3"
@update:visible="(val) => (showDrawer3 = val)"
:query="productQuery"
@after-visible-change="handleProductDrawerVisibleChange"
@confirm="handleProductConfirm"
@cancel="handleProductCancel"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import { getWorksPage } from '@/api/all/generationWorkshop.ts';
import { ref, reactive, watch, nextTick } from 'vue';
import aiIcon from '@/assets/img/media-account/icon-AI.png';
import { SwapOutlined, DeleteOutlined } from '@ant-design/icons-vue';
import { Button, DatePicker, TimePicker } from 'ant-design-vue';
import { TABS_LIST, ORIGIN_LIST, RawMaterialType } from '@/views/material-center/components/raw-material/constants';
import dayjs from 'dayjs';
import { getRawMaterialsPage } from '@/api/all/generationWorkshop';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png';
import icon4 from '@/assets/img/error-img.png';
// 引入子组件
import RawMaterialDrawer from './raw-material-drawer.vue';
import FinishedProductDrawer from './finished-product-drawer.vue';
import { message } from 'ant-design-vue';
import { getMediaAccountList } from '@/api/all/propertyMarketing';
// 平台图标
import iconDy from '@/assets/img/platform/icon-dy.png';
import iconXhs from '@/assets/img/platform/icon-xhs.png';
import iconBilibili from '@/assets/img/platform/icon-bilibili.png';
import iconKs from '@/assets/img/platform/icon-ks.png';
import iconSph from '@/assets/img/platform/icon-sph.png';
import iconWb from '@/assets/img/platform/icon-wb.png';
import iconGzh from '@/assets/img/platform/icon-gzh.png';
import iconWarn from '@/assets/img/media-account/icon-warn.png';
// 状态管理
const choseText = ref('');
const taskDescription = ref('');
const hasChoseMaterial = ref(false);
const hasChoseFinishedProducts = ref(false);
const isActive = ref('ai');
const showDriwer = ref(false);
const showDrawer2 = ref(false);
const showDrawer3 = ref(false);
const accountList = ref([]);
onMounted(() => {
getData();
});
// 平台配置
const platformConfig = {
icons: { 0: iconDy, 1: iconXhs, 2: iconBilibili, 3: iconKs, 4: iconSph, 5: iconWb, 6: iconGzh },
names: { 0: '抖音', 1: '小红书', 2: 'B站', 3: '快手', 4: '视频号', 5: '微博', 6: '公众号' },
options: [
{ id: 0, name: '抖音', icon: iconDy },
{ id: 1, name: '小红书', icon: iconXhs },
{ id: 2, name: 'B站', icon: iconBilibili },
{ id: 3, name: '快手', icon: iconKs },
{ id: 4, name: '视频号', icon: iconSph },
{ id: 5, name: '微博', icon: iconWb },
{ id: 6, name: '公众号', icon: iconGzh },
],
};
const platformOptions = ref(platformConfig.options);
// 工具函数
const getPlatformIcon = (platform: number) => platformConfig.icons[platform] || iconWarn;
const getPlatformName = (platform: number) => platformConfig.names[platform] || '未知平台';
const getData = async () => {
try {
const { code, data: accountData } = await getMediaAccountList();
if (code === 200) {
accountList.value = accountData.map((account: any) => ({
value: account.id,
name: `${account.name}(${getPlatformName(account.platform)})`,
platform: account.platform,
icon: getPlatformIcon(account.platform),
}));
}
} catch (error) {
console.error('获取账号列表失败:', error);
}
};
// 发布类型选项
const publishOptions = ref([
{ value: 'immediate', label: '立即发布' },
{ value: 'timing', label: '定时发布' },
]);
// 发布类型,默认为立即发布
const publishType = ref('immediate');
// 时间选择相关
const currentDate = ref(new Date());
const strValue = ref();
// 定义props和emit
const props = defineProps({
operators: Array,
platformOptions: Array,
accountList: Array,
query: Object,
});
// 本地筛选状态(保持上次选择)
const localQuery = ref({
accounts: props.query?.name || [],
ids: props.query?.ids || [],
});
// 原料库查询参数
const materialQuery = reactive({
page: 1,
page_size: 10,
platforms: undefined,
operator_ids: undefined,
ids: [],
top_execution_time: undefined,
});
// 成品库查询参数
const productQuery = reactive({
page: 1,
page_size: 10,
platforms: undefined,
operator_ids: undefined,
ids: [],
});
// 选中的素材数据
const selectedMaterials = ref({
keys: [],
data: [],
text: '',
images: [],
texts: [],
});
const selectedProducts = ref({
keys: [],
data: [],
text: '',
images: [],
});
// 处理AI/成品库选择切换
const handleSelect = (value) => {
isActive.value = value;
if (value === 'ai') {
showDrawer3.value = false;
} else {
showDrawer2.value = false;
}
};
// 打开原料库抽屉
const handleAddMaterial = () => {
materialQuery.page = 1;
materialQuery.page_size = 10;
showDrawer2.value = true;
};
// 打开成品库抽屉
const handleAddContent = () => {
productQuery.page = 1;
productQuery.page_size = 10;
showDrawer3.value = true;
};
const handleDelte = () => {
hasChoseFinishedProducts.value = false;
selectedProducts.value = {
keys: [],
data: [],
text: '',
images: [],
};
};
// 处理原料库选择确认
const handleMaterialConfirm = (result) => {
console.log('handleMaterialConfirm', result);
selectedMaterials.value = {
keys: result.selectedKeys,
data: result.selectedData,
text: result.choseText,
images: result.choseImgArray,
texts: result.selectedTexts || [],
};
hasChoseMaterial.value = result.selectedKeys.length > 0;
};
// 处理原料库取消
const handleMaterialCancel = () => {
// 取消逻辑
showDrawer2.value = false;
};
// 处理成品库选择确认
const handleProductConfirm = (result) => {
selectedProducts.value = {
keys: result.selectedKeys,
data: result.selectedData,
text: result.choseText,
images: result.choseImgArray,
};
// 如果是单选模式,确保只选择一个项目
if (result.selectedRows && result.selectedRows.length > 0) {
hasChoseFinishedProducts.value = true;
// 取第一个选中的项目
const selectedProduct = result.selectedRows[0];
selectedProducts.value = {
keys: [selectedProduct.id],
data: [selectedProduct],
text: '1个稿件',
images: [selectedProduct],
};
}
};
// 处理成品库取消
const handleProductCancel = () => {
// 取消逻辑
showDrawer3.value = false;
};
// 处理发布类型变化
const handlePublishTypeChange = (value) => {
publishType.value = value;
};
// 处理日期变化
const handleDateChange = (date) => {
// 日期处理逻辑
};
// 抽屉显示状态变化
const showDriwerChange = (visible: boolean) => {
console.log('Main Drawer visible: ', visible);
};
// 原料库抽屉显示状态变化
const handleMaterialDrawerVisibleChange = (visible: boolean) => {
console.log('Raw Material Drawer visible: ', visible);
};
// 成品库抽屉显示状态变化
const handleProductDrawerVisibleChange = (visible: boolean) => {
console.log('Finished Product Drawer visible: ', visible);
};
// 处理筛选条件变化
const handleChange = (field, value) => {
localQuery.value[field] = value;
localQuery.value.ids = [value];
emit('filter-change', {
accounts: localQuery.value.accounts,
});
};
// 点击创建任务按钮时触发
const handleCreateTask = () => {
// 验证表单
if (localQuery.value.ids.length == 0) {
// 可以添加错误提示:请选择发布内容
message.error('请选择发布账号');
return;
}
if (isActive.value === 'chose' && selectedProducts.value.keys.length === 0) {
// 可以添加错误提示:请选择发布内容
message.error('请选择发布内容');
return;
}
if (isActive.value === 'ai' && selectedMaterials.value.keys.length === 0) {
// 可以添加错误提示:请选择发布内容
message.error('请选择发布内容');
return;
}
console.log('有问题已返回');
// 准备提交的数据
const taskData = {
media_account_id: localQuery.value.ids[0],
is_ai_generate: isActive.value == 'chose' ? 0 : 1,
ai_prompt: taskDescription.value,
raw_material_ids: selectedMaterials.value.keys,
products: selectedProducts.value.keys,
publish_type: publishType.value == 'immediate' ? 0 : 1,
execution_time:
publishType.value === 'timing' ? `${dayjs(currentDate.value).format('YYYY-MM-DD')} ${strValue.value}` : undefined,
work_id: selectedProducts.value.keys[0],
};
// 发射创建任务事件
emit('create-task', taskData);
// 关闭抽屉
showDriwer.value = false;
};
// 暴露方法给父组件
const showDrawer = (accountInfo = null, selectedDate = null) => {
showDriwer.value = true;
if (accountInfo && accountInfo.id) {
nextTick(() => {
localQuery.value.accounts = [accountInfo.name];
localQuery.value.ids = [accountInfo.id];
});
}
// 如果传入了日期,则设置为默认日期
if (selectedDate) {
currentDate.value = selectedDate;
console.log('currentDate', currentDate.value);
publishType.value = 'timing';
}
};
// 定义事件发射器
const emit = defineEmits(['filter-change', 'create-task']);
// 暴露方法
defineExpose({
showDrawer,
});
</script>
<style scoped>
.drawer-content {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.arco-drawer {
border-top-left-radius: 8px !important;
border-bottom-left-radius: 8px !important;
}
.ai-content-generator {
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
margin-top: 24px;
width: 432px;
background: linear-gradient(to right, #f0f5ff, #fff6f5) !important;
border-radius: 8px;
}
.active-chose {
border: #722ed1 1px solid !important;
}
.form-section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 400px;
border-radius: 8px;
padding: 8px;
}
.publish-section {
width: 432px;
padding: 16px 24px;
background-color: #f7f8fa;
display: flex;
flex-direction: column;
border-radius: 8px;
align-items: start;
justify-content: space-between;
margin-top: 24px;
}
.section-title {
font-size: 14px;
color: #333;
font-weight: 500;
}
.task-description {
width: 100%;
font-size: 14px;
height: 126px;
border: none !important;
box-shadow: none !important;
outline: none !important;
}
.task-description::placeholder {
color: #999;
}
.material-section {
border-radius: 8px;
text-align: center;
background-color: #fff;
margin-bottom: 8px;
padding: 16px;
}
.add-material-btn {
width: 240px;
height: 38px;
border-radius: 8px;
font-size: 14px;
border: 1px dashed #e0e0e0 !important;
margin-bottom: 8px;
}
.material-hint {
color: #939499;
font-size: 14px;
margin: 0 40px;
}
.content-center {
position: relative;
}
.opt-btn {
display: flex;
position: absolute; /* 设置为绝对定位 */
top: 0; /* 紧贴顶部 */
right: 0; /* 紧贴右侧 */
}
.publish-type-select :deep(.ant-select-selection-item) {
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.select-with-tags :deep(.ant-select-selection-item) {
text-align: start;
display: flex;
align-items: center;
justify-content: start;
}
.line {
background-color: #e6e6e8;
width: 382px;
height: 1px;
margin-top: 16px;
}
</style>