feat: 初始化内容稿件模块

This commit is contained in:
rd
2025-07-25 15:11:57 +08:00
parent 28dc67bfa4
commit ae065418d5
33 changed files with 1022 additions and 36 deletions

5
env.d.ts vendored
View File

@ -1 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const vueComponent: DefineComponent<{}, {}, any>;
export default vueComponent;
}

View File

@ -23,7 +23,8 @@ export const DEFAULT_ROUTE = {
export const MENU_GROUP_IDS = { export const MENU_GROUP_IDS = {
DATA_ENGINE_ID: 1, // 全域数据分析 DATA_ENGINE_ID: 1, // 全域数据分析
MANAGEMENT_ID: -1, // 管理中心 CREATIVE_GENERATION_WORKSHOP_ID: 2, // 创意生成工坊
PROPERTY_ID: 10, // 资产营销平台 MANAGEMENT_ID: 3, // 管理中心
WORK_BENCH_ID: -99, // 工作 PROPERTY_ID: 4, // 资产营销平
WORK_BENCH_ID: 5, // 工作台
}; };

View File

@ -18,7 +18,7 @@ export const router = createRouter({
{ {
path: '/login', path: '/login',
name: 'UserLogin', name: 'UserLogin',
component: () => import('@/views/components/login'), component: () => import('@/views/components/login/index.vue'),
meta: { meta: {
requiresAuth: false, requiresAuth: false,
requireLogin: false, requireLogin: false,
@ -27,7 +27,7 @@ export const router = createRouter({
{ {
path: '/', path: '/',
name: 'Home', name: 'Home',
component: () => import('@/views/components/workplace'), component: () => import('@/views/components/workplace/index.vue'),
meta: { meta: {
hideSidebar: true, hideSidebar: true,
requiresAuth: false, requiresAuth: false,

View File

@ -0,0 +1,46 @@
import type { AppRouteRecordRaw } from '../types';
import { MENU_GROUP_IDS } from '@/router/constants';
import IconRepository from '@/assets/svg/svg-repository.svg';
const COMPONENTS: AppRouteRecordRaw[] = [
{
path: '/manuscript',
name: 'Manuscript',
redirect: 'manuscript/list',
meta: {
locale: '内容稿件',
icon: IconRepository,
requiresAuth: false,
requireLogin: true,
roles: ['*'],
id: MENU_GROUP_IDS.CREATIVE_GENERATION_WORKSHOP_ID,
},
children: [
{
path: 'list',
name: 'ManuscriptList',
meta: {
locale: '内容稿件列表',
requiresAuth: false,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/creative-generation-workshop/manuscript/manuscript-list/index.vue'),
},
{
path: 'check',
name: 'ManuscriptCheck',
meta: {
locale: '内容稿件审核',
requiresAuth: false,
requireLogin: true,
roles: ['*'],
},
component: () => import('@/views/creative-generation-workshop/manuscript/manuscript-check/index.vue'),
},
],
},
];
export default COMPONENTS;

View File

@ -23,7 +23,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
{ {
path: 'person', path: 'person',
name: 'ManagementPerson', name: 'ManagementPerson',
component: () => import('@/views/components/management/person'), component: () => import('@/views/components/management/person/index.vue'),
meta: { meta: {
locale: '个人信息', locale: '个人信息',
requiresAuth: false, requiresAuth: false,
@ -34,7 +34,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
{ {
path: 'enterprise', path: 'enterprise',
name: 'ManagementEnterprise', name: 'ManagementEnterprise',
component: () => import('@/views/components/management/enterprise'), component: () => import('@/views/components/management/enterprise/index.vue'),
meta: { meta: {
locale: '企业信息', locale: '企业信息',
requiresAuth: false, requiresAuth: false,
@ -45,7 +45,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
{ {
path: 'account', path: 'account',
name: 'ManagementAccount', name: 'ManagementAccount',
component: () => import('@/views/components/management/account'), component: () => import('@/views/components/management/account/index.vue'),
meta: { meta: {
locale: '账号管理', locale: '账号管理',
requiresAuth: false, requiresAuth: false,

View File

@ -60,7 +60,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/media-account/account-manage'), component: () => import('@/views/property-marketing/media-account/account-manage/index.vue'),
}, },
{ {
path: 'dashboard', path: 'dashboard',
@ -71,7 +71,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/media-account/account-dashboard'), component: () => import('@/views/property-marketing/media-account/account-dashboard/index.vue'),
}, },
{ {
path: 'detail/:id', path: 'detail/:id',
@ -84,7 +84,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
hideInMenu: true, hideInMenu: true,
activeMenu: 'MediaAccountAccountDashboard', activeMenu: 'MediaAccountAccountDashboard',
}, },
component: () => import('@/views/property-marketing/media-account/account-detail'), component: () => import('@/views/property-marketing/media-account/account-detail/index.vue'),
}, },
], ],
}, },
@ -110,7 +110,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/put-account/account-manage'), component: () => import('@/views/property-marketing/put-account/account-manage/index.vue'),
}, },
{ {
path: 'data', path: 'data',
@ -121,7 +121,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/put-account/account-data'), component: () => import('@/views/property-marketing/put-account/account-data/index.vue'),
}, },
{ {
path: 'account-dashboard', path: 'account-dashboard',
@ -132,7 +132,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/put-account/account-dashboard'), component: () => import('@/views/property-marketing/put-account/account-dashboard/index.vue'),
}, },
{ {
path: 'investmentGuidelines', path: 'investmentGuidelines',
@ -143,7 +143,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/put-account/investment-guidelines'), component: () => import('@/views/property-marketing/put-account/investment-guidelines/index.vue'),
}, },
{ {
path: 'detail/:id', path: 'detail/:id',
@ -155,7 +155,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
roles: ['*'], roles: ['*'],
activeMenu: 'PutAccountInvestmentGuidelines', activeMenu: 'PutAccountInvestmentGuidelines',
}, },
component: () => import('@/views/property-marketing/put-account/investment-guidelines/detail'), component: () => import('@/views/property-marketing/put-account/investment-guidelines/detail.vue'),
}, },
], ],
}, },
@ -181,7 +181,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
// requireLogin: true, // requireLogin: true,
// roles: ['*'], // roles: ['*'],
// }, // },
// component: () => import('@/views/property-marketing/intelligent-solution/businessAnalysisReport'), // component: () => import('@/views/property-marketing/intelligent-solution/businessAnalysisReport.vue'),
// }, // },
// { // {
// path: 'competitiveProductAnalysisReport', // path: 'competitiveProductAnalysisReport',
@ -192,7 +192,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
// requireLogin: true, // requireLogin: true,
// roles: ['*'], // roles: ['*'],
// }, // },
// component: () => import('@/views/property-marketing/intelligent-solution/competitiveProductAnalysisReport'), // component: () => import('@/views/property-marketing/intelligent-solution/competitiveProductAnalysisReport.vue'),
// }, // },
// ], // ],
// }, // },
@ -218,7 +218,7 @@ const COMPONENTS: AppRouteRecordRaw[] = [
requireLogin: true, requireLogin: true,
roles: ['*'], roles: ['*'],
}, },
component: () => import('@/views/property-marketing/project-manage/project-list'), component: () => import('@/views/property-marketing/project-manage/project-list/index.vue'),
}, },
], ],
}, },

View File

@ -46,6 +46,19 @@ export const MENU_LIST = [
}, },
], ],
}, },
{
id: MENU_GROUP_IDS.CREATIVE_GENERATION_WORKSHOP_ID,
name: '创意生成工坊',
permissionKey: '',
requiresAuth: true,
children: [
{
name: '内容稿件',
routeName: 'ManuscriptList',
includeRouteNames: ['ManuscriptList', 'ManuscriptCheck'],
}
]
},
{ {
id: MENU_GROUP_IDS.PROPERTY_ID, id: MENU_GROUP_IDS.PROPERTY_ID,
name: '营销资产中台', name: '营销资产中台',

View File

@ -0,0 +1,70 @@
<!-- eslint-disable vue/no-mutating-props -->
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 14:02:40
-->
<template>
<div class="filter-wrap px-24px pt-12px pb-24px">
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">内容稿件标题</span>
<a-space size="medium">
<a-input
v-model="query.name"
class="w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</div>
<div class="filter-row-item flex items-center">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
</template>
<template #default>重置</template>
</a-button>
</div>
</div>
</div>
</template>
<script setup>
import { defineEmits, defineProps } from 'vue';
const props = defineProps({
query: {
type: Object,
required: true,
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const handleSearch = () => {
emits('update:query', props.query);
nextTick(() => {
emits('search');
});
};
const handleReset = () => {
emits('reset');
};
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,23 @@
.filter-wrap {
.filter-row {
.filter-row-item {
&:not(:last-child) {
margin-right: 24px;
}
.label {
margin-right: 8px;
color: #211f24;
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
flex-shrink: 0;
line-height: 22px; /* 157.143% */
}
:deep(.arco-space-item) {
width: 100%;
}
}
}
}

View File

@ -0,0 +1,60 @@
export const TABLE_COLUMNS = [
{
title: '序号',
dataIndex: 'index',
width: 120,
fixed: 'left',
},
{
title: '图片/视频',
dataIndex: 'picker',
width: 240,
},
{
title: '内容稿件标题',
dataIndex: 'name',
width: 240,
},
{
title: '所属项目',
dataIndex: 'name',
width: 240,
},
{
title: '稿件类型',
dataIndex: 'budget',
width: 180,
},
{
title: '上传时间',
dataIndex: 'create_at',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '上传人员',
dataIndex: 'placement_account_count',
width: 180,
},
{
title: '最后修改时间',
dataIndex: 'last_create_at',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '最后修改人员',
dataIndex: 'placement_account_count1',
width: 180,
},
{
title: '操作',
dataIndex: 'operation',
width: 160,
fixed: 'right'
},
];

View File

@ -0,0 +1,58 @@
<template>
<a-modal
v-model:visible="visible"
title="删除稿件"
width="480px"
@close="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个稿件吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="large" @click="onDelete"
>确认删除</a-button
>
</template>
</a-modal>
</template>
<script setup>
import { ref } from 'vue';
import { deleteProject } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const update = inject('update');
const visible = ref(false);
const projectId = ref(null);
const projectName = ref('');
const isBatch = computed(() => Array.isArray(projectId.value));
function onClose() {
visible.value = false;
projectId.value = null;
projectName.value = '';
}
const open = (record) => {
const { id = null, name = '' } = record;
projectId.value = id;
projectName.value = name;
visible.value = true;
};
async function onDelete() {
const { code } = await deleteProject(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
update()
onClose();
}
}
defineExpose({ open });
</script>

View File

@ -0,0 +1,91 @@
<template>
<a-table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:pagination="false"
:scroll="{ x: '100%' }"
class="flex-1 manuscript-table w-100%"
bordered
@sorter-change="handleSorterChange"
>
<template #empty>
<NoData text="暂无项目"/>
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<template v-if="column.dataIndex === 'create_at'" #cell="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" @click="onEdit(record)">分享</a-button>
<a-button type="outline" size="mini" @click="onEdit(record)">审核</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
</template>
<script setup>
import { ref } from 'vue';
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import icon1 from '@/assets/img/media-account/icon-delete.png';
const emits = defineEmits(['edit', 'sorterChange', 'delete']);
const props = defineProps({
dataSource: {
type: Array,
default: () => [],
},
});
const tableRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
};
const onDelete = (item) => {
emits('delete', item);
};
const onEdit = (item) => {
emits('edit', item);
};
const onDetail = (item) => {
console.log('onDetail')
};
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,10 @@
.manuscript-table {
.cts {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
}

View File

@ -0,0 +1,5 @@
export const INITIAL_QUERY = {
name: '',
sort_column: undefined,
sort_order: undefined,
};

View File

@ -0,0 +1,115 @@
<template>
<div class="manuscript-check-wrap">
<div class="filter-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid mb-16px">
<div class="top flex h-64px px-24px py-10px justify-between items-center">
<p class="text-18px font-400 lh-26px color-#211F24 title">内容审核列表</p>
<div class="flex items-center">
<a-button type="primary" size="medium" @click="handleOpenAddProjectModal">
<template #icon>
<icon-plus size="16"/>
</template>
<template #default>上传内容稿件</template>
</a-button>
</div>
</div>
<FilterBlock v-model:query="query" @search="handleSearch" @reset="handleReset" />
</div>
<div
class="table-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid px-24px py-24px flex-1 flex flex-col"
>
<ManuscriptCheckTable
:dataSource="dataSource"
@sorterChange="handleSorterChange"
@delete="handleDelete"
@edit="handleEdit"
/>
<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>
<DeleteManuscriptModal ref="deleteManuscriptModalRef" />
</div>
</template>
<script lang="jsx" setup>
import { defineComponent } from 'vue';
import { Button } from '@arco-design/web-vue';
import FilterBlock from './components/filter-block';
import ManuscriptCheckTable from './components/manuscript-check-table';
import DeleteManuscriptModal from './components/manuscript-check-table/delete-manuscript-modal.vue';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getProjects } from '@/api/all/propertyMarketing';
import { INITIAL_QUERY } from './constants';
const { dataSource, pageInfo, onPageChange, onPageSizeChange, resetPageInfo } = useTableSelectionWithPagination({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const query = ref(cloneDeep(INITIAL_QUERY));
const addManuscriptModalRef = ref(null);
const deleteManuscriptModalRef = ref(null);
const getData = async () => {
const { page, page_size } = pageInfo.value;
const { code, data } = await getProjects({
...query.value,
page,
page_size,
});
if (code === 200) {
dataSource.value = data?.data ?? [];
pageInfo.value.total = data.total;
}
};
const handleSearch = () => {
reload();
};
const reload = () => {
pageInfo.value.page = 1;
getData();
};
const handleReset = () => {
resetPageInfo();
query.value = cloneDeep(INITIAL_QUERY);
reload();
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order;
reload();
};
const handleOpenAddProjectModal = () => {
console.log('handleOpenAddProjectModal');
};
const handleDelete = (item) => {
const { id, name } = item;
deleteManuscriptModalRef.value?.open({ id, name: `${name}` });
};
const handleEdit = (item) => {
// addManuscriptModalRef.value?.open(item.id);
};
onMounted(() => {
getData();
});
provide('update', getData);
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,29 @@
.manuscript-check-wrap {
height: 100%;
display: flex;
flex-direction: column;
.filter-wrap {
.top {
.title {
font-family: $font-family-medium;
font-style: normal;
}
:deep(.arco-btn) {
.arco-btn-icon {
line-height: 16px;
}
}
}
}
.table-wrap {
display: flex;
flex-direction: column;
.pagination-box {
display: flex;
width: 100%;
padding: 16px 24px;
justify-content: flex-end;
align-items: center;
}
}
}

View File

@ -0,0 +1,70 @@
<!-- eslint-disable vue/no-mutating-props -->
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 14:02:40
-->
<template>
<div class="filter-wrap px-24px pt-12px pb-24px">
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">内容稿件标题</span>
<a-space size="medium">
<a-input
v-model="query.name"
class="w-240px"
placeholder="请输入内容稿件标题"
size="medium"
allow-clear
@change="handleSearch"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</div>
<div class="filter-row-item flex items-center">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
</template>
<template #default>搜索</template>
</a-button>
<a-button size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
</template>
<template #default>重置</template>
</a-button>
</div>
</div>
</div>
</template>
<script setup>
import { defineEmits, defineProps } from 'vue';
const props = defineProps({
query: {
type: Object,
required: true,
},
});
const emits = defineEmits('search', 'reset', 'update:query');
const handleSearch = () => {
emits('update:query', props.query);
nextTick(() => {
emits('search');
});
};
const handleReset = () => {
emits('reset');
};
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,23 @@
.filter-wrap {
.filter-row {
.filter-row-item {
&:not(:last-child) {
margin-right: 24px;
}
.label {
margin-right: 8px;
color: #211f24;
font-family: $font-family-regular;
font-size: 14px;
font-style: normal;
font-weight: 400;
flex-shrink: 0;
line-height: 22px; /* 157.143% */
}
:deep(.arco-space-item) {
width: 100%;
}
}
}
}

View File

@ -0,0 +1,65 @@
export const TABLE_COLUMNS = [
{
title: '序号',
dataIndex: 'index',
width: 120,
fixed: 'left',
},
{
title: '图片/视频',
dataIndex: 'picker',
width: 240,
},
{
title: '内容稿件标题',
dataIndex: 'name',
width: 240,
},
{
title: '所属项目',
dataIndex: 'name',
width: 240,
},
{
title: '稿件类型',
dataIndex: 'budget',
width: 180,
},
{
title: '审核状态',
dataIndex: 'media_account_count',
width: 180,
},
{
title: '上传时间',
dataIndex: 'create_at',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '上传人员',
dataIndex: 'placement_account_count',
width: 180,
},
{
title: '最后修改时间',
dataIndex: 'last_create_at',
width: 180,
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '最后修改人员',
dataIndex: 'placement_account_count1',
width: 180,
},
{
title: '操作',
dataIndex: 'operation',
width: 160,
fixed: 'right'
},
];

View File

@ -0,0 +1,58 @@
<template>
<a-modal
v-model:visible="visible"
title="删除稿件"
width="480px"
@close="onClose"
>
<div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个稿件吗</span>
</div>
<template #footer>
<a-button size="large" @click="onClose">取消</a-button>
<a-button type="primary" class="ml-16px" status="danger" size="large" @click="onDelete"
>确认删除</a-button
>
</template>
</a-modal>
</template>
<script setup>
import { ref } from 'vue';
import { deleteProject } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const update = inject('update');
const visible = ref(false);
const projectId = ref(null);
const projectName = ref('');
const isBatch = computed(() => Array.isArray(projectId.value));
function onClose() {
visible.value = false;
projectId.value = null;
projectName.value = '';
}
const open = (record) => {
const { id = null, name = '' } = record;
projectId.value = id;
projectName.value = name;
visible.value = true;
};
async function onDelete() {
const { code } = await deleteProject(projectId.value);
if (code === 200) {
AMessage.success('删除成功');
update()
onClose();
}
}
defineExpose({ open });
</script>

View File

@ -0,0 +1,91 @@
<template>
<a-table
ref="tableRef"
:data="dataSource"
row-key="id"
column-resizable
:pagination="false"
:scroll="{ x: '100%' }"
class="flex-1 manuscript-table w-100%"
bordered
@sorter-change="handleSorterChange"
>
<template #empty>
<NoData text="暂无项目"/>
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
:width="column.width"
:min-width="column.minWidth"
:sortable="column.sortable"
:align="column.align"
ellipsis
tooltip
>
<template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip>
</div>
</template>
<template v-if="column.dataIndex === 'create_at'" #cell="{ record }">
{{ exactFormatTime(record.create_at) }}
</template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }">
<div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" @click="onEdit(record)">编辑</a-button>
<a-button type="outline" size="mini" @click="onDetail(record)">详情</a-button>
</div>
</template>
<template v-else #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
</template>
<script setup>
import { ref } from 'vue';
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import icon1 from '@/assets/img/media-account/icon-delete.png';
const emits = defineEmits(['edit', 'sorterChange', 'delete']);
const props = defineProps({
dataSource: {
type: Array,
default: () => [],
},
});
const tableRef = ref(null);
const handleSorterChange = (column, order) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc');
};
const onDelete = (item) => {
emits('delete', item);
};
const onEdit = (item) => {
emits('edit', item);
};
const onDetail = (item) => {
console.log('onDetail')
};
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,10 @@
.manuscript-table {
.cts {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
}

View File

@ -0,0 +1,5 @@
export const INITIAL_QUERY = {
name: '',
sort_column: undefined,
sort_order: undefined,
};

View File

@ -0,0 +1,115 @@
<template>
<div class="manuscript-list-wrap">
<div class="filter-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid mb-16px">
<div class="top flex h-64px px-24px py-10px justify-between items-center">
<p class="text-18px font-400 lh-26px color-#211F24 title">内容稿件列表</p>
<div class="flex items-center">
<a-button type="primary" size="medium" @click="handleOpenAddProjectModal">
<template #icon>
<icon-plus size="16"/>
</template>
<template #default>上传内容稿件</template>
</a-button>
</div>
</div>
<FilterBlock v-model:query="query" @search="handleSearch" @reset="handleReset" />
</div>
<div
class="table-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid px-24px py-24px flex-1 flex flex-col"
>
<ManuscriptTable
:dataSource="dataSource"
@sorterChange="handleSorterChange"
@delete="handleDelete"
@edit="handleEdit"
/>
<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>
<DeleteManuscriptModal ref="deleteManuscriptModalRef" />
</div>
</template>
<script lang="jsx" setup>
import { defineComponent } from 'vue';
import { Button } from '@arco-design/web-vue';
import FilterBlock from './components/filter-block';
import ManuscriptTable from './components/manuscript-table';
import DeleteManuscriptModal from './components/manuscript-table/delete-manuscript-modal.vue';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getProjects } from '@/api/all/propertyMarketing';
import { INITIAL_QUERY } from './constants';
const { dataSource, pageInfo, onPageChange, onPageSizeChange, resetPageInfo } = useTableSelectionWithPagination({
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const query = ref(cloneDeep(INITIAL_QUERY));
const addManuscriptModalRef = ref(null);
const deleteManuscriptModalRef = ref(null);
const getData = async () => {
const { page, page_size } = pageInfo.value;
const { code, data } = await getProjects({
...query.value,
page,
page_size,
});
if (code === 200) {
dataSource.value = data?.data ?? [];
pageInfo.value.total = data.total;
}
};
const handleSearch = () => {
reload();
};
const reload = () => {
pageInfo.value.page = 1;
getData();
};
const handleReset = () => {
resetPageInfo();
query.value = cloneDeep(INITIAL_QUERY);
reload();
};
const handleSorterChange = (column, order) => {
query.value.sort_column = column;
query.value.sort_order = order;
reload();
};
const handleOpenAddProjectModal = () => {
console.log('handleOpenAddProjectModal');
};
const handleDelete = (item) => {
const { id, name } = item;
deleteManuscriptModalRef.value?.open({ id, name: `${name}` });
};
const handleEdit = (item) => {
// addManuscriptModalRef.value?.open(item.id);
};
onMounted(() => {
getData();
});
provide('update', getData);
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,29 @@
.manuscript-list-wrap {
height: 100%;
display: flex;
flex-direction: column;
.filter-wrap {
.top {
.title {
font-family: $font-family-medium;
font-style: normal;
}
:deep(.arco-btn) {
.arco-btn-icon {
line-height: 16px;
}
}
}
}
.table-wrap {
display: flex;
flex-direction: column;
.pagination-box {
display: flex;
width: 100%;
padding: 16px 24px;
justify-content: flex-end;
align-items: center;
}
}
}

View File

@ -25,7 +25,7 @@
</div> </div>
<a-button type="primary" size="medium" @click="openAdd" <a-button type="primary" size="medium" @click="openAdd"
><template #icon> ><template #icon>
<img :src="icon3" width="16" height="16" /> <icon-plus size="16" />
</template> </template>
<template #default>添加新分组</template> <template #default>添加新分组</template>
</a-button> </a-button>
@ -47,7 +47,7 @@
<span class="s1 mb-16px">暂无分组</span> <span class="s1 mb-16px">暂无分组</span>
<a-button type="primary" class="mb-16px" size="medium" @click="openAdd" <a-button type="primary" class="mb-16px" size="medium" @click="openAdd"
><template #icon> ><template #icon>
<img :src="icon3" width="16" height="16" /> <icon-plus size="16"/>
</template> </template>
<template #default>去添加</template> <template #default>去添加</template>
</a-button> </a-button>
@ -89,7 +89,6 @@ import DeleteGroup from './delete-group.vue';
import icon1 from '@/assets/img/media-account/icon-delete.png'; import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/media-account/icon-empty.png'; import icon2 from '@/assets/img/media-account/icon-empty.png';
import icon3 from '@/assets/img/media-account/icon-add.png';
const emit = defineEmits(['update']); const emit = defineEmits(['update']);

View File

@ -25,7 +25,7 @@
</div> </div>
<a-button type="primary" size="medium" @click="openAdd"> <a-button type="primary" size="medium" @click="openAdd">
<template #icon> <template #icon>
<img :src="iconAdd" width="16" height="16" /> <icon-plus size="16" />
</template> </template>
<template #default>添加新标签</template> <template #default>添加新标签</template>
</a-button> </a-button>
@ -45,7 +45,7 @@
<span class="s1 mb-16px">暂无标签</span> <span class="s1 mb-16px">暂无标签</span>
<a-button type="primary" class="mb-16px" size="medium" @click="openAdd"> <a-button type="primary" class="mb-16px" size="medium" @click="openAdd">
<template #icon> <template #icon>
<img :src="iconAdd" width="16" height="16" class="relative top-2px" /> <icon-plus size="16" />
</template> </template>
<template #default>去添加</template> <template #default>去添加</template>
</a-button> </a-button>
@ -65,7 +65,6 @@ import { getTagsList } from '@/api/all/propertyMarketing';
import AddTag from './add-tag.vue'; import AddTag from './add-tag.vue';
import DeleteTag from './delete-tag.vue'; import DeleteTag from './delete-tag.vue';
import iconAdd from '@/assets/img/media-account/icon-add.png';
import iconDelete from '@/assets/img/media-account/icon-delete-1.png'; import iconDelete from '@/assets/img/media-account/icon-delete-1.png';
const emit = defineEmits(['update']); const emit = defineEmits(['update']);

View File

@ -22,7 +22,7 @@
</a-button> </a-button>
<a-button type="primary" class="w-112px" size="medium" @click="handleOpenAccountModal"> <a-button type="primary" class="w-112px" size="medium" @click="handleOpenAccountModal">
<template #icon> <template #icon>
<img :src="icon1" width="16" height="16" /> <icon-plus size="16"/>
</template> </template>
<template #default>添加账号</template> <template #default>添加账号</template>
</a-button> </a-button>
@ -142,7 +142,6 @@ import {
getMediaAccountSyncStatus, getMediaAccountSyncStatus,
} from '@/api/all/propertyMarketing'; } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-add.png';
import icon2 from '@/assets/img/media-account/icon-group.png'; import icon2 from '@/assets/img/media-account/icon-group.png';
import icon3 from '@/assets/img/media-account/icon-tag.png'; import icon3 from '@/assets/img/media-account/icon-tag.png';
import icon4 from '@/assets/img/media-account/icon-success.png'; import icon4 from '@/assets/img/media-account/icon-success.png';

View File

@ -13,7 +13,7 @@ export const TABLE_COLUMNS = [
title: '项目预算', title: '项目预算',
dataIndex: 'budget', dataIndex: 'budget',
width: 180, width: 180,
prefix: "¥" prefix: '¥',
}, },
{ {
title: '关联平台账号', title: '关联平台账号',
@ -42,5 +42,6 @@ export const TABLE_COLUMNS = [
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
width: 100, width: 100,
fixed: 'right',
}, },
]; ];

View File

@ -6,7 +6,7 @@
<div class="flex items-center"> <div class="flex items-center">
<a-button type="primary" class="w-112px search-btn" size="medium" @click="handleOpenAddProjectModal"> <a-button type="primary" class="w-112px search-btn" size="medium" @click="handleOpenAddProjectModal">
<template #icon> <template #icon>
<img :src="icon1" width="16" height="16" /> <icon-plus size="16"/>
</template> </template>
<template #default>添加项目</template> <template #default>添加项目</template>
</a-button> </a-button>
@ -53,8 +53,6 @@ import ProjectTable from './components/project-table';
import AddProjectModal from './components/add-project-modal'; import AddProjectModal from './components/add-project-modal';
import DeleteProjectModal from './components/project-table/delete-project-modal.vue'; import DeleteProjectModal from './components/project-table/delete-project-modal.vue';
import icon1 from '@/assets/img/media-account/icon-add.png';
const { dataSource, pageInfo, onPageChange, onPageSizeChange, resetPageInfo } = useTableSelectionWithPagination({ const { dataSource, pageInfo, onPageChange, onPageSizeChange, resetPageInfo } = useTableSelectionWithPagination({
onPageChange: () => { onPageChange: () => {
getData(); getData();

View File

@ -25,7 +25,7 @@
</div> </div>
<a-button type="primary" size="medium" @click="openAdd" <a-button type="primary" size="medium" @click="openAdd"
><template #icon> ><template #icon>
<img :src="icon3" width="16" height="16" /> <icon-plus size="16" />
</template> </template>
<template #default>添加新分组</template> <template #default>添加新分组</template>
</a-button> </a-button>
@ -46,7 +46,7 @@
<span class="s1 mb-16px">暂无分组</span> <span class="s1 mb-16px">暂无分组</span>
<a-button type="primary" class="mb-16px" size="medium" @click="openAdd" <a-button type="primary" class="mb-16px" size="medium" @click="openAdd"
><template #icon> ><template #icon>
<img :src="icon3" width="16" height="16" class="relative top-3px" /> <icon-plus size="16" />
</template> </template>
<template #default>去添加</template> <template #default>去添加</template>
</a-button> </a-button>
@ -88,7 +88,6 @@ import DeleteGroup from './delete-group.vue';
import icon1 from '@/assets/img/media-account/icon-delete.png'; import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/media-account/icon-empty.png'; import icon2 from '@/assets/img/media-account/icon-empty.png';
import icon3 from '@/assets/img/media-account/icon-add.png';
const emit = defineEmits(['update']); const emit = defineEmits(['update']);
const visible = ref(false); const visible = ref(false);

View File

@ -10,7 +10,7 @@
<div class="flex items-center"> <div class="flex items-center">
<a-button type="primary" class="w-112px" size="medium" @click="handleOpenAccountModal"> <a-button type="primary" class="w-112px" size="medium" @click="handleOpenAccountModal">
<template #icon> <template #icon>
<img :src="icon1" width="16" height="16" /> <icon-plus size="16"/>
</template> </template>
<template #default>添加账户</template> <template #default>添加账户</template>
</a-button> </a-button>
@ -104,7 +104,6 @@ import { getPlacementAccounts, getPlacementAccountsHealth } from '@/api/all/prop
import { getTaskStatus } from '@/api/all/common'; import { getTaskStatus } from '@/api/all/common';
import { showImportResultNotification } from '@/utils/arcoD'; import { showImportResultNotification } from '@/utils/arcoD';
import icon1 from '@/assets/img/media-account/icon-add.png';
import icon4 from '@/assets/img/media-account/icon-success.png'; import icon4 from '@/assets/img/media-account/icon-success.png';
import icon5 from '@/assets/img/media-account/icon-warn.png'; import icon5 from '@/assets/img/media-account/icon-warn.png';
import icon6 from '@/assets/img/media-account/icon-close.png'; import icon6 from '@/assets/img/media-account/icon-close.png';