Merge pull request 'feature/0715_投流子账号管理_rxd' (#8) from feature/0715_投流子账号管理_rxd into main

Reviewed-on: ai-team/lingji-work-fe#8
This commit is contained in:
2025-07-16 07:21:51 +00:00
27 changed files with 531 additions and 130 deletions

View File

@ -21,7 +21,7 @@ export function configAutoImport() {
'@vueuse/core', '@vueuse/core',
{ {
dayjs: [['default', 'dayjs']], dayjs: [['default', 'dayjs']],
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty'], 'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty', 'merge'],
'@/hooks': ['useModal'], '@/hooks': ['useModal'],
}, },
], ],

View File

@ -294,7 +294,7 @@ export const getPlacementAccountProjectsTrend = (params = {}) => {
export const getPlacementGuide = (params: {}) => { export const getPlacementGuide = (params: {}) => {
return Http.get(`/v1/placement-account-projects/getGuideList`, params); return Http.get(`/v1/placement-account-projects/getGuideList`, params);
}; };
//查询投放指南历史 // 查询投放指南历史
export const getPlacementGuideHistory = (params: {}) => { export const getPlacementGuideHistory = (params: {}) => {
return Http.get(`/v1/placement-account-projects/getGuideListHistory`, params); return Http.get(`/v1/placement-account-projects/getGuideListHistory`, params);
}; };
@ -312,7 +312,7 @@ export const getPlacementGuideDetail = (id: string) => {
return Http.get(`/v1/placement-account-projects/historylog/${id}`); return Http.get(`/v1/placement-account-projects/historylog/${id}`);
}; };
//删除记录 // 删除记录
export const deleteHistorylog = (id: string) => { export const deleteHistorylog = (id: string) => {
return Http.delete(`/v1/placement-account-projects/historylog/${id}`); return Http.delete(`/v1/placement-account-projects/historylog/${id}`);
}; };
@ -332,4 +332,12 @@ export const postPlacementAccountsSync = (id: string) => {
return Http.post(`/v1/placement-accounts/${id}/sync-data`); return Http.post(`/v1/placement-accounts/${id}/sync-data`);
}; };
// 投放账号-子账号分页
export const postSubAccount = (params = {}) => {
return Http.post('/v1/placement-accounts/get-subaccount', params);
};
// 投放账号-添加子账号
export const postAddSubAccount = (params = {}) => {
return Http.post('/v1/placement-accounts/subaccount', params);
};

View File

@ -0,0 +1,87 @@
import { ref, computed } from 'vue';
interface UseTableSelectionWithPaginationOptions {
rowKey?: string; // 主键字段名,默认 'id'
pageInfo?: {
page?: number;
page_size?: number;
total?: number;
};
onPageChange?: (page: number) => void;
onPageSizeChange?: (size: number) => void;
}
const DEFAULT_PAGE_INFO = {
page: 1,
page_size: 20,
total: 0,
};
export function useTableSelectionWithPagination(options: UseTableSelectionWithPaginationOptions = {}) {
const rowKey = options.rowKey || 'id';
const selectedRowKeys = ref<Array<string | number>>([]);
const selectedRows = ref<any[]>([]);
const pageInfo = ref(merge({}, DEFAULT_PAGE_INFO, options.pageInfo));
const dataSource = ref<any[]>([]);
// 单行选择
const handleSelect = (selectedKeys: (string | number)[], rowKeyValue: string | number, record: any) => {
const select = selectedKeys.includes(rowKeyValue);
selectedRowKeys.value = selectedKeys;
if (select) {
if (!selectedRows.value.some((v) => v[rowKey] === record[rowKey])) {
selectedRows.value.push(record);
}
} else {
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== record[rowKey]);
}
};
// 全选/取消全选
const handleSelectAll = (checked: boolean) => {
const currentPageRows = dataSource.value;
const currentPageKeys = currentPageRows.map((v) => v[rowKey]);
if (checked) {
selectedRowKeys.value = Array.from(new Set([...selectedRowKeys.value, ...currentPageKeys]));
const allRows = [...selectedRows.value, ...currentPageRows];
const map = new Map();
allRows.forEach((row) => map.set(row[rowKey], row));
selectedRows.value = Array.from(map.values());
} else {
selectedRowKeys.value = selectedRowKeys.value.filter((key) => !currentPageKeys.includes(key));
selectedRows.value = selectedRows.value.filter((row) => !currentPageKeys.includes(row[rowKey]));
}
};
const onPageChange = (page: number) => {
pageInfo.value.page = page;
options.onPageChange?.(page);
};
const onPageSizeChange = (size: number) => {
pageInfo.value.page_size = size;
pageInfo.value.page = 1;
options.onPageSizeChange?.(size);
};
const rowSelection = computed(() => ({
type: 'checkbox',
showCheckedAll: true,
}));
return {
selectedRowKeys,
selectedRows,
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
};
}

