Files
lingji-work-fe/src/views/property-marketing/assignment-management/index.vue
2025-09-25 16:33:52 +08:00

691 lines
20 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>
<div class="flex-column justify-between bg-#fff rounded-8px p-24px">
<!-- 日期选择器和筛选区域 -->
<div class="flex justify-between items-start w-full">
<DateSelector v-model="dateSelectorModel" @date-change="handleDateSelectorChange" />
<div class="flex items-start">
<colorTip />
<FilterPopup
:operators="operators"
:platformOptions="platformOptions"
:accountList="accountList"
:query="query"
@filter-change="handleFilterChange"
/>
<Button type="primary" class="w-112px" size="middle" @click="handleAddTask">
<template #default>创建任务</template>
</Button>
</div>
</div>
<!-- 表格内容区域 -->
<div class="flex justify-between items-start w-full">
<div class="table-wrap py-24px flex flex-col" style="width: 100%; overflow-x: auto">
<a-table
:columns="columns"
:data="data"
:bordered="{ cell: true }"
:scroll="{ x: 'max-content' }"
style="width: 100%"
:pagination="false"
@change="handleTableChange"
@cell-click="handleCellClick"
>
<!-- 空数据显示 -->
<template #empty>
<div class="flex flex-col items-center justify-center" style="min-height: 400px">
<img :src="emptyIcon" class="img mt-20px" alt="暂无数据" width="106" height="72" />
<div class="text mt-36px">暂无数据</div>
<div class="mt-12px mb-12px">可通过账号管理添加账号进行任务排期管理</div>
<a-button type="primary" @click="handleAddAccount">去添加</a-button>
</div>
</template>
<!-- 账号与平台列 -->
<template #name="{ record }">
<div class="flex items-center justify-start">
<img
:src="getPlatformIcon(record.platform)"
style="border-radius: 8px; width: 16px; height: 16px; margin-right: 8px; margin-left: 20px"
:alt="getPlatformName(record.platform)"
/>
{{ record.name || '-' }}
</div>
</template>
<!-- 动态日期单元格 -->
<template #dateCell="{ record, column }">
<div v-if="record[column.dataIndex]?.length" class="task-container">
<!-- 任务数量3时显示更多 -->
<div v-if="record[column.dataIndex].length >= 3" class="task-more">
<TaskItem :task="record[column.dataIndex][0]" :record="record" @handle-task="handleTaskAction" />
<a-trigger trigger="click" position="br">
<div class="size-12px color-#8f959f h-19px ml-4px rounded-2px cursor-pointer" @click.stop>
还有{{ record[column.dataIndex].length - 1 }}
</div>
<template #content>
<div class="bg-#fff w-160px p-12px rounded-4px flex flex-col more-content">
<TaskItem
v-for="task in record[column.dataIndex]"
:key="task.id"
:task="task"
:record="record"
@handle-task="handleTaskAction"
/>
</div>
</template>
</a-trigger>
</div>
<!-- 任务数量<3时直接显示 -->
<div v-else>
<TaskItem
v-for="task in record[column.dataIndex]"
:key="task.id"
:task="task"
:record="record"
@handle-task="handleTaskAction"
/>
</div>
</div>
<div v-else class="no-task"></div>
</template>
</a-table>
<!-- 分页控件 -->
<div v-if="pageInfo.total > 0" class="pagination-box">
<a-pagination
:total="pageInfo.total"
size="mini"
show-total
show-jumper
show-page-size
:current="pageInfo.page"
:page-size="pageInfo.page_size"
@change="onPageChange"
@page-size-change="onPageSizeChange"
/>
</div>
</div>
</div>
</div>
<!-- 删除确认弹窗 -->
<a-modal v-model:visible="showModal" @ok="handleDeleteConfirm" @cancel="showModal = false" ok-text="确认删除">
<template #title>{{ deleteTitle }}</template>
<div>{{ deleteContent }}</div>
</a-modal>
<DrowPopup ref="drawerPopupRef" @create-task="handleCreateTask" />
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, computed, inject } from 'vue';
import type { ColumnProps } from 'ant-design-vue/es/table';
import router from '@/router';
import DateUtils from '@/utils/DateUtils';
// 组件引入
import DateSelector from './components/date-selector.vue';
import colorTip from './components/colorTip.vue';
import FilterPopup from './components/filter-popup.vue';
import DrowPopup from './components/draw-popup.vue';
import TaskItem from './components/task-item.vue';
// API引入
import {
getTaskSchedules,
delTaskSchedules,
editTaskSchedulesTime,
createTask,
generateContent,
} from '@/api/all/assignment-management';
import { fetchAccountOperators, getMediaAccountList } from '@/api/all/propertyMarketing';
// 工具引入
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
// 静态资源
import emptyIcon from '@/assets/img/media-account/icon-empty.png';
// 平台图标
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';
import { Checkbox, Button, Space, Pagination, notification } from 'ant-design-vue';
import { message } from 'ant-design-vue';
// 表格分页逻辑
const { pageInfo, onPageChange, onPageSizeChange } = useTableSelectionWithPagination({
onPageChange: () => handleSearch(),
onPageSizeChange: () => handleSearch(),
});
// 状态管理
const dateSelectorModel = ref({
choseType: '周' as '日' | '周' | '月',
dayModel: new Date(),
weekModel: new Date(),
monthModel: new Date(),
});
const columns = ref<ColumnProps[]>([]);
const data = ref<any[]>([]);
const operators = ref([]);
const accountList = ref([]);
const showModal = ref(false);
const currentTask = ref<any>(null);
const deleteTitle = ref('');
const deleteContent = ref('');
// 获取当前周的日期范围
const getCurrentWeekRange = () => {
const weekRange = DateUtils.getWeekRangeByDate(new Date());
return [weekRange.startFormatted, weekRange.endFormatted];
};
// 查询参数
const query = reactive({
page: pageInfo.value.page,
page_size: pageInfo.value.page_size,
platforms: undefined,
operator_ids: undefined,
ids: [],
execution_time: getCurrentWeekRange(), // 设置默认值为当前周
top_execution_time: undefined,
});
// 平台配置
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 timestampToDayNumber = (timestamp: number) => {
return new Date(timestamp * 1000).getDate();
};
// 处理表格数据
const processTableData = (apiData: any[]) => {
const processedData: any[] = [];
const dateHeaders = currentDateHeaders.value;
apiData.forEach((account) => {
const rowData: any = {
id: account.id,
name: account.name,
platform: account.platform,
};
// 初始化日期列
dateHeaders.forEach((day) => {
rowData[day] = [];
});
// 分配任务到对应日期列
if (account.task_schedules?.length) {
account.task_schedules.forEach((task: any) => {
const taskDay = timestampToDayNumber(task.execution_time);
if (dateHeaders.includes(taskDay)) {
rowData[taskDay].push(task);
}
});
}
processedData.push(rowData);
});
return processedData;
};
// 创建任务
const handleAddTask = () => {
drawerPopupRef.value?.showDrawer();
};
const handleCreateTask = async (value) => {
const res = await createTask(value);
if (res && res.code === 200) {
message.success('创建成功');
handleSearch();
}
};
// 添加对DrowPopup组件的引用
const drawerPopupRef = ref();
// 设置表格列
const setTableColumns = () => {
columns.value = [
{
title: '账号与发布平台',
slotName: 'name',
width: 150,
ellipsis: true,
tooltip: true,
fixed: 'left', // 固定列
},
];
let dateHeaders: any[] = [];
const { choseType } = dateSelectorModel.value;
if (choseType === '周') {
dateHeaders = DateUtils.getWeekDaysByDate(dateSelectorModel.value.weekModel, 0);
} else if (choseType === '月') {
const date = dateSelectorModel.value.monthModel;
dateHeaders = DateUtils.getDaysAndWeekdays(date.getFullYear(), date.getMonth());
} else {
const date = dateSelectorModel.value.dayModel;
dateHeaders = [
{
day: date.getDate(),
weekday: DateUtils.formatDateToWeekdayDay(date),
date,
},
];
}
// 判断是否为今天
const isToday = (date: Date) => {
if (!date) return false;
const today = new Date();
today.setHours(0, 0, 0, 0);
const targetDate = new Date(date);
targetDate.setHours(0, 0, 0, 0);
return targetDate.getTime() === today.getTime();
};
// 添加日期列
dateHeaders.forEach((item) => {
const isWeekend = item.date?.getDay() === 0 || item.date?.getDay() === 6;
// 确保item.date存在再进行判断
const todayFlag = item.date ? isToday(item.date) : false;
// 调试信息
if (todayFlag) {
console.log('Today column detected:', item);
console.log('Today flag value:', todayFlag);
}
const columnConfig = {
title: `${item.weekday}`,
dataIndex: item.day,
slotName: 'dateCell',
date: item.date,
width: 135,
sortable: {
sortDirections: ['ascend', 'descend'],
sorter: true,
},
weekday: item.weekday,
todayFlag,
// 为周末设置特定的背景色
cellClass: isWeekend ? 'weekend-column' : '',
// 为今日添加特殊类名
class: todayFlag ? 'today-column' : '',
};
// 更多调试信息
if (todayFlag) {
console.log('Column config for today:', columnConfig);
}
columns.value.push(columnConfig);
});
};
// 当前日期头部计算
const currentDateHeaders = computed(() => {
const { choseType } = dateSelectorModel.value;
if (choseType === '周') {
return DateUtils.getWeekDaysByDate(dateSelectorModel.value.weekModel, 0).map((item) =>
parseInt(item.day.toString(), 10),
);
} else if (choseType === '月') {
const date = dateSelectorModel.value.monthModel;
return DateUtils.getDaysAndWeekdays(date.getFullYear(), date.getMonth()).map((item) =>
parseInt(item.day.toString(), 10),
);
} else {
return [dateSelectorModel.value.dayModel.getDate()];
}
});
// 数据获取
const handleSearch = () => {
query.page = pageInfo.value.page;
query.page_size = pageInfo.value.page_size;
getTaskSchedules(query)
.then((response) => {
if (response.data) {
const apiData = response.data.data || response.data;
if (apiData) {
data.value = processTableData(apiData);
}
pageInfo.value.total = response.data.total || apiData.length;
}
})
.catch(() => {
data.value = [];
});
};
// 添加一个标志位来避免死循环
let isDateSelectorUpdating = false;
// 日期选择器变化处理
const handleDateSelectorChange = (value: any) => {
// 如果正在更新中,则跳过
if (isDateSelectorUpdating) {
return;
}
const newStartDate = value.dateRange.start;
const newEndDate = value.dateRange.end;
// 添加空值检查以避免数组越界错误
const currentStart =
Array.isArray(query.execution_time) && query.execution_time.length > 0 ? query.execution_time[0] : null;
const currentEnd =
Array.isArray(query.execution_time) && query.execution_time.length > 1 ? query.execution_time[1] : null;
// 如果日期范围没有变化,则不执行后续操作
if (currentStart === newStartDate && currentEnd === newEndDate) {
return;
}
// 设置标志位
isDateSelectorUpdating = true;
query.execution_time = [newStartDate, newEndDate];
setTableColumns();
handleSearch();
// 在下一个事件循环重置标志位
setTimeout(() => {
isDateSelectorUpdating = false;
}, 0);
};
// 筛选条件变化处理
const handleFilterChange = (filters: any) => {
console.log(filters);
if (typeof filters === 'object' && filters !== null) {
Object.keys(filters).forEach((key) => {
switch (key) {
case 'operator':
query.operator_ids = filters[key];
break;
case 'platform':
query.platforms = filters[key];
break;
case 'accounts':
query.ids = filters[key];
break;
default:
query[key] = filters[key];
}
});
console.log(query);
handleSearch();
}
};
// 表格排序变化
const handleTableChange = (pagination: any, sorter: any) => {
if (sorter && sorter.sorter?.direction === 'ascend' && sorter.sorter?.field) {
const column = columns.value.find((col) => col.dataIndex == sorter.sorter.field);
if (column?.date) {
query.top_execution_time = DateUtils.formatDate(column.date);
}
} else {
query.top_execution_time = undefined;
}
handleSearch();
};
const handleCellClick = (record: any, rowIndex: any, column: any) => {
const accountInfo = {
id: record.id,
name: record.name,
platform: record.platform,
};
const selectedDate = rowIndex.date;
// 检查选中的日期是否小于今天,如果是则不处理
const today = new Date();
today.setHours(0, 0, 0, 0);
const selectedDateTime = new Date(selectedDate);
selectedDateTime.setHours(0, 0, 0, 0);
if (selectedDateTime < today) {
console.log('选择的日期已过去,不打开抽屉');
return;
}
console.log('selectedDate', selectedDate, accountInfo);
drawerPopupRef.value.showDrawer(accountInfo, selectedDate);
};
// 任务操作处理
const handleTaskAction = async (action: string, task: any, ...args: any[]) => {
console.log('handleTaskAction', action, task, args);
switch (action) {
case 'delete':
currentTask.value = task;
deleteTitle.value = task.type === 1 ? '删除内容稿件排期' : '删除选题排期';
deleteContent.value = `确认删除“${task.name || 'AI生成内容'}”吗?`;
showModal.value = true;
break;
case 'edit-time':
console.log('handleTaskAction edit-time', task, args);
editTaskSchedulesTime(task.id, { execution_time: args[0] }).then((res) => {
if (res.code === 200) {
message.success(res.message);
handleSearch();
}
});
break;
case 'goto-detail':
router.push(`/media-account/management-detail/${task.id}`);
break;
case 'ai-create':
const res = await generateContent(task.id);
if (res.code === 200) {
message.success(res.message);
}
break;
case 'edit-task':
console.log('edit-task', args[0], typeof args[0]);
const accountInfo = {
id: args[0].id,
name: args[0].name,
platform: args[0].platform,
};
const selectedDate = task.execution_time;
const date = new Date(selectedDate);
// 显示抽屉
drawerPopupRef.value.showDrawer(accountInfo, date);
// 等待抽屉打开后再填充数据
nextTick(() => {
// 将任务信息回填到draw-popup组件
drawerPopupRef.value.fillTaskData(task);
});
break;
}
};
// 确认删除
const handleDeleteConfirm = () => {
if (currentTask.value) {
delTaskSchedules(currentTask.value.id).then(() => {
showModal.value = false;
handleSearch();
});
}
};
// 添加账号
const handleAddAccount = () => {
// 跳转到添加账号页面的逻辑
router.push('/media-account/add');
};
// 获取运营人员列表
const getOperators = async () => {
try {
const { code, data: operatorsData } = await fetchAccountOperators();
if (code === 200) {
operators.value = operatorsData.map((op: any) => ({
value: op.id,
name: op.name,
}));
}
} catch (error) {
console.error('获取运营人员失败:', error);
}
};
// 获取账号列表
const getAccountList = 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);
}
};
// 初始化
onMounted(() => {
// 确保在初始化时设置表格列和执行搜索
setTableColumns();
handleSearch();
getOperators();
getAccountList();
});
</script>
<style scoped>
.task-container {
display: flex;
flex-direction: column;
height: 100%;
}
.task-item {
display: flex;
align-items: center;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.task-more {
display: flex;
flex-direction: column;
cursor: pointer;
}
.no-task {
min-height: 42px;
display: flex;
align-items: center;
justify-content: center;
}
/* 周末列样式 */
:deep(.weekend-column) {
background-color: #fbfaff !important;
}
/* 今日列样式 */
:deep(th.today-column),
:deep(td.today-column) {
background-color: #6d4cfe !important;
}
:deep(th.today-column) .arco-table-th-item,
:deep(th.today-column) .arco-table-th-item-title {
color: #6d4cfe !important;
background-color: white !important;
font-weight: bold;
}
:deep(th.today-column) .arco-table-th-item-title {
color: #6d4cfe !important;
}
/* 只对 td 单元格应用样式,避免影响表头 */
:deep(td .arco-table-cell) {
padding: 0px !important;
display: flex;
flex-direction: column;
align-items: start;
min-height: 42px;
}
:deep(td .arco-table-cell:first-child) {
align-items: center;
justify-content: center;
}
:deep(td.today-column) .arco-table-cell-wrap {
color: white !important;
}
/* 抽屉左侧圆角样式 */
:deep(.rounded-left .ant-drawer-content) {
border-top-left-radius: 8px !important;
border-bottom-left-radius: 8px !important;
}
/* 任务弹出框样式 */
:deep(.task-popup-content) {
max-height: 300px;
overflow-y: auto;
}
/* 分页样式 */
.pagination-box {
display: flex;
width: 100%;
padding: 16px 0;
justify-content: flex-end;
align-items: center;
}
/* 下拉框样式优化 */
:deep(.arco-dropdown-open .arco-icon-down) {
transform: rotate(180deg);
transition: transform 0.2s ease;
}
.more-content {
border-radius: 8px;
background: #fff;
/* Shadow 2 */
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1);
}
</style>