Merge remote-tracking branch 'origin/main' into feature/0918_账号管理新增视图_rxd
2
pnpm-lock.yaml
generated
@ -5554,7 +5554,7 @@ packages:
|
||||
eslint: '>=6.0.0'
|
||||
|
||||
vue-lazyload@3.0.0:
|
||||
resolution: {integrity: sha512-h2keL/Rj550dLgesgOtXJS9qOiSMmuJNeVlfNAYV1/IYwOQYaWk5mFJlwRxmZDK9YC5gECcFLYYj7z1lKSf9ug==, tarball: https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-3.0.0.tgz}
|
||||
resolution: {integrity: sha512-h2keL/Rj550dLgesgOtXJS9qOiSMmuJNeVlfNAYV1/IYwOQYaWk5mFJlwRxmZDK9YC5gECcFLYYj7z1lKSf9ug==}
|
||||
|
||||
vue-router@4.5.1:
|
||||
resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
|
||||
|
||||
@ -35,7 +35,6 @@ const redTheme = {
|
||||
const init = async () => {
|
||||
const { isLogin } = userStore;
|
||||
|
||||
// 已开通
|
||||
if (isLogin) {
|
||||
await initApp();
|
||||
|
||||
@ -49,7 +48,6 @@ const init = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
|
||||
// 监听全局未处理错误
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
39
src/api/all/assignment-management.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import Http from '@/api';
|
||||
|
||||
// 任务管理-分页
|
||||
export const getTaskSchedules = (params = {}) => {
|
||||
return Http.get('/v1/media-accounts/task-schedules', params);
|
||||
};
|
||||
|
||||
// 任务管理-删除
|
||||
export const delTaskSchedules = (id: string) => {
|
||||
console.log('id', id);
|
||||
return Http.delete(`/v1/task-schedules/${id}`);
|
||||
};
|
||||
|
||||
// 任务管理-修改
|
||||
export const editTaskSchedules = (id: string, params = {}) => {
|
||||
console.log('id', id);
|
||||
return Http.put(`/v1/task-schedules/${id}`, params);
|
||||
};
|
||||
|
||||
// 任务管理-详情
|
||||
export const getTaskSchedulesDetail = (id: string) => {
|
||||
console.log('id', id);
|
||||
return Http.get(`/v1/task-schedules/${id}`);
|
||||
};
|
||||
//任务管理-手动添加
|
||||
export const createTask = (params = {}) => {
|
||||
return Http.post(`/v1/task-schedules/manual`, params);
|
||||
};
|
||||
|
||||
//任务管理-手动添加
|
||||
export const generateContent = (id: string) => {
|
||||
return Http.post(`/v1/task-schedules/${id}/generate-content`);
|
||||
};
|
||||
|
||||
// 任务管理-修改
|
||||
export const editTaskSchedulesTime = (id: string, params = {}) => {
|
||||
console.log('id', id);
|
||||
return Http.patch(`/v1/task-schedules/${id}/execution-time`, params);
|
||||
};
|
||||
BIN
src/assets/img/media-account/icon-AI.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/img/platform/icon-TikTok.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/img/platform/icon-bilibili.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
src/assets/img/platform/icon-gzh.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
src/assets/img/platform/icon-ks.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/img/platform/icon-sph.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/assets/img/platform/icon-wb.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
@ -11,20 +11,35 @@
|
||||
:allowClear="allClear"
|
||||
:showSearch="allowSearch"
|
||||
showArrow
|
||||
:maxTagCount="maxTagCount"
|
||||
:maxTagCount="maxTagCount !== undefined ? maxTagCount : multiple ? 3 : undefined"
|
||||
@change="handleChange"
|
||||
@dropdownVisibleChange="onDropdownVisibleChange"
|
||||
:filterOption="allowSearch ? filterOption : undefined"
|
||||
>
|
||||
<Option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
|
||||
{{ item.name }}
|
||||
<Option v-for="(item, index) in validOptions" :key="index" :value="item.value" :label="item.label">
|
||||
<div class="flex items-center">
|
||||
<img v-if="item.icon" :src="item.icon" class="w-16px h-16px mr-4px rounded-4px" />
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</Option>
|
||||
<template #tag="{ label, icon }">
|
||||
<div class="flex items-center">
|
||||
<img v-if="icon" :src="icon" class="w-16px h-16px mr-4px rounded-4px" />
|
||||
<span>{{ label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!allowSearch" #empty>
|
||||
<div v-if="validOptions.length === 0" class="empty-placeholder">
|
||||
{{ placeholder || '暂无数据' }}
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Select } from 'ant-design-vue';
|
||||
const { Option } = Select;
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@ -45,7 +60,7 @@ const props = defineProps({
|
||||
},
|
||||
maxTagCount: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
default: undefined,
|
||||
},
|
||||
allClear: {
|
||||
type: Boolean,
|
||||
@ -61,6 +76,26 @@ const emits = defineEmits(['update:modelValue', 'change', 'dropdownVisibleChange
|
||||
|
||||
const selectedValues = ref(props.multiple ? [] : '');
|
||||
|
||||
// 计算有效选项,兼容不同的数据格式
|
||||
const validOptions = computed(() => {
|
||||
if (!props.options || !Array.isArray(props.options)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.options
|
||||
.filter(
|
||||
(item) =>
|
||||
item &&
|
||||
(item.id !== undefined || item.value !== undefined) &&
|
||||
(item.name !== undefined || item.label !== undefined),
|
||||
)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
value: item.id !== undefined ? item.id : item.value,
|
||||
label: item.name !== undefined ? item.name : item.label,
|
||||
}));
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
@ -81,3 +116,11 @@ const onDropdownVisibleChange = (visible) => {
|
||||
emits('dropdownVisibleChange', visible);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty-placeholder {
|
||||
padding: 8px 12px;
|
||||
color: #8f959e;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
115
src/components/filter-popup/index.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<!-- // 新增内容: -->
|
||||
<template>
|
||||
<a-popover trigger="click" :overlayStyle="{ width: '300px' }" overlayClassName="filter-popup-popover">
|
||||
<template #content>
|
||||
<div>
|
||||
<!-- 运营人员 -->
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-70px">运营人员</div>
|
||||
<a-space class="w-200px">
|
||||
<CommonSelect
|
||||
placeholder="请选择运营人员"
|
||||
:options="operators"
|
||||
v-model="localQuery.operator"
|
||||
@change="(val) => handleChange('operator_id', val)"
|
||||
class="!w-200px"
|
||||
:allowSearch="true"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 发布平台 -->
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-70px">发布平台</div>
|
||||
<a-space class="w-200px">
|
||||
<CommonSelect
|
||||
:options="platformOptions"
|
||||
v-model="localQuery.platform"
|
||||
@change="(val) => handleChange('platform', val)"
|
||||
class="!w-200px"
|
||||
placeholder="请选择发布平台"
|
||||
:allowSearch="true"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 账号名称 -->
|
||||
<div class="flex items-center">
|
||||
<div class="w-70px">账号名称</div>
|
||||
<a-space class="w-200px">
|
||||
<CommonSelect
|
||||
v-model="localQuery.accounts"
|
||||
:options="accountList"
|
||||
:multiple="true"
|
||||
@change="(val) => handleChange('account_id', val)"
|
||||
class="!w-200px"
|
||||
placeholder="请选择账号名称"
|
||||
:allowSearch="true"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-button size="small">
|
||||
<template #icon>
|
||||
<FilterOutlined class="color-#55585F" />
|
||||
</template>
|
||||
<template #default>筛选</template>
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { FilterOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
operators: Array,
|
||||
platformOptions: Array,
|
||||
accountList: Array,
|
||||
query: Object,
|
||||
triggerStyle: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['filter-change']);
|
||||
|
||||
// 初始化本地查询对象,只设置非空值
|
||||
const localQuery = ref({});
|
||||
|
||||
// 监听外部 query 变化并同步到本地状态
|
||||
watch(
|
||||
() => props.query,
|
||||
(newQuery) => {
|
||||
if (newQuery) {
|
||||
// 只有当值不为空时才设置到localQuery中
|
||||
const filteredQuery = {};
|
||||
if (newQuery.operator) {
|
||||
filteredQuery.operator = newQuery.operator;
|
||||
}
|
||||
if (newQuery.platform) {
|
||||
filteredQuery.platform = newQuery.platform;
|
||||
}
|
||||
if (newQuery.accounts && newQuery.accounts.length > 0) {
|
||||
filteredQuery.accounts = newQuery.accounts;
|
||||
}
|
||||
localQuery.value = { ...filteredQuery };
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
// 处理筛选变化
|
||||
const handleChange = (field, value) => {
|
||||
localQuery.value[field] = value;
|
||||
console.log(localQuery.value);
|
||||
emit('filter-change', { ...localQuery.value });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-popup-popover :deep(.ant-popover-inner-content) {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
@ -1,7 +1,10 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import { merge } from 'lodash-es';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
interface UseTableSelectionWithPaginationOptions {
|
||||
rowKey?: string; // 主键字段名,默认 'id'
|
||||
type?: 'checkbox' | 'radio'; // 选择类型,默认 'checkbox'
|
||||
pageInfo?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
@ -20,6 +23,7 @@ const DEFAULT_PAGE_INFO = {
|
||||
|
||||
export function useTableSelectionWithPagination(options: UseTableSelectionWithPaginationOptions = {}) {
|
||||
const rowKey = options.rowKey || 'id';
|
||||
const type = options.type || 'checkbox'; // 默认为复选框
|
||||
|
||||
const selectedRowKeys = ref<Array<string | number>>([]);
|
||||
const selectedRows = ref<Array<any>>([]);
|
||||
@ -31,6 +35,17 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
|
||||
// 单行选择
|
||||
const handleSelect = (record: any, select: boolean) => {
|
||||
const _targetKey = record[rowKey];
|
||||
if (type === 'radio') {
|
||||
// 单选模式
|
||||
if (select) {
|
||||
selectedRows.value = [record];
|
||||
selectedRowKeys.value = [_targetKey];
|
||||
} else {
|
||||
selectedRows.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
} else {
|
||||
// 多选模式(默认)
|
||||
if (select) {
|
||||
selectedRows.value.push(record);
|
||||
selectedRowKeys.value.push(_targetKey);
|
||||
@ -38,12 +53,16 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
|
||||
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== _targetKey);
|
||||
selectedRowKeys.value = selectedRowKeys.value.filter((key) => key !== _targetKey);
|
||||
}
|
||||
}
|
||||
|
||||
options.onSelectChange?.();
|
||||
};
|
||||
|
||||
// 全选/取消全选
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
// 单选模式下不支持全选
|
||||
if (type === 'radio') return;
|
||||
|
||||
const currentPageRows = dataSource.value;
|
||||
const currentPageKeys = currentPageRows.map((v) => v[rowKey]);
|
||||
|
||||
@ -60,26 +79,44 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
|
||||
options.onSelectChange?.();
|
||||
};
|
||||
|
||||
// 选择变更处理
|
||||
const handleSelectChange = (keys: Array<string | number>, rows: Array<any>) => {
|
||||
if (type === 'radio') {
|
||||
// 单选模式下只保留最后一个选择
|
||||
selectedRowKeys.value = keys.length > 0 ? [keys[keys.length - 1]] : [];
|
||||
selectedRows.value = rows.length > 0 ? [rows[rows.length - 1]] : [];
|
||||
} else {
|
||||
// 多选模式
|
||||
selectedRowKeys.value = keys;
|
||||
selectedRows.value = rows;
|
||||
}
|
||||
options.onSelectChange?.();
|
||||
};
|
||||
|
||||
const onPageChange = (page: number, pageSize: number) => {
|
||||
// console.log('onPageChange', page, pageSize);
|
||||
pageInfo.value.page = page;
|
||||
pageInfo.value.page_size = pageSize;
|
||||
options.onPageChange?.(page);
|
||||
};
|
||||
|
||||
const onPageSizeChange = (current: number, size: number) => {
|
||||
// console.log('onPageSizeChange', current, size);
|
||||
// pageInfo.value.page_size = size;
|
||||
// pageInfo.value.page = 1;
|
||||
// options.onPageSizeChange?.(size);
|
||||
};
|
||||
|
||||
const resetPageInfo = () => {
|
||||
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO);
|
||||
};
|
||||
|
||||
const rowSelection = computed(() => ({
|
||||
type: 'checkbox',
|
||||
showCheckedAll: true,
|
||||
type: type,
|
||||
showCheckedAll: type === 'checkbox', // 只有复选框模式才显示全选
|
||||
width: 48,
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: handleSelectChange
|
||||
}));
|
||||
|
||||
return {
|
||||
@ -92,6 +129,7 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
|
||||
rowSelection,
|
||||
handleSelect,
|
||||
handleSelectAll,
|
||||
handleSelectChange,
|
||||
resetPageInfo,
|
||||
DEFAULT_PAGE_INFO,
|
||||
};
|
||||
|
||||
@ -109,10 +109,11 @@ export const MENU_LIST = <Record<string, typeMenuItem[]>>{
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'ModTaskManage',
|
||||
key: 'assignmentManagement',
|
||||
label: '任务管理',
|
||||
icon: ['svg-taskManage', 'svg-taskManage-active'],
|
||||
routeName: 'TaskManagement',
|
||||
routeName: 'AssignmentManagement',
|
||||
activeMatch: ['AssignmentManagement'],
|
||||
},
|
||||
],
|
||||
[GROUP_WRITER_NAME]: [
|
||||
@ -156,9 +157,7 @@ export const MENU_LIST = <Record<string, typeMenuItem[]>>{
|
||||
label: '个人信息',
|
||||
routeName: 'ManagementPerson',
|
||||
requireLogin: true,
|
||||
activeMatch: [
|
||||
'ManagementPerson',
|
||||
],
|
||||
activeMatch: ['ManagementPerson'],
|
||||
},
|
||||
{
|
||||
key: 'ModManagementEnterprise',
|
||||
@ -167,9 +166,7 @@ export const MENU_LIST = <Record<string, typeMenuItem[]>>{
|
||||
routeName: 'ManagementEnterprise',
|
||||
requireLogin: true,
|
||||
requireAuth: true,
|
||||
activeMatch: [
|
||||
'ManagementEnterprise',
|
||||
],
|
||||
activeMatch: ['ManagementEnterprise'],
|
||||
},
|
||||
{
|
||||
key: 'ModManagementAccount',
|
||||
@ -180,5 +177,5 @@ export const MENU_LIST = <Record<string, typeMenuItem[]>>{
|
||||
requireAuth: true,
|
||||
activeMatch: ['ManagementAccount'],
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ import SvgIcon from '@/components/svg-icon/index.vue';
|
||||
|
||||
import '@/api/index';
|
||||
import './core';
|
||||
import '@arco-design/web-vue/dist/arco.css'; // 已移除 Arco 样式
|
||||
// import '@arco-design/web-vue/dist/arco.css'; // 已移除 Arco 样式
|
||||
|
||||
import 'normalize.css';
|
||||
import 'uno.css';
|
||||
@ -28,6 +28,7 @@ const app = createApp(App);
|
||||
|
||||
app.component('NoData', NoData);
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
(Object.keys(directives) as Array<keyof typeof directives>).forEach((k) => app.use(directives[k])); // 注册指令
|
||||
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
|
||||
@ -73,6 +73,20 @@ const COMPONENTS: AppRouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import('@/views/property-marketing/media-account/account-dashboard/index.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: 'management-detail/:id',
|
||||
name: 'managementDetail',
|
||||
meta: {
|
||||
locale: '任务详情',
|
||||
requiresAuth: true,
|
||||
requireLogin: true,
|
||||
roles: ['*'],
|
||||
hideInMenu: true,
|
||||
activeMenu: 'MediaAccountAccountDashboard',
|
||||
},
|
||||
component: () => import('@/views/property-marketing/assignment-management/managementDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: 'detail/:id',
|
||||
name: 'MediaAccountAccountDetails',
|
||||
@ -172,6 +186,17 @@ const COMPONENTS: AppRouteRecordRaw[] = [
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/assignmentManagement',
|
||||
name: 'AssignmentManagement',
|
||||
meta: {
|
||||
locale: '任务管理',
|
||||
requiresAuth: true,
|
||||
requireLogin: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
component: () => import('@/views/property-marketing/assignment-management/index.vue'),
|
||||
},
|
||||
// {
|
||||
// path: '/intelligent-solution',
|
||||
// name: 'IntelligentSolution',
|
||||
|
||||
@ -114,3 +114,129 @@
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
import { MENU_GROUP_IDS } from '@/router/constants';
|
||||
export const MENU_LIST = [
|
||||
{
|
||||
id: MENU_GROUP_IDS.WORK_BENCH_ID,
|
||||
name: '工作台',
|
||||
routeName: 'Home',
|
||||
includeRouteNames: ['Home'],
|
||||
requiresAuth: false,
|
||||
permissionKey: '', // 权限key,如果为空,则表示该菜单不需要权限,与后端约定
|
||||
},
|
||||
{
|
||||
id: MENU_GROUP_IDS.MANAGEMENT_ID,
|
||||
name: '任务管理',
|
||||
routeName: 'assignmentManagement',
|
||||
includeRouteNames: ['assignmentManagement'],
|
||||
requiresAuth: false,
|
||||
permissionKey: '', // 权限key,如果为空,则表示该菜单不需要权限,与后端约定
|
||||
},
|
||||
{
|
||||
id: MENU_GROUP_IDS.DATA_ENGINE_ID,
|
||||
name: '全域数据分析',
|
||||
permissionKey: 'data_analysis',
|
||||
requiresAuth: true,
|
||||
children: [
|
||||
{
|
||||
name: '行业热门话题洞察',
|
||||
routeName: 'DataEngineHotTranslation',
|
||||
includeRouteNames: ['DataEngineHotTranslation'],
|
||||
},
|
||||
{
|
||||
name: '行业词云',
|
||||
routeName: 'DataEngineHotCloud',
|
||||
includeRouteNames: ['DataEngineHotCloud'],
|
||||
},
|
||||
{
|
||||
name: '行业关键词动向',
|
||||
routeName: 'DataEngineKeyWord',
|
||||
includeRouteNames: ['DataEngineKeyWord'],
|
||||
},
|
||||
{
|
||||
name: '用户痛点观察',
|
||||
routeName: 'DataEngineUserPainPoints',
|
||||
includeRouteNames: ['DataEngineUserPainPoints'],
|
||||
},
|
||||
{
|
||||
name: '重点品牌动向',
|
||||
routeName: 'DataEngineKeyBrandMovement',
|
||||
includeRouteNames: ['DataEngineKeyBrandMovement'],
|
||||
},
|
||||
{
|
||||
name: '用户画像',
|
||||
routeName: 'DataEngineUserPersona',
|
||||
includeRouteNames: ['DataEngineUserPersona'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: MENU_GROUP_IDS.CREATIVE_GENERATION_WORKSHOP_ID,
|
||||
name: '创意生成工坊',
|
||||
permissionKey: '',
|
||||
requiresAuth: true,
|
||||
children: [
|
||||
{
|
||||
name: '内容稿件',
|
||||
routeName: 'ManuscriptList',
|
||||
includeRouteNames: [
|
||||
'ManuscriptList',
|
||||
'ManuscriptUpload',
|
||||
'ManuscriptEdit',
|
||||
'ManuscriptDetail',
|
||||
'ManuscriptCheckList',
|
||||
'ManuscriptCheckListDetail',
|
||||
'ManuscriptCheck',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: MENU_GROUP_IDS.PROPERTY_ID,
|
||||
name: '营销资产中台',
|
||||
permissionKey: 'marketing_asset',
|
||||
requiresAuth: true,
|
||||
children: [
|
||||
{
|
||||
name: '品牌资产管理',
|
||||
routeName: 'RepositoryBrandMaterials',
|
||||
includeRouteNames: ['RepositoryBrandMaterials'],
|
||||
},
|
||||
{
|
||||
name: '账号资源中心',
|
||||
routeName: 'MediaAccountAccountManagement',
|
||||
includeRouteNames: [
|
||||
'MediaAccountAccountManagement',
|
||||
'MediaAccountAccountDashboard',
|
||||
'MediaAccountAccountDetails',
|
||||
'assignmentManagement',
|
||||
'managementDetail',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '投放资源中心',
|
||||
routeName: 'PutAccountAccountManagement',
|
||||
includeRouteNames: [
|
||||
'PutAccountAccountManagement',
|
||||
'PutAccountAccountData',
|
||||
'PutAccountAccountDashboard',
|
||||
'PutAccountInvestmentGuidelines',
|
||||
'guideDetail',
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: '智能方案管理',
|
||||
// routeName: 'IntelligentSolutionBusinessAnalysisReport',
|
||||
// includeRouteNames: [
|
||||
// 'IntelligentSolutionBusinessAnalysisReport',
|
||||
// 'IntelligentSolutionCompetitiveProductAnalysisReport',
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: '项目管理',
|
||||
routeName: 'ProjectList',
|
||||
includeRouteNames: ['ProjectList'],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
382
src/utils/DateUtils.ts
Normal file
@ -0,0 +1,382 @@
|
||||
/**
|
||||
* 日期工具类
|
||||
* 提供月维度和周维度的开始结束日期获取功能
|
||||
* 周以星期日开始,星期六结束
|
||||
* 只返回日期部分,不包含时间
|
||||
*/
|
||||
class DateUtils {
|
||||
/**
|
||||
* 获取当前时间的月开始日期和结束日期
|
||||
* @returns 包含开始和结束日期的对象
|
||||
*/
|
||||
static getMonthRange(): {
|
||||
start: Date;
|
||||
end: Date;
|
||||
startFormatted: string;
|
||||
endFormatted: string;
|
||||
} {
|
||||
const now = new Date();
|
||||
|
||||
// 月开始日期(当月第一天)
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// 月结束日期(当月最后一天)
|
||||
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
startFormatted: this.formatDate(start),
|
||||
endFormatted: this.formatDate(end),
|
||||
};
|
||||
}
|
||||
|
||||
static MonthStrToDate(dateStr: string): Date {
|
||||
const year = parseInt(dateStr.split('年')[0]);
|
||||
const month = parseInt(dateStr.split('年')[1].split('月')[0]) - 1; // 月份从0开始
|
||||
|
||||
const date = new Date(year, month, 1);
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定年份和月的范围
|
||||
* @param year 年份
|
||||
* @param month 月份(1-12)
|
||||
*/
|
||||
static getMonthRangeByYearMonth(
|
||||
year: number,
|
||||
month: number,
|
||||
): {
|
||||
start: Date;
|
||||
end: Date;
|
||||
startFormatted: string;
|
||||
endFormatted: string;
|
||||
} {
|
||||
// 月开始日期
|
||||
const start = new Date(year, month - 1, 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// 月结束日期
|
||||
const end = new Date(year, month, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
startFormatted: this.formatDate(start),
|
||||
endFormatted: this.formatDate(end),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间的周范围(以星期日开始)
|
||||
* @returns 包含开始和结束日期的对象
|
||||
*/
|
||||
static getWeekRange(): {
|
||||
start: Date;
|
||||
end: Date;
|
||||
startFormatted: string;
|
||||
endFormatted: string;
|
||||
} {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const dayOfWeek = today.getDay(); // 0是周日,1是周一,...,6是周六
|
||||
|
||||
// 周开始日期(周日)
|
||||
const start = new Date(today);
|
||||
start.setDate(today.getDate() - dayOfWeek);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// 周结束日期(周六)
|
||||
const end = new Date(start);
|
||||
end.setDate(start.getDate() + 6);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
startFormatted: this.formatDate(start),
|
||||
endFormatted: this.formatDate(end),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的周范围
|
||||
* @param date 指定日期
|
||||
*/
|
||||
static getWeekRangeByDate(date: Date): {
|
||||
start: Date;
|
||||
end: Date;
|
||||
startFormatted: string;
|
||||
endFormatted: string;
|
||||
} {
|
||||
const inputDate = new Date(date);
|
||||
inputDate.setHours(0, 0, 0, 0);
|
||||
const dayOfWeek = inputDate.getDay();
|
||||
|
||||
// 周开始日期(周日)
|
||||
const start = new Date(inputDate);
|
||||
start.setDate(inputDate.getDate() - dayOfWeek);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
|
||||
// 周结束日期(周六)
|
||||
const end = new Date(start);
|
||||
end.setDate(start.getDate() + 6);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
startFormatted: this.formatDate(start),
|
||||
endFormatted: this.formatDate(end),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期(只返回日期部分)
|
||||
* @param date 日期对象
|
||||
* @returns 格式化后的日期字符串 (YYYY-MM-DD)
|
||||
*/
|
||||
static formatDate(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期信息
|
||||
* @returns 包含各种格式的当前日期信息
|
||||
*/
|
||||
static getCurrentDateInfo() {
|
||||
const now = new Date();
|
||||
now.setHours(0, 0, 0, 0);
|
||||
|
||||
const monthRange = this.getMonthRange();
|
||||
const weekRange = this.getWeekRange();
|
||||
|
||||
return {
|
||||
current: {
|
||||
date: now,
|
||||
formatted: this.formatDate(now),
|
||||
dayOfWeek: this.getChineseDayOfWeek(now),
|
||||
},
|
||||
month: {
|
||||
start: monthRange.start,
|
||||
end: monthRange.end,
|
||||
startFormatted: monthRange.startFormatted,
|
||||
endFormatted: monthRange.endFormatted,
|
||||
totalDays: this.getDaysInMonth(now.getFullYear(), now.getMonth() + 1),
|
||||
},
|
||||
week: {
|
||||
start: weekRange.start,
|
||||
end: weekRange.end,
|
||||
startFormatted: weekRange.startFormatted,
|
||||
endFormatted: weekRange.endFormatted,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定月份的天数
|
||||
* @param year 年份
|
||||
* @param month 月份(1-12)
|
||||
* @returns 该月的天数
|
||||
*/
|
||||
static getDaysInMonth(year: number, month: number): number {
|
||||
return new Date(year, month, 0).getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中文星期几
|
||||
* @param date 日期对象
|
||||
* @returns 中文星期几
|
||||
*/
|
||||
static getChineseDayOfWeek(date: Date): string {
|
||||
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||||
return days[date.getDay()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中文月份名称
|
||||
* @param month 月份(1-12)
|
||||
* @returns 中文月份名称
|
||||
*/
|
||||
static getChineseMonthName(month: number): string {
|
||||
const months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
|
||||
return months[month - 1] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为同一天
|
||||
* @param date1 日期1
|
||||
* @param date2 日期2
|
||||
* @returns 是否为同一天
|
||||
*/
|
||||
static isSameDay(date1: Date, date2: Date): boolean {
|
||||
return this.formatDate(date1) === this.formatDate(date2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日期差(天数)
|
||||
* @param date1 日期1
|
||||
* @param date2 日期2
|
||||
* @returns 相差的天数
|
||||
*/
|
||||
static getDaysDifference(date1: Date, date2: Date): number {
|
||||
const d1 = new Date(date1);
|
||||
const d2 = new Date(date2);
|
||||
d1.setHours(0, 0, 0, 0);
|
||||
d2.setHours(0, 0, 0, 0);
|
||||
|
||||
const diffTime = Math.abs(d2.getTime() - d1.getTime());
|
||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
/**
|
||||
* 获取当前周的所有日期及星期几
|
||||
* @param startDay 周起始日(0-6,0表示周日,默认1表示周一)
|
||||
* @returns 当前周的日期数组
|
||||
*/
|
||||
static getWeekDaysByDate(targetDate: Date, startDay: number = 1) {
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
const currentDay = targetDate.getDay();
|
||||
const firstDayOffset = (currentDay - startDay + 7) % 7;
|
||||
const firstDayOfWeek = new Date(targetDate);
|
||||
firstDayOfWeek.setDate(targetDate.getDate() - firstDayOffset);
|
||||
return Array.from({ length: 7 }).map((_, i) => {
|
||||
const date = new Date(firstDayOfWeek);
|
||||
date.setDate(firstDayOfWeek.getDate() + i);
|
||||
return {
|
||||
date,
|
||||
day: date.getDate(),
|
||||
weekday: weekdays[date.getDay()] + ' ' + date.getDate(),
|
||||
month: date.getMonth() + 1, // 添加月份信息(1-12)
|
||||
year: date.getFullYear(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 获取某个月每一天的星期几
|
||||
static getDaysAndWeekdays(year: number, month: number): Array<{ day: number; weekday: string }> {
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate(); // 获取当月总天数
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; // 中文星期数组
|
||||
const days: Array<{ day: number; weekday: string }> = []; // 结果数组
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
const weekdayIndex = date.getDay(); // 获取星期几的索引(0-6)
|
||||
days.push({
|
||||
day,
|
||||
weekday: weekdays[weekdayIndex] + ' ' + day,
|
||||
});
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
/**
|
||||
* 获取当前的年份
|
||||
* @returns 当前年份
|
||||
*/
|
||||
static getCurrentYear(): number {
|
||||
return new Date().getFullYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的月份
|
||||
* @returns 当前月份 (1-12)
|
||||
*/
|
||||
static getCurrentMonth(): number {
|
||||
return new Date().getMonth() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的年份和月份
|
||||
* @returns 包含当前年份和月份的对象
|
||||
*/
|
||||
static getCurrentYearMonth(): { year: number; month: number } {
|
||||
const now = new Date();
|
||||
return {
|
||||
year: now.getFullYear(),
|
||||
month: now.getMonth() + 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式化的当前年月字符串
|
||||
* @param separator 分隔符,默认为 '-'
|
||||
* @returns 格式化的年月字符串 (YYYY-MM)
|
||||
*/
|
||||
static getFormattedYearMonth(separator: string = '-'): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
return `${year}${separator}${month}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取中文格式的当前年月
|
||||
* @returns 中文年月字符串 (YYYY年MM月)
|
||||
*/
|
||||
static getChineseYearMonth(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
return `${year}年${month}月`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前季度
|
||||
* @returns 当前季度 (1-4)
|
||||
*/
|
||||
static getCurrentQuarter(): number {
|
||||
const month = new Date().getMonth() + 1;
|
||||
return Math.ceil(month / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前季度的开始和结束月份
|
||||
* @returns 包含季度开始和结束月份的对象
|
||||
*/
|
||||
static getCurrentQuarterRange(): { startMonth: number; endMonth: number } {
|
||||
const quarter = this.getCurrentQuarter();
|
||||
return {
|
||||
startMonth: (quarter - 1) * 3 + 1,
|
||||
endMonth: quarter * 3,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定年份的所有月份信息
|
||||
* @param year 年份
|
||||
* @returns 包含所有月份信息的数组
|
||||
*/
|
||||
static getYearMonths(year: number): Array<{
|
||||
month: number;
|
||||
name: string;
|
||||
days: number;
|
||||
formatted: string;
|
||||
}> {
|
||||
return Array.from({ length: 12 }, (_, i) => {
|
||||
const month = i + 1;
|
||||
return {
|
||||
month,
|
||||
name: this.getChineseMonthName(month),
|
||||
days: this.getDaysInMonth(year, month),
|
||||
formatted: `${year}-${month.toString().padStart(2, '0')}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
static formatDateToWeekdayDay(date: Date): string {
|
||||
const day = date.getDate();
|
||||
const weekday = this.getChineseDayOfWeek(date).replace('星期', '周');
|
||||
return `${weekday} ${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default DateUtils;
|
||||
@ -3,6 +3,11 @@ import icon2 from '@/assets/img/media-account/icon-jg.png';
|
||||
import icon3 from '@/assets/img/media-account/icon-bili.png';
|
||||
import icon4 from '@/assets/img/platform/icon-dy.png';
|
||||
import icon5 from '@/assets/img/platform/icon-xhs.png';
|
||||
import icon6 from '@/assets/img/platform/icon-bilibili.png';
|
||||
import icon7 from '@/assets/img/platform/icon-ks.png';
|
||||
import icon8 from '@/assets/img/platform/icon-sph.png';
|
||||
import icon9 from '@/assets/img/platform/icon-wb.png';
|
||||
import icon10 from '@/assets/img/platform/icon-gzh.png';
|
||||
|
||||
// 投放账户
|
||||
export enum ENUM_PUT_ACCOUNT_PLATFORM {
|
||||
@ -14,6 +19,11 @@ export enum ENUM_PUT_ACCOUNT_PLATFORM {
|
||||
export enum ENUM_MEDIA_ACCOUNT_PLATFORM {
|
||||
dy = 0,
|
||||
xhs = 1,
|
||||
bili = 2,
|
||||
ks = 3,
|
||||
sph = 4,
|
||||
wb = 5,
|
||||
gzh = 6,
|
||||
}
|
||||
|
||||
export const PLATFORM_LIST = [
|
||||
@ -45,6 +55,31 @@ export const MEDIA_ACCOUNT_PLATFORMS = [
|
||||
value: 1,
|
||||
icon: icon5,
|
||||
},
|
||||
{
|
||||
label: 'B站',
|
||||
value: 2,
|
||||
icon: icon6,
|
||||
},
|
||||
{
|
||||
label: '快手',
|
||||
value: 3,
|
||||
icon: icon7,
|
||||
},
|
||||
{
|
||||
label: '视频号',
|
||||
value: 4,
|
||||
icon: icon8,
|
||||
},
|
||||
{
|
||||
label: '微博',
|
||||
value: 5,
|
||||
icon: icon9,
|
||||
},
|
||||
{
|
||||
label: '公众号',
|
||||
value: 6,
|
||||
icon: icon10,
|
||||
},
|
||||
];
|
||||
|
||||
export const getPutAccountPlatformLogo = (value: ENUM_PUT_ACCOUNT_PLATFORM) => {
|
||||
|
||||
346
src/views/components/login/index.vue
Normal file
@ -0,0 +1,346 @@
|
||||
<!-- eslint-disable vue/no-duplicate-attributes -->
|
||||
<template>
|
||||
<div
|
||||
class="relative w-100vw h-100vh min-h-175 flex justify-center items-center bg-cover bg-center bg-#f0edff login-wrap"
|
||||
>
|
||||
<section class="login-bg"></section>
|
||||
<section class="relative flex justify-between w-1200 h-100% my-0 mx-auto">
|
||||
<div class="flex flex-col justify-center flex-column h-100% mt--12.5">
|
||||
<img src="@/assets/img/Frame.svg" class="w-480 h-480 mr-40" alt="" />
|
||||
</div>
|
||||
<div class="flex items-center w-400 h-100%">
|
||||
<Space
|
||||
direction="vertical"
|
||||
size="large"
|
||||
align="center"
|
||||
class="w-400 bg-#fff rounded-8px shadow-[0_4px_10px_0_#6D4CFE33] px-40px py-48px"
|
||||
>
|
||||
<img src="@/assets/img/icon-logo.png" alt="" width="96" height="24" class="mb-8px" />
|
||||
<span class="text-4 color-#737478">AI营销工具</span>
|
||||
<Form ref="formRef" :model="loginForm" :rules="formRules" auto-label-width class="w-320 mt-48px form-wrap">
|
||||
<FormItem name="mobile">
|
||||
<Input
|
||||
v-model:value="loginForm.mobile"
|
||||
placeholder="输入手机号"
|
||||
class="form-input border border-solid !border-#d7d7d9 w-100% h-48px !text-14px rounded-4px color-#333 bg-#fff"
|
||||
clearable
|
||||
allowClear
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="captcha">
|
||||
<div
|
||||
class="form-input border border-solid !border-#d7d7d9 w-100% h-48px !text-14px rounded-4px color-#333 bg-#fff flex justify-between items-center"
|
||||
>
|
||||
<Input
|
||||
v-model:value="loginForm.captcha"
|
||||
placeholder="验证码"
|
||||
style="background-color: #fff; border: none !important;"
|
||||
allowClear
|
||||
class="form-input"
|
||||
:maxlength="6"
|
||||
/>
|
||||
<span
|
||||
class="w-120 font-400 text-right mr-4 text-16px"
|
||||
:style="{
|
||||
color: countdown > 0 || hasGetCode ? '#6D4CFE' : '#211F24',
|
||||
cursor: countdown > 0 ? 'not-allowed' : 'pointer',
|
||||
}"
|
||||
@click="getCode"
|
||||
>{{ countdown > 0 ? `${countdown}s` : hasGetCode ? '重新发送' : '发送验证码' }}</span
|
||||
>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem class="mt-68px mb-16px">
|
||||
<Button
|
||||
type="primary"
|
||||
class="w-full h-48 !text-16px !rounded-8px"
|
||||
:class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'"
|
||||
:disabled="disabledSubmitBtn"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ isLogin ? '登录' : '注册并开通企业账号' }}
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<Space class="text-12px color-#737478 justify-start items-center">
|
||||
<Checkbox v-model:checked="hasCheck" class="!text-12px mr-8px"></Checkbox>
|
||||
<span class="text-12px color-#737478">{{ isLogin ? '登录' : '注册' }}即代表同意</span>
|
||||
<Link href="link" class="form-link color-#211F24" target="_blank">用户协议</Link>
|
||||
<span class="text-12px color-#737478">和</span>
|
||||
<Link href="link" class="form-link color-#211f24" target="_blank">隐私政策</Link>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="login-footer">
|
||||
<p class="text">闽公网安备 352018502850842号 闽ICP备20250520582号 © 2025小题科技</p>
|
||||
</section>
|
||||
</div>
|
||||
<PuzzleVerification
|
||||
:show="isVerificationVisible"
|
||||
@submit="handleVerificationSubmit"
|
||||
@cancel="isVerificationVisible = false"
|
||||
/>
|
||||
<Modal v-model:open="visible" centered unmountOnClose @cancel="handleCancel">
|
||||
<template #title>
|
||||
<span style="text-align: left; width: 100%">选择账号</span>
|
||||
</template>
|
||||
<div class="account-bind-container">
|
||||
<Card :bordered="false" class="bind-card">
|
||||
<div class="bind-header">
|
||||
<Typography.Text class="mobile-number">{{ mobileNumber }} 已在以下企业绑定了账号</Typography.Text>
|
||||
</div>
|
||||
|
||||
<List :bordered="false" :split="false" class="account-list">
|
||||
<List.Item
|
||||
v-for="(account, index) in accounts"
|
||||
:key="index"
|
||||
class="account-item"
|
||||
:class="{
|
||||
selected: selectedAccountIndex === index,
|
||||
'cursor-no-drop': account.status === 0,
|
||||
'cursor-pointer': account.status !== 0,
|
||||
}"
|
||||
@click="selectAccount(account, index)"
|
||||
>
|
||||
<List.Item.Meta>
|
||||
<template #title>
|
||||
<div style="display: flex; align-items: center; gap: 12px">
|
||||
<Checkbox :checked="selectedAccountIndex === index" />
|
||||
<Typography.Text>{{ account.name || '-' }}</Typography.Text>
|
||||
</div>
|
||||
</template>
|
||||
</List.Item.Meta>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Card>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex">
|
||||
<Button type="primary" @click="handleOk">确定</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Checkbox, Modal, Button, Form, FormItem, Input, Space, message, Typography, Card, List } from 'ant-design-vue';
|
||||
const { Link } = Typography;
|
||||
import PuzzleVerification from './components/PuzzleVerification.vue';
|
||||
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo } from '@/api/all/login';
|
||||
import { joinEnterpriseByInviteCode } from '@/api/all';
|
||||
import { ref, reactive, onUnmounted, computed } from 'vue';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useEnterpriseStore } from '@/stores/modules/enterprise';
|
||||
import { handleUserLogin } from '@/utils/user';
|
||||
import router from '@/router';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const formRef = ref();
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
const enterpriseStore = useEnterpriseStore();
|
||||
const countdown = ref(0);
|
||||
let timer = ref();
|
||||
const isLogin = ref(true);
|
||||
const isVerificationVisible = ref(false);
|
||||
const visible = ref(false);
|
||||
const hasGetCode = ref(false);
|
||||
const submitting = ref(false);
|
||||
const hasCheck = ref(false);
|
||||
const mobileNumber = ref('');
|
||||
const selectedAccountIndex = ref(0);
|
||||
const accounts = ref([]);
|
||||
|
||||
const loginForm = reactive({
|
||||
mobile: '',
|
||||
captcha: '',
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
const formRules = {
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请填写手机号');
|
||||
}
|
||||
if (!/^1[3-9]\d{9}$/.test(value)) {
|
||||
return Promise.reject('手机号格式不正确');
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
captcha: [
|
||||
{
|
||||
required: true,
|
||||
validator: (_rule: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请填写验证码');
|
||||
}
|
||||
if (!/^\d{6}$/.test(value)) {
|
||||
return Promise.reject('验证码必须是6位数字');
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 表单是否有效
|
||||
const isFormValid = computed(() => {
|
||||
return (
|
||||
loginForm.mobile.trim() !== '' &&
|
||||
/^1[3-9]\d{9}$/.test(loginForm.mobile) &&
|
||||
loginForm.captcha.trim() !== '' &&
|
||||
/^\d{6}$/.test(loginForm.captcha)
|
||||
);
|
||||
});
|
||||
|
||||
const disabledSubmitBtn = computed(() => {
|
||||
return !isFormValid.value;
|
||||
});
|
||||
|
||||
const selectAccount = (account: any, index: any) => {
|
||||
if (account.status === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
enterpriseStore.setEnterpriseInfo(account);
|
||||
selectedAccountIndex.value = index;
|
||||
};
|
||||
|
||||
const validateField = (field: string) => {
|
||||
formRef.value.validateFields(field);
|
||||
};
|
||||
|
||||
const clearError = (field: string) => {
|
||||
formRef.value.clearValidate(field);
|
||||
};
|
||||
|
||||
const handleOk = async () => {
|
||||
visible.value = false;
|
||||
|
||||
handleUserLogin();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const getCode = async () => {
|
||||
if (countdown.value > 0) return;
|
||||
|
||||
// 先重置验证状态
|
||||
formRef.value.clearValidate('mobile');
|
||||
|
||||
formRef.value.validateFields('mobile').then(() => {
|
||||
isVerificationVisible.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
// 验证码验证通过后
|
||||
const handleVerificationSubmit = async () => {
|
||||
isVerificationVisible.value = false;
|
||||
startCountdown();
|
||||
|
||||
try {
|
||||
const { code, message: msg } = await fetchLoginCaptCha({ mobile: loginForm.mobile });
|
||||
if (code === 200) {
|
||||
message.success(msg);
|
||||
}
|
||||
} catch (error) {
|
||||
// 重置倒计时
|
||||
countdown.value = 0;
|
||||
clearInterval(timer.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户信息
|
||||
const getProfileInfo = async () => {
|
||||
const { code, data } = await fetchProfileInfo();
|
||||
if (code === 200) {
|
||||
let enterprises = data['enterprises'];
|
||||
mobileNumber.value = data['mobile'];
|
||||
accounts.value = enterprises;
|
||||
|
||||
if (enterprises.length > 0) {
|
||||
enterpriseStore.setEnterpriseInfo(data.enterprises[0]);
|
||||
if (enterprises.length === 1) {
|
||||
handleUserLogin();
|
||||
} else {
|
||||
// 多个企业时候需要弹窗让用户选择企业
|
||||
visible.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (disabledSubmitBtn.value) return;
|
||||
|
||||
try {
|
||||
// 校验所有字段
|
||||
await formRef.value.validate();
|
||||
|
||||
if (!hasCheck.value) {
|
||||
message.error('请先勾选同意用户协议');
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
const { code, data } = await fetchAuthorizationsCaptcha(loginForm);
|
||||
|
||||
if (code === 200) {
|
||||
// 处理登录成功逻辑
|
||||
message.success(isLogin.value ? '登录成功' : '注册成功');
|
||||
userStore.setToken(data.access_token);
|
||||
|
||||
const { invite_code } = route.query;
|
||||
if (invite_code) {
|
||||
const { code } = await joinEnterpriseByInviteCode(invite_code as string);
|
||||
if (code === 200) {
|
||||
message.success('加入企业成功');
|
||||
}
|
||||
}
|
||||
|
||||
getProfileInfo();
|
||||
}
|
||||
} catch (error) {
|
||||
// 错误信息会显示在输入框下方
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 开始倒计时
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60;
|
||||
hasGetCode.value = true;
|
||||
timer.value = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer.value as number);
|
||||
timer.value = null;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './style.scss';
|
||||
</style>
|
||||
@ -43,14 +43,6 @@ export const TABLE_COLUMNS = [
|
||||
dataIndex: 'origin',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 180,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '上传人员',
|
||||
dataIndex: 'uploader',
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<a-popover trigger="click" overlayClassName="color-tip-popover">
|
||||
<template #content>
|
||||
<div class="flex items-center mt-8px w-104px mr-8px">
|
||||
<div style="background-color: #ffae00; width: 16px; height: 16px; margin-right: 5px; border-radius: 4px"></div>
|
||||
<div>待生成</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-8px w-104px mr-8px">
|
||||
<div style="background-color: #6d4cfe; width: 16px; height: 16px; margin-right: 5px; border-radius: 4px"></div>
|
||||
<div>待发布</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-8px w-104px mr-8px">
|
||||
<div style="background-color: #939499; width: 16px; height: 16px; margin-right: 5px; border-radius: 4px"></div>
|
||||
<div>已发布</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-8px w-104px mr-8px">
|
||||
<div style="background-color: #f64b31; width: 16px; height: 16px; margin-right: 5px; border-radius: 4px"></div>
|
||||
<div>发布失败</div>
|
||||
</div>
|
||||
</template>
|
||||
<Button class="w-112px mr-8px" size="middle">
|
||||
<template #icon>
|
||||
<InfoCircleOutlined class="color-#55585F" />
|
||||
</template>
|
||||
<template #default>颜色示意</template>
|
||||
</Button>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { Checkbox, Button, Space, Pagination, notification } from 'ant-design-vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.color-tip-popover :deep(.ant-popover-inner-content) {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
box-shadow: #00000010 0px 2px 8px;
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.color-tip-popover :deep(.ant-popover-arrow) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<!-- 日选择器 -->
|
||||
<a-date-picker
|
||||
class="w-188px size-14px"
|
||||
v-if="choseType === '日'"
|
||||
@change="handleDateChange"
|
||||
v-model="currentDate"
|
||||
format="YYYY年MM月DD日周dd"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<!-- 周选择器 -->
|
||||
<a-week-picker
|
||||
class="w-188px size-14px"
|
||||
v-else-if="choseType === '周'"
|
||||
@change="handleDateChange"
|
||||
v-model="currentDate"
|
||||
format="YYYY年MM月DD日"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<!-- 月选择器 -->
|
||||
<a-month-picker
|
||||
class="w-188px size-14px"
|
||||
v-else
|
||||
@change="handleDateChange"
|
||||
v-model="currentDate"
|
||||
format="YYYY年MM月"
|
||||
value-format="YYYY-MM"
|
||||
/>
|
||||
|
||||
<!-- 日期导航按钮 -->
|
||||
<div class="flex items-center ml-12px">
|
||||
<a-button class="mr-4px prv-btn" @click="navigate(-1)" type="text">
|
||||
<template #icon><icon-left /></template>
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="navigateToToday"
|
||||
type="text"
|
||||
style="background-color: #f7f8fa !important; color: #211f24 !important; height: 28px"
|
||||
>今天</a-button
|
||||
>
|
||||
<a-button class="ml-4px prv-btn" @click="navigate(1)" type="text">
|
||||
<template #icon><icon-right /></template>
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<!-- 维度切换下拉框 -->
|
||||
<div class="flex items-center">
|
||||
<a-dropdown
|
||||
position="bottom"
|
||||
@select="handleTypeChange"
|
||||
class="w-80px"
|
||||
:popupVisible="dropdownVisible"
|
||||
@popupVisibleChange="handleDropdownVisibleChange"
|
||||
>
|
||||
<a-button type="text" class="prv-today"> {{ choseType }}<icon-down class="ml-4px" /> </a-button>
|
||||
<template #content>
|
||||
<a-doption value="日" class="doption">日</a-doption>
|
||||
<a-doption value="周" class="doption">周</a-doption>
|
||||
<a-doption value="月" class="doption">月</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, defineProps, defineEmits, withDefaults, computed } from 'vue';
|
||||
import DateUtils from '@/utils/DateUtils';
|
||||
|
||||
// 1. 定义Props:接收父组件初始日期配置
|
||||
interface Props {
|
||||
modelValue?: {
|
||||
choseType: '日' | '周' | '月';
|
||||
dayModel?: Date;
|
||||
weekModel?: Date;
|
||||
monthModel?: Date;
|
||||
};
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: () => ({ choseType: '周', dayModel: new Date(), weekModel: new Date(), monthModel: new Date() }),
|
||||
});
|
||||
|
||||
// 2. 定义Emits:向父组件传递事件
|
||||
const emit = defineEmits([
|
||||
'update:modelValue', // v-model双向绑定
|
||||
'date-change', // 日期/维度切换统一事件(父组件只需要监听这个)
|
||||
]);
|
||||
|
||||
// 3. 内部状态管理:根据当前维度同步日期
|
||||
const choseType = ref<Props['modelValue']['choseType']>(props.modelValue.choseType);
|
||||
const dayModel = ref(props.modelValue.dayModel || new Date());
|
||||
const weekModel = ref(props.modelValue.weekModel || new Date());
|
||||
const monthModel = ref(props.modelValue.monthModel || new Date());
|
||||
|
||||
// 下拉菜单显示状态
|
||||
const dropdownVisible = ref(false);
|
||||
|
||||
// 添加一个标志位,用于避免初始化时触发事件
|
||||
let isInitializing = true;
|
||||
|
||||
// 计算属性:根据当前维度返回对应日期(简化选择器v-model绑定)
|
||||
const currentDate = computed({
|
||||
get() {
|
||||
switch (choseType.value) {
|
||||
case '日':
|
||||
return dayModel.value;
|
||||
case '周':
|
||||
return weekModel.value;
|
||||
case '月':
|
||||
return monthModel.value;
|
||||
}
|
||||
},
|
||||
set(val: Date) {
|
||||
switch (choseType.value) {
|
||||
case '日':
|
||||
dayModel.value = val;
|
||||
break;
|
||||
case '周':
|
||||
weekModel.value = val;
|
||||
break;
|
||||
case '月':
|
||||
monthModel.value = val;
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 4. 核心工具函数:获取当前维度的日期范围(给父组件用)
|
||||
const getDateRange = (): { start: string; end: string } => {
|
||||
if (choseType.value === '周') {
|
||||
const range = DateUtils.getWeekRangeByDate(weekModel.value);
|
||||
return { start: range.startFormatted, end: range.endFormatted };
|
||||
}
|
||||
if (choseType.value === '月') {
|
||||
const date = monthModel.value;
|
||||
const range = DateUtils.getMonthRangeByYearMonth(date.getFullYear(), date.getMonth() + 1);
|
||||
return { start: range.startFormatted, end: range.endFormatted };
|
||||
}
|
||||
// 日维度
|
||||
const dateStr = DateUtils.formatDate(dayModel.value);
|
||||
return { start: dateStr, end: dateStr };
|
||||
};
|
||||
|
||||
// 5. 维度切换处理(日/周/月)
|
||||
const handleTypeChange = (val: '日' | '周' | '月') => {
|
||||
choseType.value = val;
|
||||
// 切换维度时默认选中当天对应的维度日期
|
||||
const today = new Date();
|
||||
currentDate.value = today;
|
||||
emitChange();
|
||||
|
||||
// 选择后隐藏下拉菜单
|
||||
setTimeout(() => {
|
||||
dropdownVisible.value = false;
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 6. 日期选择器变更处理
|
||||
const handleDateChange = (val: Date | string | undefined) => {
|
||||
if (!val) return;
|
||||
|
||||
let selectedDate: Date;
|
||||
if (val instanceof Date) {
|
||||
selectedDate = val;
|
||||
} else {
|
||||
// 处理字符串格式的日期
|
||||
if (choseType.value === '月') {
|
||||
// 月份选择器返回 YYYY-MM 格式
|
||||
const [year, month] = val.split('-').map(Number);
|
||||
selectedDate = new Date(year, month - 1, 1);
|
||||
} else {
|
||||
// 日和周选择器返回 YYYY-MM-DD 格式
|
||||
selectedDate = new Date(val);
|
||||
}
|
||||
}
|
||||
|
||||
currentDate.value = selectedDate;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
// 7. 日期导航(上一个/下一个维度)
|
||||
const navigate = (step: 1 | -1) => {
|
||||
const current = currentDate.value;
|
||||
const newDate = new Date(current);
|
||||
|
||||
switch (choseType.value) {
|
||||
case '日':
|
||||
newDate.setDate(current.getDate() + step);
|
||||
break;
|
||||
case '周':
|
||||
newDate.setDate(current.getDate() + step * 7);
|
||||
break;
|
||||
case '月':
|
||||
newDate.setMonth(current.getMonth() + step);
|
||||
break;
|
||||
}
|
||||
|
||||
currentDate.value = newDate;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
// 8. 跳转今天
|
||||
const navigateToToday = () => {
|
||||
currentDate.value = new Date();
|
||||
emitChange();
|
||||
};
|
||||
|
||||
// 9. 下拉菜单显示状态变化处理
|
||||
const handleDropdownVisibleChange = (visible: boolean) => {
|
||||
dropdownVisible.value = visible;
|
||||
};
|
||||
|
||||
// 10. 统一事件发射:向父组件传递完整信息
|
||||
const emitChange = () => {
|
||||
const result = {
|
||||
choseType: choseType.value,
|
||||
dayModel: dayModel.value,
|
||||
weekModel: weekModel.value,
|
||||
monthModel: monthModel.value,
|
||||
dateRange: getDateRange(), // 父组件直接可用的日期范围
|
||||
};
|
||||
|
||||
// 只有在初始化完成后才触发 update:modelValue 和 date-change 事件
|
||||
if (!isInitializing) {
|
||||
emit('update:modelValue', result);
|
||||
console.log('emitChange', result);
|
||||
emit('date-change', result); // 父组件监听此事件做后续处理
|
||||
}
|
||||
};
|
||||
|
||||
// 11. 监听父组件Props变化(同步外部修改)
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal && !isInitializing) {
|
||||
// 只有在初始化完成后才响应外部props变化
|
||||
choseType.value = newVal.choseType;
|
||||
dayModel.value = newVal.dayModel || new Date();
|
||||
weekModel.value = newVal.weekModel || new Date();
|
||||
monthModel.value = newVal.monthModel || new Date();
|
||||
emitChange();
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 初始化完成,设置标志位,并在之后触发一次事件
|
||||
setTimeout(() => {
|
||||
isInitializing = false;
|
||||
emitChange();
|
||||
}, 0);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.arco-dropdown-open .arco-icon-down {
|
||||
transform: rotate(180deg);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.prv-btn {
|
||||
background-color: #f7f8fa !important; /* 使用正确的6位十六进制颜色值 */
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
color: #211f24 !important;
|
||||
}
|
||||
.doption {
|
||||
width: 78px !important;
|
||||
}
|
||||
.prv-today {
|
||||
color: #211f24 !important;
|
||||
width: 80px !important;
|
||||
background-color: #f7f8fa !important;
|
||||
height: 28px;
|
||||
margin-left: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,625 @@
|
||||
<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>
|
||||
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="filter-popup-wrapper">
|
||||
<!-- 关键修复:添加手动控制弹窗显示/隐藏的逻辑 -->
|
||||
<a-popover
|
||||
trigger="click"
|
||||
:visible="visible"
|
||||
:overlayStyle="{ width: '360px' }"
|
||||
overlayClassName="filter-popup-popover"
|
||||
@clickoutside="handleClickOutside"
|
||||
>
|
||||
<template #content>
|
||||
<div class="filter-popup-content">
|
||||
<!-- 运营人员 -->
|
||||
<div class="flex items-center mb-3 select-container">
|
||||
<div class="w-70px pt-6px">运营人员</div>
|
||||
<a-space class="flex-1">
|
||||
<CommonSelect
|
||||
placeholder="请选择运营人员"
|
||||
:options="operators || []"
|
||||
v-model="localQuery.operator"
|
||||
@change="(val) => handleChange('operator', val)"
|
||||
class="!w-240px select-with-tags"
|
||||
:allowSearch="true"
|
||||
:maxTagCount="999"
|
||||
popup-container=".filter-popup-content"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 发布平台 -->
|
||||
<div class="flex items-center mb-3 select-container">
|
||||
<div class="w-70px pt-6px">发布平台</div>
|
||||
<a-space class="flex-1">
|
||||
<CommonSelect
|
||||
:options="platformOptions || []"
|
||||
v-model="localQuery.platform"
|
||||
@change="(val) => handleChange('platform', val)"
|
||||
class="!w-240px select-with-tags"
|
||||
placeholder="请选择发布平台"
|
||||
:allowSearch="true"
|
||||
:maxTagCount="999"
|
||||
popup-container=".filter-popup-content"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
<!-- 账号名称 -->
|
||||
<div class="flex items-center mb-3 select-container">
|
||||
<div class="w-70px pt-6px">账号名称</div>
|
||||
<a-space class="flex-1">
|
||||
<CommonSelect
|
||||
v-model="localQuery.accounts"
|
||||
:options="accountList || []"
|
||||
:multiple="true"
|
||||
@change="(val) => handleChange('accounts', val)"
|
||||
class="!w-240px select-with-tags"
|
||||
placeholder="请选择账号名称"
|
||||
:allowSearch="true"
|
||||
:maxTagCount="999"
|
||||
popup-container=".filter-popup-content"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 触发按钮:添加点击事件控制弹窗显示 -->
|
||||
<Button class="w-112px mr-8px" size="middle" @click="visible = !visible">
|
||||
<template #icon>
|
||||
<FilterOutlined class="color-#55585F" />
|
||||
</template>
|
||||
<template #default>筛选</template>
|
||||
</Button>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||
import { FilterOutlined } from '@ant-design/icons-vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
// 定义props和emit
|
||||
const props = defineProps({
|
||||
operators: Array,
|
||||
platformOptions: Array,
|
||||
accountList: Array,
|
||||
query: Object,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['filter-change']);
|
||||
|
||||
// 弹窗显示状态(关键:控制弹窗显示的核心变量)
|
||||
const visible = ref(false);
|
||||
|
||||
// 本地筛选状态(保持上次选择)
|
||||
const localQuery = ref({
|
||||
operator: props.query?.operator_ids,
|
||||
platform: props.query?.platforms,
|
||||
accounts: props.query?.ids || [],
|
||||
});
|
||||
|
||||
// 监听父组件query变化,同步到本地
|
||||
watch(
|
||||
() => props.query,
|
||||
(newQuery) => {
|
||||
if (newQuery) {
|
||||
localQuery.value = {
|
||||
operator: newQuery.operator_ids,
|
||||
platform: newQuery.platforms,
|
||||
accounts: newQuery.ids || [],
|
||||
};
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
// 处理筛选条件变化(不关闭弹窗,直接触发筛选)
|
||||
const handleChange = (field, value) => {
|
||||
localQuery.value[field] = value;
|
||||
// 直接触发筛选变更,不需要确认按钮
|
||||
emit('filter-change', {
|
||||
operator: localQuery.value.operator,
|
||||
platform: localQuery.value.platform,
|
||||
accounts: localQuery.value.accounts,
|
||||
});
|
||||
|
||||
// 选择后自动隐藏下拉菜单
|
||||
setTimeout(() => {
|
||||
visible.value = false;
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// 点击外部关闭弹窗
|
||||
const handleClickOutside = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-popup-popover :deep(.ant-popover-inner-content) {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 24px;
|
||||
margin-top: 8px;
|
||||
margin-right: 50px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.select-with-tags :deep(.ant-select-selector) {
|
||||
height: auto !important;
|
||||
min-height: 32px !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.select-with-tags :deep(.ant-select-selection-item) {
|
||||
max-width: none !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: unset !important;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 确保选择器容器能够适应内容高度 */
|
||||
.select-with-tags :deep(.ant-select) {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.select-with-tags :deep(.ant-select-multiple .ant-select-selection-item) {
|
||||
height: auto !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.select-with-tags :deep(.ant-select-selection-overflow) {
|
||||
flex-wrap: wrap !important;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="成品库"
|
||||
cancel-text="取消"
|
||||
ok-text="确定"
|
||||
placement="right"
|
||||
:visible="visible"
|
||||
@after-visible-change="onAfterVisibleChange"
|
||||
@cancel="handleCancel"
|
||||
width="904px"
|
||||
class="task-drawer"
|
||||
style="right: 481px"
|
||||
>
|
||||
<!-- 成品库表格 -->
|
||||
<Table
|
||||
:data-source="materialData"
|
||||
bordered
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:row-selection="rowSelection"
|
||||
>
|
||||
<template #cover="{ record }">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
:src="record.cover ? record.cover : icon4"
|
||||
alt="内容预览"
|
||||
class="w-44px h-44px border-rounded-8px bg-#F0EDFF"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<div class="flex items-center">{{ record.title }}</div>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
<div class="flex items-center">
|
||||
<img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
|
||||
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
|
||||
record.type === EnumManuscriptType.Image ? '图文' : '视频'
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #uploader="{ record }">
|
||||
<div class="flex items-center">{{ record.last_modifier?.mobile || '-' }}</div>
|
||||
</template>
|
||||
<template #audit_status="{ record }">
|
||||
<div
|
||||
class="flex items-center justify-center font-size-14px p-4px border-rounded-4px"
|
||||
:style="getAuditStatusStyle(record.audit_status)"
|
||||
>
|
||||
{{ getStatus(record.audit_status) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #last_modified_at="{ record }">
|
||||
<div class="flex items-center">
|
||||
{{ record.last_modified_at ? dayjs(record.last_modified_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div 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>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="color-#737478 font-size-14px">已选择:</div>
|
||||
<div class="color-#737478 font-size-14px">{{ choseText }}</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button @click="handleCancel">取消</Button>
|
||||
<Button class="ml-16px" type="primary" @click="handleOk">确定</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, defineProps, defineEmits, watchEffect } from 'vue';
|
||||
import { Table, Button, Pagination } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
|
||||
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
|
||||
import { getWorksPage } from '@/api/all/generationWorkshop.ts';
|
||||
// 引入图片资源
|
||||
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';
|
||||
|
||||
// 定义Props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
query: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
platforms: undefined,
|
||||
operator_ids: undefined,
|
||||
ids: [],
|
||||
keyword: '',
|
||||
audit_status: undefined,
|
||||
type: undefined,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// 定义Emits
|
||||
const emit = defineEmits(['update:visible', 'after-visible-change', 'confirm', 'cancel']);
|
||||
|
||||
// 内部状态管理
|
||||
const materialData = ref([]);
|
||||
const choseText = ref('');
|
||||
const choseImgArray = ref([]);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref([
|
||||
{ title: '图片/视频', dataIndex: 'cover', width: 200, slots: { customRender: 'cover' } },
|
||||
{ title: '文件名称', dataIndex: 'name', width: 200, slots: { customRender: 'name' } },
|
||||
{ title: '稿件类型', dataIndex: 'type', width: 100, slots: { customRender: 'type' } },
|
||||
{ title: '审核状态', dataIndex: 'audit_status', width: 100, slots: { customRender: 'audit_status' } },
|
||||
{ title: '最后修改时间', dataIndex: 'last_modified_at', width: 200, slots: { customRender: 'last_modified_at' } },
|
||||
{ title: '上传人员', dataIndex: 'uploader', width: 200, slots: { customRender: 'uploader' } },
|
||||
]);
|
||||
|
||||
// 1. 先定义数据获取函数,确保使用最新的query参数
|
||||
const fetchProductData = async () => {
|
||||
try {
|
||||
// 使用主组件传递的最新查询参数
|
||||
const params = { ...props.query };
|
||||
console.log('成品库请求参数:', params); // 调试用
|
||||
|
||||
const res = await getWorksPage(params);
|
||||
materialData.value = [];
|
||||
pageInfo.value.total = res.data.total;
|
||||
if (pageInfo.value.page === 1) {
|
||||
materialData.value = res.data.data;
|
||||
} else {
|
||||
materialData.value = [...materialData.value, ...res.data.data];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取成品库数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 初始化表格选择逻辑
|
||||
const { pageInfo, onPageChange, onPageSizeChange, rowSelection, selectedRowKeys, selectedRows } =
|
||||
useTableSelectionWithPagination({
|
||||
rowKey: 'id',
|
||||
type: 'radio', // 改为单选模式
|
||||
onPageChange: fetchProductData,
|
||||
onPageSizeChange: fetchProductData,
|
||||
});
|
||||
|
||||
// 监听query参数变化,当主组件传递的参数变化时重新请求数据
|
||||
watchEffect(() => {
|
||||
if (props.visible) {
|
||||
console.log('成品库查询参数变化,重新加载数据');
|
||||
fetchProductData();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听选中项变化
|
||||
watch(selectedRows, (newRows) => {
|
||||
if (rowSelection.value.type === 'radio') {
|
||||
// 单选模式
|
||||
choseText.value = newRows.length > 0 ? '1个稿件' : '0个稿件';
|
||||
// 如果类型是文本,则choseImgArray为空数组
|
||||
if (newRows.length > 0 && newRows[0].type !== 'text') {
|
||||
choseImgArray.value = [newRows[0]];
|
||||
} else {
|
||||
choseImgArray.value = [];
|
||||
}
|
||||
} else {
|
||||
// 多选模式
|
||||
choseText.value = newRows.length + '个稿件';
|
||||
// 过滤掉文本类型,只保留非文本类型的项目
|
||||
choseImgArray.value = newRows.filter((item) => item.type !== 'text');
|
||||
}
|
||||
});
|
||||
|
||||
// 格式化审核状态显示
|
||||
const getStatus = (status: number) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '待审核';
|
||||
case 2:
|
||||
return '审核中';
|
||||
case 3:
|
||||
return '已通过';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
};
|
||||
|
||||
const getAuditStatusStyle = (status: number) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return { backgroundColor: '#f0f0f0', color: '#666' };
|
||||
case 2:
|
||||
return { backgroundColor: '#fff7ed', color: '#ff9c00' };
|
||||
case 3:
|
||||
return { backgroundColor: '#e6ffe6', color: '#008000' };
|
||||
default:
|
||||
return { backgroundColor: '#f0f0f0', color: '#666' };
|
||||
}
|
||||
};
|
||||
|
||||
// 抽屉显示状态变化处理
|
||||
const onAfterVisibleChange = (visible: boolean) => {
|
||||
emit('after-visible-change', visible);
|
||||
|
||||
if (visible) {
|
||||
// 当抽屉显示时,使用最新参数请求数据
|
||||
fetchProductData();
|
||||
} else {
|
||||
// 关闭时清空数据
|
||||
materialData.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
choseText.value = '';
|
||||
choseImgArray.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 取消按钮处理
|
||||
const handleCancel = () => {
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
// 确定按钮处理
|
||||
const handleOk = () => {
|
||||
emit('confirm', {
|
||||
selectedKeys: selectedRowKeys.value,
|
||||
selectedData: selectedRows.value,
|
||||
selectedRows: selectedRows.value,
|
||||
choseText: choseText.value,
|
||||
choseImgArray: choseImgArray.value,
|
||||
});
|
||||
emit('update:visible', false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 16px 0;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="原料库"
|
||||
cancel-text="取消"
|
||||
ok-text="确定"
|
||||
placement="right"
|
||||
:visible="visible"
|
||||
@after-visible-change="onAfterVisibleChange"
|
||||
@cancel="handleCancel"
|
||||
width="904px"
|
||||
class="task-drawer"
|
||||
style="right: 481px"
|
||||
>
|
||||
<!-- 成品库表格 -->
|
||||
<Table
|
||||
:data-source="materialData"
|
||||
bordered
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:row-selection="rowSelection"
|
||||
>
|
||||
<template #name="{ record }">
|
||||
<div class="name-cell flex items-center">
|
||||
<img
|
||||
:src="record.type == 2 ? icon5 : record.cover"
|
||||
alt="类型图标"
|
||||
class="w-44px h-44px border-rounded-8px bg-#F0EDFF"
|
||||
/>
|
||||
<div class="flex flex-col ml-8px">
|
||||
<span class="material-name">{{ record.name }}</span>
|
||||
<span class="material-type">{{ record.uid }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #type="{ record }">
|
||||
<div class="flex items-center">
|
||||
<span>{{ getType(record.type) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #origin="{ record }">
|
||||
<span>{{ getOrigin(record.origin) }}</span>
|
||||
</template>
|
||||
<template #created_at="{ record }">
|
||||
<div class="flex items-center">
|
||||
{{ record.created_at ? dayjs(record.created_at * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' }}
|
||||
</div>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div 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>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<template #footer>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="color-#737478 font-size-14px">已选择:</div>
|
||||
<div class="color-#737478 font-size-14px">{{ choseText }}</div>
|
||||
<div v-for="item in choseImgArray" :key="item.id" class="ml-16px">
|
||||
<img
|
||||
:src="item.cover ? item.cover : icon4"
|
||||
alt="选中的内容"
|
||||
class="w-44px h-44px border-rounded-8px bg-#F0EDFF"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in choseTextArray"
|
||||
:key="item.id"
|
||||
class="ml-16px bg-#F7F8FA h-44px overflow-hidden w-75px border-rounded-8px flex items-center"
|
||||
>
|
||||
<div class="whitespace-nowrap overflow-hidden text-ellipsis w-full px-2" :title="item.name">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button @click="handleCancel">取消</Button>
|
||||
<Button class="ml-16px" type="primary" @click="handleOk">确定</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, defineProps, defineEmits, watchEffect } from 'vue';
|
||||
import { Table, Button, Pagination } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
|
||||
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
|
||||
import { getRawMaterialsPage } from '@/api/all/generationWorkshop';
|
||||
// 引入图片资源
|
||||
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 { RawMaterialType } from '@/views/material-center/components/raw-material/constants';
|
||||
import icon5 from '../../../../views/material-center/components/raw-material/img/icon-no-text.png';
|
||||
// 定义Props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
query: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
platforms: undefined,
|
||||
operator_ids: undefined,
|
||||
ids: [],
|
||||
keyword: '',
|
||||
audit_status: undefined,
|
||||
type: undefined,
|
||||
}),
|
||||
},
|
||||
});
|
||||
// 辅助函数
|
||||
const getType = (type: number): string => {
|
||||
const typeMap = {
|
||||
[RawMaterialType.Image]: '图片',
|
||||
[RawMaterialType.Video]: '视频',
|
||||
[RawMaterialType.Text]: '文本',
|
||||
};
|
||||
return typeMap[type] || '未知';
|
||||
};
|
||||
|
||||
const getOrigin = (origin: number): string => {
|
||||
const fromMap = {
|
||||
0: '本地上传',
|
||||
1: 'AI生成',
|
||||
};
|
||||
return fromMap[origin] || '未知';
|
||||
};
|
||||
// 定义Emits类型
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(
|
||||
e: 'confirm',
|
||||
data: {
|
||||
selectedKeys: string[];
|
||||
selectedData: any[];
|
||||
choseText: string;
|
||||
choseImgArray: any[];
|
||||
selectedTexts: string[];
|
||||
},
|
||||
): void;
|
||||
(e: 'cancel'): void;
|
||||
}>();
|
||||
// 内部状态管理
|
||||
const materialData = ref([]);
|
||||
const choseText = ref('');
|
||||
const choseImgArray = ref([]);
|
||||
const choseTextArray = ref([]);
|
||||
// 表格列配置
|
||||
const columns = ref([
|
||||
{ title: '文件名称', dataIndex: 'name', width: 200, slots: { customRender: 'name' } },
|
||||
{ title: '类型', dataIndex: 'type', width: 100, slots: { customRender: 'type' } },
|
||||
{ title: '来源', dataIndex: 'origin', width: 100, slots: { customRender: 'origin' } },
|
||||
{ title: '上传时间', dataIndex: 'created_at', width: 200, slots: { customRender: 'created_at' } },
|
||||
]);
|
||||
|
||||
// 1. 先定义数据获取函数,确保使用最新的query参数
|
||||
const fetchProductData = async () => {
|
||||
try {
|
||||
// 使用主组件传递的最新查询参数
|
||||
const params = { ...props.query };
|
||||
console.log('成品库请求参数:', params); // 调试用
|
||||
|
||||
const res = await getRawMaterialsPage(params);
|
||||
materialData.value = [];
|
||||
pageInfo.value.total = res.data.total;
|
||||
if (pageInfo.value.page === 1) {
|
||||
materialData.value = res.data.data;
|
||||
} else {
|
||||
materialData.value = [...materialData.value, ...res.data.data];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取成品库数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 初始化表格选择逻辑
|
||||
const { pageInfo, onPageChange, onPageSizeChange, rowSelection, selectedRowKeys } = useTableSelectionWithPagination({
|
||||
rowKey: 'id',
|
||||
onPageChange: fetchProductData,
|
||||
onPageSizeChange: fetchProductData,
|
||||
});
|
||||
|
||||
// 监听query参数变化,当主组件传递的参数变化时重新请求数据
|
||||
watchEffect(() => {
|
||||
if (props.visible) {
|
||||
console.log('成品库查询参数变化,重新加载数据');
|
||||
fetchProductData();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听选中项变化
|
||||
watch(selectedRowKeys, (newKeys) => {
|
||||
const filteredData = materialData.value.filter((item) => newKeys.includes(item.id));
|
||||
|
||||
const typeCount: Record<string, number> = {};
|
||||
filteredData.forEach((item) => {
|
||||
typeCount[item.type] = (typeCount[item.type] || 0) + 1;
|
||||
});
|
||||
|
||||
choseText.value = Object.entries(typeCount)
|
||||
.map(([type, count]) => {
|
||||
const typeName = getType(Number(type));
|
||||
return `${typeName}: ${count}个`;
|
||||
})
|
||||
.join(' ');
|
||||
|
||||
choseImgArray.value = filteredData.filter((item) => [0, 1].includes(item.type));
|
||||
choseTextArray.value = filteredData.filter((item) => [2].includes(item.type));
|
||||
});
|
||||
|
||||
// 格式化审核状态显示
|
||||
const getStatus = (status: number) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '待审核';
|
||||
case 1:
|
||||
return '审核中';
|
||||
case 2:
|
||||
return '已通过';
|
||||
case 3:
|
||||
return '已拒绝';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
};
|
||||
|
||||
// 抽屉显示状态变化处理
|
||||
const onAfterVisibleChange = (visible: boolean) => {
|
||||
emit('after-visible-change', visible);
|
||||
if (visible) {
|
||||
// 当抽屉显示时,使用最新参数请求数据
|
||||
fetchProductData();
|
||||
} else {
|
||||
// 关闭时清空数据
|
||||
materialData.value = [];
|
||||
selectedRowKeys.value = [];
|
||||
choseText.value = '';
|
||||
choseImgArray.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 取消按钮处理
|
||||
const handleCancel = () => {
|
||||
console.log('取消');
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
// 确定按钮处理
|
||||
const handleOk = () => {
|
||||
const selectedData = materialData.value.filter((item) => selectedRowKeys.value.includes(item.id));
|
||||
const selectedTexts = selectedData
|
||||
.filter((item) => item.type === RawMaterialType.Text)
|
||||
.map((item) => item.content || item.name);
|
||||
emit('confirm', {
|
||||
selectedKeys: selectedRowKeys.value,
|
||||
selectedData,
|
||||
choseText: choseText.value,
|
||||
choseImgArray: choseImgArray.value,
|
||||
selectedTexts: selectedTexts,
|
||||
});
|
||||
emit('update:visible', false);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 16px 0;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<a-trigger trigger="click" position="br" @click.stop @popup-visible-change="onPopupVisibleChange">
|
||||
<div class="task-item">
|
||||
<div class="color-indicator" :style="{ background: getTaskColor() }"></div>
|
||||
<div>{{ timestampToTime(task.execution_time) }}</div>
|
||||
<div class="task-name" :title="task?.name || '-'">{{ task?.name || 'AI生成内容' }}</div>
|
||||
</div>
|
||||
<template #content>
|
||||
<div class="flex flex-col items-start popup-content" @click="gotoDetail">
|
||||
<div class="flex flex-col items-start p-16px w-full" @click="gotoDetail">
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<div class="flex items-center title-container">
|
||||
<img
|
||||
:src="getPlatformIcon(record.platform)"
|
||||
style="border-radius: 8px; width: 16px; height: 16px; margin-right: 8px; font-size: 14px"
|
||||
/>
|
||||
<div class="task-title" :title="record.name || 'AI生成内容'">{{ record.name || 'AI生成内容' }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="status-tag"
|
||||
:style="{
|
||||
color: getTaskColor(),
|
||||
backgroundColor: getTaskColor() + '50',
|
||||
}"
|
||||
>
|
||||
{{ getTaskStatusText(task.status) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-12px color-#939499 mt-4px h-22px" @click="gotoDetail">
|
||||
{{ timestampToTime1(task.execution_time) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="font-size-14px color-#211F24 color-#211F24 task-description" :title="task.name || 'AI生成内容'">
|
||||
{{ task.name || 'AI生成内容' }}
|
||||
</div>
|
||||
<div v-if="task.ai_generate_status == 0" class="AILoding">
|
||||
<ASpin />
|
||||
<div style="color: #9a56ba">内容生成中...</div>
|
||||
<div style="color: #9a56ba">完成后将自动展示,您可先返回其他操作</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="task.ai_generate_status == 1 && taskDetail" class="w-full pl-16px">
|
||||
<div
|
||||
v-for="item in choseTextArray"
|
||||
: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 choseImgArray"
|
||||
:key="item.id"
|
||||
:src="item.cover || item.url"
|
||||
class="w-88 h-88 mr-8px border-rounded-8px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center w-full">
|
||||
<video
|
||||
v-for="item in choseVideoArray"
|
||||
:key="item.id"
|
||||
:src="item.cover || item.url"
|
||||
class="w-44 h-44 mr-8px border-rounded-8px object-fit-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center mt-12px w-full h-1px bg-#E6E6E8"></div>
|
||||
<div class="flex items-center mt-12px mb-16px action-buttons w-full px-16px">
|
||||
<div class="flex items-center mr-12px" @click.stop="handleDelete" v-if="task.status != 1">
|
||||
<icon-delete style="font-size: 20px; margin-left: 0" />
|
||||
</div>
|
||||
<div class="flex w-full" :class="{ 'justify-between': task.ai_generate_status == 0 }">
|
||||
<a-date-picker
|
||||
v-model="datePickerValue"
|
||||
placeholder="修改发布时间"
|
||||
:show-time="{ format: 'HH:mm' }"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="YYYY-MM-DD HH:mm"
|
||||
@change="onChange"
|
||||
@ok="onOk"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@focus.stop
|
||||
/>
|
||||
<button v-if="task.ai_generate_status == 0" class="opt-btn ml-12px opt-right" @click.stop="handleAICreate">
|
||||
AI立即生成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { Spin as ASpin } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
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 { getTaskSchedulesDetail } from '@/api/all/assignment-management';
|
||||
import { DatePicker } from '@arco-design/web-vue';
|
||||
// 定义props和emit
|
||||
const props = defineProps({
|
||||
task: Object,
|
||||
record: Object,
|
||||
});
|
||||
|
||||
const taskDetail = ref(null);
|
||||
const choseImgArray = ref([]);
|
||||
const choseTextArray = ref([]);
|
||||
const choseVideoArray = ref([]);
|
||||
const getTaskDetail = async () => {
|
||||
if (!props.task || !props.task.id) return;
|
||||
|
||||
try {
|
||||
const res = await getTaskSchedulesDetail(props.task.id);
|
||||
|
||||
if (res && res.data) {
|
||||
datePickerValue.value = dayjs(res.data.execution_time * 1000);
|
||||
console.log('任务详情:', datePickerValue.value);
|
||||
if ('work' in res.data && res.data.work) {
|
||||
taskDetail.value = res.data.work;
|
||||
choseImgArray.value = res.data.work.files.filter((item) => [0].includes(item.type));
|
||||
choseTextArray.value = res.data.work.files.filter((item) => [2].includes(item.type));
|
||||
choseVideoArray.value = res.data.work.files.filter((item) => [1].includes(item.type));
|
||||
} else {
|
||||
taskDetail.value = res.data.raw_materials;
|
||||
choseImgArray.value = taskDetail.value.filter((item) => [0].includes(item.type));
|
||||
choseTextArray.value = taskDetail.value.filter((item) => [2].includes(item.type));
|
||||
choseVideoArray.value = taskDetail.value.filter((item) => [1].includes(item.type));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取任务详情失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (date, dateString) => {
|
||||
console.log('Selected Date: ', date, dateString);
|
||||
};
|
||||
|
||||
const onOk = (value) => {
|
||||
console.log('DatePicker OK: ', value);
|
||||
if (value) {
|
||||
emit('handle-task', 'edit-time', props.task, value);
|
||||
}
|
||||
};
|
||||
|
||||
const onPopupVisibleChange = (visible) => {
|
||||
if (visible) {
|
||||
getTaskDetail();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
emit('handle-task', 'delete', props.task, props.record);
|
||||
};
|
||||
const gotoDetail = () => {
|
||||
console.log('跳转详情');
|
||||
emit('handle-task', 'goto-detail', props.task, props.record);
|
||||
};
|
||||
const handleTimeChange = (time: string) => {
|
||||
// if (time) {
|
||||
// emit('handle-task', 'edit-time', props.task, timestampToTime1() + ' ' + time + ':00');
|
||||
// }
|
||||
};
|
||||
const handleAICreate = () => {
|
||||
emit('handle-task', 'ai-create', props.task, props.record);
|
||||
};
|
||||
const timestampToTime = (timestamp: number): string => {
|
||||
// 如果没有传入时间戳,则返回空字符串
|
||||
if (!timestamp) return '';
|
||||
|
||||
// 将时间戳转换为毫秒(如果时间戳是秒单位的话)
|
||||
const date = new Date(timestamp * 1000);
|
||||
|
||||
// 格式化为 HH:mm 格式
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${hours}:${minutes}`;
|
||||
};
|
||||
const timestampToTime1 = (timestamp: number): string => {
|
||||
// 如果没有传入时间戳,则使用当前时间
|
||||
if (!timestamp) {
|
||||
timestamp = Date.now() / 1000; // 使用秒级时间戳保持一致性
|
||||
}
|
||||
|
||||
const date = new Date(timestamp * 1000);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // 补零
|
||||
const day = String(date.getDate()).padStart(2, '0'); // 补零
|
||||
|
||||
return `${month}月${day}日`;
|
||||
};
|
||||
|
||||
const emit = defineEmits(['filter-change', 'handle-task']);
|
||||
|
||||
// 日期选择器的值
|
||||
const datePickerValue = ref(null);
|
||||
|
||||
// 平台配置
|
||||
const platformConfig = {
|
||||
icons: {
|
||||
0: iconDy,
|
||||
1: iconXhs,
|
||||
2: iconBilibili,
|
||||
3: iconKs,
|
||||
4: iconSph,
|
||||
5: iconWb,
|
||||
6: iconGzh,
|
||||
},
|
||||
};
|
||||
|
||||
// 获取平台图标
|
||||
const getPlatformIcon = (platform: number) => platformConfig.icons[platform] || iconWarn;
|
||||
|
||||
// 根据任务类型获取颜色
|
||||
const getTaskColor = () => {
|
||||
if (!props.task) return '#000';
|
||||
|
||||
// 根据colorTip.vue中的颜色定义设置不同状态的颜色
|
||||
switch (props.task.status) {
|
||||
case 0: // 待生成
|
||||
return '#ffae00';
|
||||
case 1: // 待发布
|
||||
return '#6d4cfe';
|
||||
case 2: // 已发布
|
||||
return '#939499';
|
||||
case 3: // 发布失败
|
||||
return '#f64b31';
|
||||
default:
|
||||
return props.task.color || '#939499';
|
||||
}
|
||||
};
|
||||
|
||||
const getTaskStatusText = (status) => {
|
||||
console.log('任务状态:', status);
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '待生成';
|
||||
case 1:
|
||||
return '待发布';
|
||||
case 2:
|
||||
return '已发布';
|
||||
case 3:
|
||||
return '发布失败';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.task-item {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
height: 19px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-indicator {
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 388px;
|
||||
background-color: #fff;
|
||||
box-shadow: #e6e6e8 0px 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.task-description {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: 365px;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 12px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.opt-btn {
|
||||
width: 154px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
background: #f2f3f5;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.opt-right {
|
||||
background: linear-gradient(90deg, #fbf9ff 0%, #f3fafe 100%);
|
||||
color: #9a56ba;
|
||||
}
|
||||
|
||||
.AILoding {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 356px;
|
||||
height: 108px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(90deg, #fbf9ff 0%, #f3fafe 100%);
|
||||
color: linear-gradient(90deg, #9a56ba 0%, #576fd1 100%);
|
||||
margin-left: 16px;
|
||||
font-weight: 400;
|
||||
font-style: Regular;
|
||||
font-size: 12px;
|
||||
leading-trim: NONE;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
667
src/views/property-marketing/assignment-management/index.vue
Normal file
@ -0,0 +1,667 @@
|
||||
<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', y: 600 }"
|
||||
style="width: 100%"
|
||||
:pagination="false"
|
||||
@change="handleTableChange"
|
||||
@cell-click="handleCellClick"
|
||||
>
|
||||
<!-- 空数据显示 -->
|
||||
<template #empty>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<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">
|
||||
<img
|
||||
:src="getPlatformIcon(record.platform)"
|
||||
style="border-radius: 8px; width: 16px; height: 16px; margin-right: 8px"
|
||||
: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 = () => {
|
||||
console.log('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);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 确认删除
|
||||
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 {
|
||||
height: 30px;
|
||||
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;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
: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>
|
||||
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div
|
||||
class="table-wrap bg-#fff rounded-8px px-24px py-24px flex flex-col justify-start items-center"
|
||||
style="height: 90%"
|
||||
>
|
||||
<!-- 使用 flex 布局实现整体居中 -->
|
||||
<div class="flex justify-center" style="width: 100%">
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="font-bold color-#211F24" style="font-size: 28px">{{ task.name }}</div>
|
||||
<div class="color-#666666 mt-8px">{{ timestampToDayNumber(task.created_at) }}</div>
|
||||
<div class="color-#666666 mt-8px">{{ task.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end mt-24px">
|
||||
<a-button type="outline" @click="handleBack">返回</a-button>
|
||||
<a-button type="outline" class="ml-12px">编辑</a-button>
|
||||
<a-button type="primary" class="ml-12px">去审核</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import router from '@/router';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { getTaskSchedulesDetail } from '@/api/all/assignment-management';
|
||||
import DateUtils from '@/utils/DateUtils';
|
||||
const task = ref({});
|
||||
// 时间戳转日期格式,并提取日期数字
|
||||
const timestampToDayNumber = (timestamp: number) => {
|
||||
const date = new Date(timestamp * 1000); // 假设时间戳是秒级
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${month}月${day}日`;
|
||||
};
|
||||
const handleBack = () => {
|
||||
router.go(-1);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const route = useRoute();
|
||||
const noteId = route.params.id; // 从路由参数中获取笔记ID
|
||||
|
||||
if (noteId) {
|
||||
getTaskSchedulesDetail(noteId)
|
||||
.then((response) => {
|
||||
if (response.data) {
|
||||
// 根据API响应结构调整
|
||||
const apiData = response.data.data || response.data;
|
||||
console.log(apiData);
|
||||
task.value = apiData;
|
||||
}
|
||||
})
|
||||
.catch((error) => {});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.management-detail {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.van-card__title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.van-card__desc {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.van-card__price {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 新增样式 */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 添加新的样式规则 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.mt-8px {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
@ -39,7 +39,27 @@
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<span class="label">平台</span>
|
||||
<img :src="item.platform === 0 ? icon1 : icon2" width="16" height="16" />
|
||||
<img
|
||||
:src="
|
||||
item.platform === 0
|
||||
? icon1 // 抖音
|
||||
: item.platform === 1
|
||||
? icon2 // 小红书
|
||||
: item.platform === 4
|
||||
? icon4 // 视频号
|
||||
: item.platform === 5
|
||||
? icon5 // 微博
|
||||
: item.platform === 6
|
||||
? icon6 // 公众号
|
||||
: item.platform === 3
|
||||
? icon7 // 快手
|
||||
: item.platform === 2
|
||||
? icon8 // B站
|
||||
: icon3 // 默认图标
|
||||
"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<span class="label">账号ID</span>
|
||||
@ -150,7 +170,11 @@ import FooterBtn from './footer-btn';
|
||||
import icon1 from '@/assets/img/platform/icon-dy.png';
|
||||
import icon2 from '@/assets/img/platform/icon-xhs.png';
|
||||
import icon3 from '@/assets/img/media-account/icon-warn.png';
|
||||
|
||||
import icon4 from '@/assets/img/platform/icon-sph.png';
|
||||
import icon5 from '@/assets/img/platform/icon-wb.png';
|
||||
import icon6 from '@/assets/img/platform/icon-gzh.png';
|
||||
import icon7 from '@/assets/img/platform/icon-ks.png';
|
||||
import icon8 from '@/assets/img/platform/icon-bilibili.png';
|
||||
const props = defineProps({
|
||||
dataSource: {
|
||||
type: Array,
|
||||
|
||||
@ -43,8 +43,17 @@ import {
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-download.png';
|
||||
import icon2 from '@/assets/img/media-account/icon-delete.png';
|
||||
|
||||
|
||||
|
||||
import icon3 from '@/assets/img/platform/icon-dy.png';
|
||||
import icon4 from '@/assets/img/platform/icon-xhs.png';
|
||||
import icon5 from '@/assets/img/platform/icon-bilibili.png';
|
||||
import icon6 from '@/assets/img/platform/icon-ks.png';
|
||||
import icon7 from '@/assets/img/platform/icon-sph.png';
|
||||
import icon8 from '@/assets/img/platform/icon-wb.png';
|
||||
import icon9 from '@/assets/img/platform/icon-gzh.png';
|
||||
|
||||
// import icon5 from '@/assets/img/media-account/icon-warn-1.png';
|
||||
// import icon6 from '@/assets/img/media-account/icon-success.png';
|
||||
|
||||
@ -104,6 +113,7 @@ export default {
|
||||
},
|
||||
],
|
||||
operator_name: [{ required: true, message: '请输入运营人员' }],
|
||||
end_work_link: [{ required: true, message: '请输入笔记链接' }],
|
||||
};
|
||||
|
||||
const isBatchImport = computed(() => uploadType.value === 'batch');
|
||||
@ -396,11 +406,37 @@ export default {
|
||||
</FormItem>
|
||||
<FormItem label="运营平台" required={!isEdit.value}>
|
||||
{isEdit.value ? (
|
||||
<img src={form.value.platform === 0 ? icon3 : icon4} width="24" height="24" />
|
||||
<img
|
||||
src={
|
||||
form.value.platform === 0
|
||||
? icon3
|
||||
: form.value.platform === 1
|
||||
? icon4
|
||||
: form.value.platform === 2
|
||||
? icon5
|
||||
: form.value.platform === 3
|
||||
? icon6
|
||||
: form.value.platform === 4
|
||||
? icon7
|
||||
: form.value.platform === 5
|
||||
? icon8
|
||||
: form.value.platform === 6
|
||||
? icon9
|
||||
: icon3
|
||||
/* default icon */
|
||||
}
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
) : (
|
||||
<RadioGroup v-model:value={form.value.platform}>
|
||||
<Radio value={1}>小红书</Radio>
|
||||
<Radio value={0}>抖音</Radio>
|
||||
<Radio value={2}>B站</Radio>
|
||||
<Radio value={3}>快手</Radio>
|
||||
<Radio value={4}>视频号</Radio>
|
||||
<Radio value={5}>微博</Radio>
|
||||
<Radio value={6}>公众号</Radio>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormItem>
|
||||
@ -452,7 +488,8 @@ export default {
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="笔记链接"
|
||||
name="end_work_link"
|
||||
field="end_work_link"
|
||||
required
|
||||
v-slots={{
|
||||
label: () =>
|
||||
renderLabel('笔记链接', '平台将从该笔记“之后”的内容开始同步,该笔记及更早的数据均不采集'),
|
||||
@ -460,7 +497,7 @@ export default {
|
||||
>
|
||||
<TextArea
|
||||
v-model:value={form.value.end_work_link}
|
||||
placeholder="请输入..."
|
||||
placeholder="请输入笔记链接。若无需输入,填写 “无” "
|
||||
size="large"
|
||||
autoSize={{ minRows: 3, maxRows: 5 }}
|
||||
/>
|
||||
|
||||
@ -150,7 +150,29 @@ const isBtnDisabled = computed(() => {
|
||||
|
||||
const open = (accountId, platformCode) => {
|
||||
id.value = accountId;
|
||||
platform.value = platformCode === 0 ? '抖音' : '小红书';
|
||||
switch (platformCode) {
|
||||
case 0:
|
||||
platform.value = '抖音';
|
||||
break;
|
||||
case 1:
|
||||
platform.value = '小红书';
|
||||
break;
|
||||
case 2:
|
||||
platform.value = 'B站';
|
||||
break;
|
||||
case 3:
|
||||
platform.value = '快手';
|
||||
break;
|
||||
case 4:
|
||||
platform.value = '视频号';
|
||||
break;
|
||||
case 5:
|
||||
platform.value = '微博';
|
||||
break;
|
||||
case 6:
|
||||
platform.value = '公众号';
|
||||
break;
|
||||
}
|
||||
modalState.value = MODAL_STATE.QR_LOADING;
|
||||
getAuthorizedQrCode();
|
||||
visible.value = true;
|
||||
@ -159,7 +181,6 @@ const open = (accountId, platformCode) => {
|
||||
const resetTaskFields = () => {
|
||||
modalState.value = MODAL_STATE.QR_LOADING;
|
||||
failReason.value = '';
|
||||
platform.value = '';
|
||||
progress.value = 0;
|
||||
qrCodeUrl.value = '';
|
||||
};
|
||||
|
||||
@ -166,7 +166,30 @@ const confirmBtnText = computed(() => {
|
||||
|
||||
const open = (accountId, platformCode) => {
|
||||
id.value = accountId;
|
||||
platform.value = platformCode === 0 ? '抖音' : '小红书';
|
||||
console.log('pingtai code', platformCode);
|
||||
switch (platformCode) {
|
||||
case 0:
|
||||
platform.value = '抖音';
|
||||
break;
|
||||
case 1:
|
||||
platform.value = '小红书';
|
||||
break;
|
||||
case 2:
|
||||
platform.value = 'B站';
|
||||
break;
|
||||
case 3:
|
||||
platform.value = '快手';
|
||||
break;
|
||||
case 4:
|
||||
platform.value = '视频号';
|
||||
break;
|
||||
case 5:
|
||||
platform.value = '微博';
|
||||
break;
|
||||
case 6:
|
||||
platform.value = '公众号';
|
||||
break;
|
||||
}
|
||||
modalState.value = MODAL_STATE.QR_LOADING;
|
||||
getAuthorizedQrCode();
|
||||
visible.value = true;
|
||||
@ -175,7 +198,6 @@ const open = (accountId, platformCode) => {
|
||||
const resetTaskFields = () => {
|
||||
modalState.value = MODAL_STATE.QR_LOADING;
|
||||
failReason.value = '';
|
||||
platform.value = '';
|
||||
progress.value = 0;
|
||||
qrCodeUrl.value = '';
|
||||
isNicknameChanged.value = false;
|
||||
|
||||