View File

@ -0,0 +1,15 @@
.arco-picker {
border-radius: 4px !important;
border-color: #d7d7d9 !important;
background-color: #fff !important;
&:hover {
background-color: #fff !important;
}
&:focus-within,
&.arco-input-focus,
&.arco-textarea-focus {
background-color: var(--color-bg-2) !important;
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
}

View File

@ -4,3 +4,6 @@
@import './pagination.scss'; @import './pagination.scss';
@import './tabs.scss'; @import './tabs.scss';
@import './modal.scss'; @import './modal.scss';
@import "./textarea.scss";
@import "./select.scss";
@import "./date-picker.scss"

View File

@ -1,5 +1,15 @@
.arco-input-wrapper { .arco-input-wrapper {
border-radius: 4px; border-radius: 4px !important;
border-color: #d7d7d9; border-color: #d7d7d9 !important;
background-color: #fff; background-color: #fff !important;
&:hover {
background-color: #fff !important;
}
&:focus-within,
&.arco-input-focus,
&.arco-textarea-focus {
background-color: var(--color-bg-2) !important;
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
} }

View File

@ -4,6 +4,11 @@
height: 56px; height: 56px;
padding: 0 20px; padding: 0 20px;
.arco-modal-title { .arco-modal-title {
font-family: $font-family-medium;
color: #211f24;
font-size: 16px;
font-style: normal;
font-weight: 400;
justify-content: flex-start; justify-content: flex-start;
} }
} }

View File

@ -0,0 +1,15 @@
.arco-select {
border-radius: 4px !important;
border-color: #d7d7d9 !important;
background-color: #fff !important;
&:hover {
background-color: #fff !important;
}
&:focus-within,
&.arco-input-focus,
&.arco-textarea-focus {
background-color: var(--color-bg-2) !important;
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
}

View File

@ -0,0 +1,15 @@
.arco-textarea-wrapper {
border-radius: 4px !important;
border-color: #d7d7d9 !important;
background-color: #fff !important;
&:hover {
background-color: #fff !important;
}
&:focus-within,
&.arco-input-focus,
&.arco-textarea-focus {
background-color: var(--color-bg-2) !important;
border-color: rgb(var(--primary-6)) !important;
box-shadow: 0 0 0 0 var(--color-primary-light-2) !important;
}
}

View File

@ -1,19 +1,3 @@
.arco-input-wrapper,
.arco-select-view-single,
.arco-textarea-wrapper,
.arco-picker,
.arco-select-view-multiple {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus,
&.arco-textarea-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.arco-modal { .arco-modal {
.cancel-btn { .cancel-btn {
border-radius: 4px; border-radius: 4px;

View File

@ -1,17 +1,4 @@
.container { .container {
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-select-view-multiple) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.filter-row { .filter-row {
.filter-row-item { .filter-row-item {
&:not(:last-child) { &:not(:last-child) {

View File

@ -1,18 +1,4 @@
.note-table-wrap { .note-table-wrap {
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-select-view-multiple),
:deep(.arco-picker) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.filter-row { .filter-row {
.filter-row-item { .filter-row-item {
&:not(:last-child) { &:not(:last-child) {

View File

@ -1,17 +1,4 @@
.container { .container {
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-select-view-multiple) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.filter-row { .filter-row {
.filter-row-item { .filter-row-item {
&:not(:last-child) { &:not(:last-child) {

View File

@ -9,6 +9,7 @@
size="medium" size="medium"
:placeholder="placeholder" :placeholder="placeholder"
allow-clear allow-clear
:max-tag-count="3"
@change="handleChange" @change="handleChange"
> >
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name"> <a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">

View File

@ -9,6 +9,7 @@
size="medium" size="medium"
:placeholder="placeholder" :placeholder="placeholder"
allow-clear allow-clear
:max-tag-count="3"
@change="handleChange" @change="handleChange"
> >
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name"> <a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">

View File

@ -1,18 +1,4 @@
.container { .container {
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-select-view-multiple),
:deep(.arco-picker) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.filter-row { .filter-row {
.filter-row-item { .filter-row-item {
&:not(:last-child) { &:not(:last-child) {

View File

@ -9,6 +9,7 @@
size="medium" size="medium"
:placeholder="placeholder" :placeholder="placeholder"
allow-clear allow-clear
:max-tag-count="3"
@change="handleChange" @change="handleChange"
> >
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name"> <a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">

View File

@ -63,8 +63,8 @@
</div> </div>
</div> </div>
</div> </div>
<PauseAccountPatchModal ref="pauseAccountPatchModalRef" @update="emits('update')" /> <PauseAccountPatchModal ref="pauseAccountPatchModalRef" />
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" /> <AuthorizedAccountModal ref="authorizedAccountModalRef" />
</div> </div>
</template> </template>
@ -92,7 +92,7 @@ const props = defineProps({
}, },
}); });
const emits = defineEmits(['openEdit', 'update', 'selectionChange', 'delete']); const emits = defineEmits(['openEdit', 'selectionChange', 'delete']);
const pauseAccountPatchModalRef = ref(null); const pauseAccountPatchModalRef = ref(null);
const authorizedAccountModalRef = ref(null); const authorizedAccountModalRef = ref(null);
@ -121,7 +121,7 @@ const openDelete = (item) => {
}; };
const handleReauthorize = (item) => { const handleReauthorize = (item) => {
authorizedAccountModalRef.value?.open(item.id, item.last_synced_at); authorizedAccountModalRef.value?.open({ accountId: item.id, last_synced_at: item.last_synced_at });
}; };
const handlePause = (item) => { const handlePause = (item) => {

View File

@ -20,8 +20,8 @@ import { ref } from 'vue';
import { pausePatchPlacementAccount } from '@/api/all/propertyMarketing'; import { pausePatchPlacementAccount } from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const emits = defineEmits(['update', 'close']); const emits = defineEmits(['close']);
const update = inject('update');
const visible = ref(false); const visible = ref(false);
const accountId = ref(null); const accountId = ref(null);
const accountName = ref(''); const accountName = ref('');
@ -45,7 +45,7 @@ async function onConfirm() {
const { code } = await pausePatchPlacementAccount(accountId.value); const { code } = await pausePatchPlacementAccount(accountId.value);
if (code === 200) { if (code === 200) {
AMessage.success('暂停成功'); AMessage.success('暂停成功');
emits('update'); update();
onClose(); onClose();
} }
} }

View File

@ -137,7 +137,7 @@
</a-button> </a-button>
</template> </template>
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" /> <AuthorizedAccountModal ref="authorizedAccountModalRef" />
<!-- <ImportPromptModal ref="importPromptModalRef" /> --> <!-- <ImportPromptModal ref="importPromptModalRef" /> -->
</a-modal> </a-modal>
</template> </template>
@ -148,7 +148,7 @@ import { ref, defineEmits } from 'vue';
import AuthorizedAccountModal from '../authorized-account-modal'; import AuthorizedAccountModal from '../authorized-account-modal';
// import ImportPromptModal from '../import-prompt-modal'; // import ImportPromptModal from '../import-prompt-modal';
import StatusBox from '../status-box'; import StatusBox from '../status-box';
import { PLATFORM_LIST } from '@/views/property-marketing/put-account/common_constants'; import { PLATFORM_LIST, ENUM_PLATFORM } from '@/views/property-marketing/put-account/common_constants';
import { import {
postPlacementAccounts, postPlacementAccounts,
@ -161,7 +161,7 @@ import {
import icon1 from '@/assets/img/media-account/icon-download.png'; import icon1 from '@/assets/img/media-account/icon-download.png';
import icon2 from '@/assets/img/media-account/icon-delete.png'; import icon2 from '@/assets/img/media-account/icon-delete.png';
const emits = defineEmits(['update']); const update = inject('update');
const UploadStatus = { const UploadStatus = {
DEFAULT: 'default', DEFAULT: 'default',
@ -278,7 +278,7 @@ const handleBatchImport = async () => {
}); });
if (code === 200) { if (code === 200) {
AMessage.success('导入成功'); AMessage.success('导入成功');
emits('update'); update();
onClose(); onClose();
} else { } else {
uploadStatus.value = UploadStatus.ERROR; uploadStatus.value = UploadStatus.ERROR;
@ -290,6 +290,27 @@ const handleBatchImport = async () => {
// importPromptModalRef.value.open(); // importPromptModalRef.value.open();
}; };
const handleAdd = async () => {
// 聚光无子账号
if (form.value.platform === ENUM_PLATFORM.jg) {
const { code, data } = await postPlacementAccounts(form.value);
if (code === 200) {
update();
authorizedAccountModalRef.value.open({ accountId: data?.id });
}
} else {
authorizedAccountModalRef.value.open({ form: form.value, needSelectSubAccount: true });
}
};
const handleEdit = async () => {
const { code } = await putPlacementAccounts({ id: id.value, ...form.value });
if (code === 200) {
isEdit.value && AMessage.success('修改成功');
update();
onClose();
}
};
async function onSubmit() { async function onSubmit() {
if (isBatchImport.value) { if (isBatchImport.value) {
handleBatchImport(); handleBatchImport();
@ -298,27 +319,11 @@ async function onSubmit() {
formRef.value.validate(async (errors) => { formRef.value.validate(async (errors) => {
if (!errors) { if (!errors) {
const _fn = id.value ? putPlacementAccounts : postPlacementAccounts; isEdit.value ? handleEdit() : handleAdd();
const _params = id.value ? { id: id.value, ...form.value } : form.value;
const { code, data } = await _fn(_params);
if (code === 200) {
isEdit.value && AMessage.success('修改成功');
emits('update');
if (isEdit.value) {
onClose();
} else {
handleSuccess(data?.id);
}
}
} }
}); });
} }
const handleSuccess = (id) => {
authorizedAccountModalRef.value.open(id);
};
const handleDownloadTemplate = async () => { const handleDownloadTemplate = async () => {
const { code, data } = await getPlacementAccountsTemplateUrl(); const { code, data } = await getPlacementAccountsTemplateUrl();
if (code === 200) { if (code === 200) {
@ -326,6 +331,7 @@ const handleDownloadTemplate = async () => {
} }
}; };
provide('closeAddAccountModal', onClose);
// 对外暴露打开弹窗方法 // 对外暴露打开弹窗方法
defineExpose({ open }); defineExpose({ open });
</script> </script>

View File

@ -59,23 +59,28 @@
<a-button type="primary" size="large" @click="handleOk">{{ confirmBtnText }} </a-button> <a-button type="primary" size="large" @click="handleOk">{{ confirmBtnText }} </a-button>
</template> </template>
</a-modal> </a-modal>
<SelectSubAccountModal ref="selectSubAccountModalRef" />
</template> </template>
<script setup> <script setup>
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { defineExpose, ref, computed, defineEmits } from 'vue'; import { defineExpose, ref, computed, defineEmits } from 'vue';
import { exactFormatTime } from '@/utils/tools'; import { exactFormatTime } from '@/utils/tools';
import { ENUM_PLATFORM } from '@/views/property-marketing/put-account/common_constants';
import { import {
putPlacementAccountsAuthorized, putPlacementAccountsAuthorized,
getPlacementAccountsAuthorizedStatus, getPlacementAccountsAuthorizedStatus,
postPlacementAccountsSync, postPlacementAccountsSync,
} from '@/api/all/propertyMarketing'; } from '@/api/all/propertyMarketing';
import SelectSubAccountModal from '../select-sub-account-modal';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
import icon2 from '@/assets/img/media-account/icon-feedback-success.png'; import icon2 from '@/assets/img/media-account/icon-feedback-success.png';
import icon3 from '@/assets/img/media-account/icon-feedback-fail.png'; import icon3 from '@/assets/img/media-account/icon-feedback-fail.png';
const emits = defineEmits(['update']); const update = inject('update');
const INITIAL_SYNC_TYPE = 'sync'; const INITIAL_SYNC_TYPE = 'sync';
const visible = ref(false); const visible = ref(false);
const isLoading = ref(false); const isLoading = ref(false);
@ -86,9 +91,12 @@ const failReason = ref('');
const progress = ref(0); const progress = ref(0);
const id = ref(''); const id = ref('');
const selectSubAccountModalRef = ref(null);
const lastSyncedAt = ref(null); // 上次同步时间戳 const lastSyncedAt = ref(null); // 上次同步时间戳
const showSyncTip = ref(false); const showSyncTip = ref(false);
const shouldSelectSubAccount = ref(false);
const syncType = ref(INITIAL_SYNC_TYPE); // sync no_sync const syncType = ref(INITIAL_SYNC_TYPE); // sync no_sync
const addAccountFormData = ref(null); // 添加账户表单数据
const INITIAL_FORM = { const INITIAL_FORM = {
account: '', account: '',
@ -125,13 +133,17 @@ const getDaysDiffText = (lastSyncedAt) => {
return `${daysDiff}`; return `${daysDiff}`;
}; };
const open = (accountId, last_synced_at = null) => { const open = ({ accountId, last_synced_at = null, form = null, needSelectSubAccount = false }) => {
reset();
id.value = accountId; id.value = accountId;
lastSyncedAt.value = last_synced_at; lastSyncedAt.value = last_synced_at;
addAccountFormData.value = form;
shouldSelectSubAccount.value = needSelectSubAccount;
visible.value = true; visible.value = true;
}; };
const close = () => { const reset = () => {
formRef.value?.resetFields(); formRef.value?.resetFields();
formRef.value?.clearValidate(); formRef.value?.clearValidate();
@ -145,8 +157,13 @@ const close = () => {
lastSyncedAt.value = null; lastSyncedAt.value = null;
syncType.value = INITIAL_SYNC_TYPE; syncType.value = INITIAL_SYNC_TYPE;
showSyncTip.value = false; showSyncTip.value = false;
shouldSelectSubAccount.value = false;
addAccountFormData.value = null;
clearFakeProgressTimer(); clearFakeProgressTimer();
clearStatusPollingTimer(); clearStatusPollingTimer();
};
const close = () => {
visible.value = false; visible.value = false;
}; };
@ -242,27 +259,39 @@ const handleSyncData = () => {
}; };
const handleOk = () => { const handleOk = () => {
// n天未同步更新
if (lastSyncedAt.value && lastSyncedAt.value < dayjs().startOf('day').valueOf()) { if (lastSyncedAt.value && lastSyncedAt.value < dayjs().startOf('day').valueOf()) {
handleSyncData(); handleSyncData();
return; return;
} }
// 已完成
if (isCompleted.value) { if (isCompleted.value) {
if (isSuccess.value) { if (isSuccess.value) {
emits('update'); update();
close(); close();
} else { } else {
startLoading(); startLoading();
} }
} else { return;
formRef.value.validate(async (errors) => {
if (!errors) {
startLoading();
}
});
} }
// 未完成,校验表单
formRef.value.validate(async (errors) => {
if (errors) return;
if (shouldSelectSubAccount.value) {
visible.value = false;
selectSubAccountModalRef.value.open({
...addAccountFormData.value,
...form.value,
});
} else {
startLoading();
}
});
}; };
provide('closeAuthorizeAccountModal', close);
defineExpose({ defineExpose({
open, open,
}); });

View File

@ -1,17 +1,4 @@
.container { .container {
:deep(.arco-input-wrapper),
:deep(.arco-select-view-single),
:deep(.arco-select-view-multiple) {
border-radius: 4px;
border-color: #d7d7d9;
background-color: #fff;
&:focus-within,
&.arco-input-focus {
background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2);
}
}
.filter-row { .filter-row {
.filter-row-item { .filter-row-item {
&:not(:last-child) { &:not(:last-child) {

View File

@ -0,0 +1,23 @@
export const INITIAL_FORM = {
platform: '',
account: '',
password: '',
account_name: '',
account_id: '',
};
export const INITIAL_PAGE_INFO = {
page: 1,
page_size: 20,
total: 0,
};
export const TABLE_COLUMNS = [
{
title: '账户名称',
dataIndex: 'account_name',
},
{
title: '账户ID',
dataIndex: 'account_id',
},
];

View File

@ -0,0 +1,213 @@
<template>
<a-modal
v-model:visible="visible"
title="选择子账户"
modal-class="select-sub-account-modal"
width="720px"
:mask-closable="false"
@close="onClose"
>
<div class="filter-row flex mb-16px">
<div class="filter-row-item flex items-center">
<span class="label">账户名称</span>
<a-input
v-model="query.account_name"
class="w-240px"
placeholder="请搜索..."
size="medium"
allow-clear
@change="reload"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</div>
<div class="filter-row-item flex items-center">
<span class="label">账户ID</span>
<a-input
v-model="query.account_id"
class="w-240px"
placeholder="请搜索..."
size="medium"
allow-clear
@change="reload"
>
<template #prefix>
<icon-search />
</template>
</a-input>
</div>
</div>
<a-table
ref="tableRef"
:data="dataSource"
column-resizable
:row-selection="rowSelection"
:row-key="ROW_KEY"
:pagination="false"
:scroll="{ x: '100%', y: '100%' }"
class="w-100% flex-1 overflow-hidden"
:selected-keys="selectedRowKeys"
bordered
@select="handleSelect"
@select-all="handleSelectAll"
>
<template #empty>
<NoData />
</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 #cell="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</a-table-column>
</template>
</a-table>
<div v-if="pageInfo.total > 0" class="flex justify-end mt-16px">
<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>
<span class="cts color-#3C4043 s1"
>已选<span class="color-#6D4CFE num mx-3px">{{ selectedAccounts.length }}</span>账户</span
>
<div class="flex items-center">
<a-button class="cancel-btn" size="large" @click="onClose">取消</a-button>
<a-button
type="primary"
class="ml-16px"
status="danger"
size="large"
:disabled="!selectedAccounts.length"
@click="onConfirm"
>添加已选账户</a-button
>
</div>
</template>
</a-modal>
</template>
<script setup>
import { INITIAL_FORM, INITIAL_PAGE_INFO, TABLE_COLUMNS } from './constants';
import { formatTableField } from '@/utils/tools';
import { postSubAccount, postAddSubAccount } from '@/api/all/propertyMarketing';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
const emits = defineEmits('confirm');
const update = inject('update');
const closeAddAccountModal = inject('closeAddAccountModal');
const closeAuthorizeAccountModal = inject('closeAuthorizeAccountModal');
const visible = ref(false);
const query = ref(cloneDeep(INITIAL_FORM));
const form = ref(null);
const ROW_KEY = 'account_id';
const {
selectedRowKeys,
selectedRows: selectedAccounts,
dataSource,
pageInfo,
onPageChange,
onPageSizeChange,
rowSelection,
handleSelect,
handleSelectAll,
} = useTableSelectionWithPagination({
rowKey: ROW_KEY,
onPageChange: () => {
getData();
},
onPageSizeChange: () => {
getData();
},
});
const open = (formData) => {
const { platform, account, password } = formData;
form.value = formData;
query.value = {
...query.value,
platform,
account,
password,
};
getData();
visible.value = true;
};
const onClose = () => {
form.value = null;
selectedAccounts.value = [];
selectedRowKeys.value = [];
query.value = cloneDeep(INITIAL_FORM);
pageInfo.value = cloneDeep(INITIAL_PAGE_INFO);
visible.value = false;
};
const onConfirm = async () => {
const { code } = await postAddSubAccount({ ...form.value, subaccounts: selectedAccounts.value });
if (code === 200) {
visible.value = false;
update();
closeAuthorizeAccountModal();
closeAddAccountModal();
}
};
const getData = async () => {
const { page, page_size } = pageInfo.value;
const { code, data } = await postSubAccount({
...query.value,
page,
page_size,
});
if (code === 200) {
dataSource.value = data?.data ?? [];
pageInfo.value.total = data.total;
}
};
const reload = () => {
pageInfo.value.page = 1;
getData();
};
defineExpose({ open });
</script>
<style lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,45 @@
.select-sub-account-modal {
.cts {
color: var(--Text-1, #211f24);
font-family: $font-family-medium;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
.arco-modal-header {
}
.arco-modal-body {
height: 536px;
overflow: hidden;
display: flex;
flex-direction: column;
.filter-row {
.filter-row-item {
&:not(:last-child) {
margin-right: 24px;
}
.label {
margin-right: 12px;
color: #211f24;
font-family: 'PuHuiTi-Regular';
font-size: 14px;
font-style: normal;
font-weight: 400;
flex-shrink: 0;
line-height: 22px;
}
}
}
}
.arco-modal-footer {
justify-content: space-between;
.s1 {
font-family: $font-family-regular;
.num {
font-family: $font-family-medium;
}
}
}
}

View File

@ -67,7 +67,6 @@
@selectionChange="handleSelectionChange" @selectionChange="handleSelectionChange"
@delete="handleDelete" @delete="handleDelete"
@openEdit="handleOpenEdit" @openEdit="handleOpenEdit"
@update="getData"
/> />
<NoData v-else /> <NoData v-else />
@ -86,7 +85,7 @@
</div> </div>
</div> </div>
<AddAccountModal ref="addAccountModalRef" @update="getData" /> <AddAccountModal ref="addAccountModalRef" />
<DeleteAccountModal ref="deleteAccountRef" @update="onDeleteSuccess" /> <DeleteAccountModal ref="deleteAccountRef" @update="onDeleteSuccess" />
</div> </div>
</template> </template>
@ -254,6 +253,8 @@ const handleOpenAbnormalAccount = () => {
query.value.status = 2; query.value.status = 2;
reload(); reload();
}; };
provide('update', getData);
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -2,6 +2,12 @@ import icon1 from '@/assets/img/media-account/icon-jl.png';
import icon2 from '@/assets/img/media-account/icon-jg.png'; import icon2 from '@/assets/img/media-account/icon-jg.png';
import icon3 from '@/assets/img/media-account/icon-bili.png'; import icon3 from '@/assets/img/media-account/icon-bili.png';
export const ENUM_PLATFORM = {
jl: 0,
jg: 1,
bili: 2,
};
export const PLATFORM_LIST = [ export const PLATFORM_LIST = [
{ {
label: '巨量', label: '巨量',