feat: 同步数据/批量同步
This commit is contained in:
@ -70,6 +70,9 @@
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption class="color-#211F24" @click="openEdit(item)">编辑</a-doption>
|
||||
<a-doption v-if="item.status === EnumStatus.NORMAL" class="color-#211F24" @click="handleReauthorize(item)"
|
||||
>重新授权</a-doption
|
||||
>
|
||||
<a-doption v-if="showPauseButton(item.status)" class="color-#211F24" @click="handlePause(item)"
|
||||
>暂停同步</a-doption
|
||||
>
|
||||
@ -113,7 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, ref, computed } from 'vue';
|
||||
import { defineProps, ref, computed, inject } from 'vue';
|
||||
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
|
||||
|
||||
import PauseAccountPatchModal from './pause-account-patch';
|
||||
@ -183,6 +186,9 @@ const handlePause = (item) => {
|
||||
const showPauseButton = (status) => {
|
||||
return ![EnumStatus.PAUSE, EnumStatus.UNAUTHORIZED].includes(status);
|
||||
};
|
||||
const showSyncDataButton = (status) => {
|
||||
return [EnumStatus.NORMAL, EnumStatus.ABNORMAL_MISSING].includes(status);
|
||||
};
|
||||
const isUnauthorizedStatus = (status) => {
|
||||
return [EnumStatus.UNAUTHORIZED].includes(status);
|
||||
};
|
||||
@ -201,13 +207,19 @@ const getTooltipText = (status) => {
|
||||
return STATUS_LIST.find((v) => v.value === status)?.tooltip ?? '-';
|
||||
};
|
||||
|
||||
const handleSyncData = inject('handleSyncData');
|
||||
|
||||
const onBtnClick = (item) => {
|
||||
if (showSyncDataButton(item.status)) {
|
||||
handleSyncData && handleSyncData(item);
|
||||
return;
|
||||
}
|
||||
if (isUnauthorizedStatus(item.status)) {
|
||||
handleReauthorize(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([EnumStatus.PAUSE, EnumStatus.NORMAL].includes(item.status) || isAbnormalStatus(item.status)) {
|
||||
if ([EnumStatus.PAUSE].includes(item.status) || isAbnormalStatus(item.status)) {
|
||||
handleReauthorize(item);
|
||||
return;
|
||||
}
|
||||
@ -216,11 +228,15 @@ const onBtnClick = (item) => {
|
||||
};
|
||||
|
||||
const getBtnText = (item) => {
|
||||
if (showSyncDataButton(item.status)) {
|
||||
return '更新数据';
|
||||
}
|
||||
|
||||
if (isUnauthorizedStatus(item.status)) {
|
||||
return '去授权';
|
||||
}
|
||||
|
||||
if ([EnumStatus.PAUSE, EnumStatus.NORMAL].includes(item.status) || isAbnormalStatus(item.status)) {
|
||||
if ([EnumStatus.PAUSE].includes(item.status) || isAbnormalStatus(item.status)) {
|
||||
return '重新授权';
|
||||
}
|
||||
|
||||
|
||||
@ -162,6 +162,8 @@
|
||||
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
|
||||
<ImportPromptModal ref="importPromptModalRef" />
|
||||
</a-modal>
|
||||
|
||||
<SyncDataModal ref="syncDataModalRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -171,6 +173,7 @@ import GroupSelect from '@/views/property-marketing/media-account/components/gro
|
||||
import AuthorizedAccountModal from '../authorized-account-modal';
|
||||
import ImportPromptModal from '../import-prompt-modal';
|
||||
import StatusBox from '../status-box';
|
||||
import SyncDataModal from '../sync-data-modal';
|
||||
|
||||
import {
|
||||
fetchAccountTags,
|
||||
@ -220,6 +223,7 @@ const importPromptModalRef = ref(null);
|
||||
const uploadRef = ref(null);
|
||||
const isCustomCookie = ref(false);
|
||||
const form = ref(cloneDeep(INITIAL_FORM));
|
||||
const syncDataModalRef = ref(null);
|
||||
|
||||
const rules = {
|
||||
mobile: [
|
||||
@ -245,8 +249,13 @@ const rules = {
|
||||
|
||||
const isBatchImport = computed(() => uploadType.value === 'batch');
|
||||
const confirmBtnText = computed(() => {
|
||||
if (isBatchImport.value) return '确定导入';
|
||||
return isEdit.value ? '确定' : '生成授权码';
|
||||
if (isBatchImport.value) {
|
||||
return '确定导入';
|
||||
} else if (isEdit.value) {
|
||||
return '确定';
|
||||
} else {
|
||||
return isCustomCookie.value ? '确认添加' : '生成授权码';
|
||||
}
|
||||
});
|
||||
|
||||
// 获取分组数据
|
||||
@ -354,7 +363,11 @@ const handleAddAccount = async () => {
|
||||
onClose();
|
||||
|
||||
const { id, platform } = data;
|
||||
!_isCustomCookie && startAuthorized(id, platform);
|
||||
if (_isCustomCookie) {
|
||||
syncDataModalRef.value.open(id);
|
||||
} else {
|
||||
startAuthorized(id, platform);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -375,6 +388,11 @@ async function onSubmit() {
|
||||
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
if (isCustomCookie.value && !form.value.cookie) {
|
||||
AMessage.warning('请填写Cookie值');
|
||||
return;
|
||||
}
|
||||
|
||||
isEdit.value ? handleEditAccount() : handleAddAccount();
|
||||
}
|
||||
});
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
<template v-else>
|
||||
<!-- 完成状态 -->
|
||||
<template v-if="modalState === MODAL_STATE.SUCCESS || modalState === MODAL_STATE.FAILED">
|
||||
<template v-if="[MODAL_STATE.SUCCESS, MODAL_STATE.FAILED].includes(modalState)">
|
||||
<img :src="modalState === MODAL_STATE.SUCCESS ? icon2 : icon3" width="80" height="80" class="mb-16px" />
|
||||
<p class="s2">{{ `数据初始化${modalState === MODAL_STATE.SUCCESS ? '成功' : '失败'}。` }}</p>
|
||||
<p v-if="modalState === MODAL_STATE.FAILED" class="red-text">失败原因:{{ failReason || '-' }}</p>
|
||||
@ -61,10 +61,8 @@
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 正常二维码 -->
|
||||
<a-image v-else :src="qrCodeUrl" width="160" height="160" />
|
||||
|
||||
<!-- 二维码失效遮罩 -->
|
||||
<div v-if="modalState === MODAL_STATE.QR_EXPIRED" class="mask cursor-pointer" @click="handleRefreshQrCode">
|
||||
<icon-refresh size="24" class="mb-13px" />
|
||||
@ -82,7 +80,7 @@
|
||||
重新生成
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="modalState === MODAL_STATE.SUCCESS || modalState === MODAL_STATE.FAILED"
|
||||
v-if="[MODAL_STATE.SUCCESS, MODAL_STATE.FAILED].includes(modalState)"
|
||||
size="large"
|
||||
class="cancel-btn"
|
||||
@click="close"
|
||||
@ -94,12 +92,15 @@
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<SyncDataModal ref="syncDataModalRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineExpose, ref, computed } from 'vue';
|
||||
import { Message as AMessage } from '@arco-design/web-vue';
|
||||
import { getAuthorizedImage, getMediaAccountsAuthorizedStatus } from '@/api/all/propertyMarketing';
|
||||
import SyncDataModal from '../sync-data-modal';
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-default-qrcode.png';
|
||||
import icon2 from '@/assets/img/media-account/icon-feedback-success.png';
|
||||
@ -127,6 +128,7 @@ const progress = ref(0);
|
||||
const id = ref('');
|
||||
const platform = ref('');
|
||||
const qrCodeUrl = ref('');
|
||||
const syncDataModalRef = ref(null);
|
||||
|
||||
let progressTimer = null;
|
||||
let overdueTimer = null;
|
||||
@ -140,7 +142,7 @@ const confirmBtnText = computed(() => {
|
||||
[MODAL_STATE.QR_READY]: '完成扫码',
|
||||
[MODAL_STATE.QR_EXPIRED]: '重新生成',
|
||||
[MODAL_STATE.LOADING]: '处理中...',
|
||||
[MODAL_STATE.SUCCESS]: '继续添加',
|
||||
[MODAL_STATE.SUCCESS]: '确认添加',
|
||||
[MODAL_STATE.FAILED]: '重新扫码',
|
||||
};
|
||||
return btnTextMap[modalState.value] || '确定';
|
||||
@ -299,6 +301,7 @@ const handleOk = () => {
|
||||
|
||||
// 授权成功,关闭弹窗
|
||||
if (modalState.value === MODAL_STATE.SUCCESS) {
|
||||
syncDataModalRef.value.open(id.value);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,12 +108,15 @@
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<SyncDataModal ref="syncDataModalRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineExpose, ref, computed } from 'vue';
|
||||
import { Message as AMessage } from '@arco-design/web-vue';
|
||||
import { getMediaAccountsAuthorizedStatus, getAuthorizedImage } from '@/api/all/propertyMarketing';
|
||||
import SyncDataModal from '../sync-data-modal';
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-default-qrcode.png';
|
||||
import icon2 from '@/assets/img/media-account/icon-feedback-success.png';
|
||||
@ -142,6 +145,7 @@ const id = ref('');
|
||||
const platform = ref('');
|
||||
const qrCodeUrl = ref('');
|
||||
const isNicknameChanged = ref(false); // 昵称发生变化
|
||||
const syncDataModalRef = ref(null);
|
||||
|
||||
let progressTimer = null;
|
||||
let overdueTimer = null;
|
||||
@ -155,7 +159,7 @@ const confirmBtnText = computed(() => {
|
||||
[MODAL_STATE.QR_READY]: '完成扫码',
|
||||
[MODAL_STATE.QR_EXPIRED]: '重新生成',
|
||||
[MODAL_STATE.LOADING]: '处理中...',
|
||||
[MODAL_STATE.SUCCESS]: isNicknameChanged.value ? '确定覆盖' : '继续添加',
|
||||
[MODAL_STATE.SUCCESS]: isNicknameChanged.value ? '确定覆盖' : '确认添加',
|
||||
[MODAL_STATE.FAILED]: '重新扫码',
|
||||
};
|
||||
return btnTextMap[modalState.value] || '确定';
|
||||
@ -318,6 +322,7 @@ const handleOk = () => {
|
||||
console.log('确定覆盖');
|
||||
// 这里可以添加覆盖逻辑
|
||||
} else {
|
||||
syncDataModalRef.value.open(id.value);
|
||||
close();
|
||||
}
|
||||
return;
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
<!--
|
||||
* @Author: RenXiaoDong
|
||||
* @Date: 2025-06-25 17:51:46
|
||||
-->
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
width="480px"
|
||||
title="更新数据"
|
||||
modal-class="sync-data-modal"
|
||||
:mask-closable="false"
|
||||
@close="close"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="flex items-center">
|
||||
<img :src="icon1" width="20" height="20" class="mr-12px" />
|
||||
<div class="flex flex-col">
|
||||
<p class="tip">为确保数据准确,建议立即同步账号数据。您也可以在账号管理列表中手动触发更新。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button size="large" class="cancel-btn" @click="close">稍后再说</a-button>
|
||||
<a-button type="primary" size="large" @click="handleOk"> 更新数据 </a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineExpose } from 'vue';
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
|
||||
|
||||
const handleSyncData = inject('handleSyncData');
|
||||
|
||||
const id = ref('');
|
||||
|
||||
const visible = ref(false);
|
||||
const open = (accountId) => {
|
||||
id.value = accountId;
|
||||
visible.value = true;
|
||||
};
|
||||
const close = () => {
|
||||
id.value = '';
|
||||
visible.value = false;
|
||||
};
|
||||
const handleOk = () => {
|
||||
handleSyncData({ id: id.value });
|
||||
close();
|
||||
};
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss';
|
||||
</style>
|
||||
@ -0,0 +1,14 @@
|
||||
@import "@/views/property-marketing/component.scss";
|
||||
.sync-data-modal {
|
||||
border-radius: 8px;
|
||||
.arco-modal-body {
|
||||
.tip {
|
||||
color: var(--Text-1, #211f24);
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px; /* 157.143% */
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,6 +51,9 @@
|
||||
个账号
|
||||
</span>
|
||||
|
||||
<span class="operation-btn" :class="{ disabled: isDisabledBatchSyncData }" @click="handleBatchSyncData"
|
||||
>批量更新数据</span
|
||||
>
|
||||
<span class="operation-btn" @click="handleBatchTag">批量标签</span>
|
||||
<span class="operation-btn" @click="handleBatchGroup">批量分组</span>
|
||||
<span class="operation-btn red" @click="handleBatchDelete"> 批量删除 </span>
|
||||
@ -111,7 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, provide } from 'vue';
|
||||
|
||||
import FilterBlock from './components/filter-block';
|
||||
import AccountTable from './components/account-table';
|
||||
@ -121,9 +124,17 @@ import AddAccountModal from './components/add-account-modal';
|
||||
import DeleteAccountModal from './components/account-table/delete-account';
|
||||
import BatchTagModal from './components/batch-tag-modal';
|
||||
import BatchGroupModal from './components/batch-group-modal';
|
||||
import { Notification } from '@arco-design/web-vue';
|
||||
|
||||
import { INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
|
||||
import { getMediaAccounts, getMediaAccountsHealth } from '@/api/all/propertyMarketing';
|
||||
import { EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
|
||||
import {
|
||||
getMediaAccounts,
|
||||
getMediaAccountsHealth,
|
||||
postSyncMediaAccountData,
|
||||
postBatchSyncMediaAccountData,
|
||||
getMediaAccountSyncStatus,
|
||||
} from '@/api/all/propertyMarketing';
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-add.png';
|
||||
import icon2 from '@/assets/img/media-account/icon-group.png';
|
||||
@ -132,6 +143,8 @@ import icon4 from '@/assets/img/media-account/icon-success.png';
|
||||
import icon5 from '@/assets/img/media-account/icon-warn.png';
|
||||
import icon6 from '@/assets/img/media-account/icon-close.png';
|
||||
|
||||
let syncDataTimer = null;
|
||||
|
||||
const groupManageModalRef = ref(null);
|
||||
const tagsManageModalRef = ref(null);
|
||||
const addAccountModalRef = ref(null);
|
||||
@ -145,8 +158,10 @@ const query = ref(cloneDeep(INITIAL_QUERY));
|
||||
const dataSource = ref([]);
|
||||
const selectedItems = ref([]);
|
||||
const healthData = ref({});
|
||||
const startSyncData = ref(false);
|
||||
|
||||
const isAbNormalStatus = computed(() => healthData.value?.total_abnormal_number > 0);
|
||||
const isDisabledBatchSyncData = computed(() => selectedItems.value.some((item) => item.status !== EnumStatus.NORMAL));
|
||||
|
||||
const checkedAll = computed(() => selectedItems.value.length === dataSource.value.length);
|
||||
const indeterminate = computed(
|
||||
@ -180,10 +195,6 @@ const tipLabel = computed(() => {
|
||||
return `共有 ${total_abnormal_number} 个账号存在授权异常,其中:${abnormalLabels.join(',')}。`;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
getHealthData();
|
||||
getAccountData();
|
||||
@ -262,10 +273,54 @@ const handleDelete = (item) => {
|
||||
const handleCloseTip = () => {
|
||||
selectedItems.value = [];
|
||||
};
|
||||
const startSyncDataPolling = () => {
|
||||
startSyncData.value = true;
|
||||
clearSyncDataTimer();
|
||||
|
||||
syncDataTimer = setInterval(async () => {
|
||||
const { code, data } = await getMediaAccountSyncStatus();
|
||||
if (code === 200) {
|
||||
// 所有任务都结束了,才停止轮询,刷新页面
|
||||
const isEnd = data.every((item) => item.status !== 0);
|
||||
if (isEnd) {
|
||||
clearSyncDataTimer();
|
||||
startSyncData.value = false;
|
||||
|
||||
getData();
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const handleSyncData = async (item) => {
|
||||
Notification.info({
|
||||
title: '账号正在同步数据。',
|
||||
});
|
||||
|
||||
const { code } = await postSyncMediaAccountData(item.id);
|
||||
if (code === 200) {
|
||||
if (!startSyncData.value) {
|
||||
startSyncDataPolling();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleBatchTag = () => {
|
||||
batchTagModalRef.value?.open(selectedItems.value);
|
||||
};
|
||||
const handleBatchSyncData = async () => {
|
||||
if (isDisabledBatchSyncData.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = selectedItems.value.map((item) => item.id);
|
||||
const { code } = await postBatchSyncMediaAccountData({ ids });
|
||||
if (code === 200) {
|
||||
if (!startSyncData.value) {
|
||||
startSyncDataPolling();
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleBatchGroup = () => {
|
||||
batchGroupModalRef.value?.open(selectedItems.value);
|
||||
};
|
||||
@ -277,6 +332,21 @@ const handleOpenAbnormalAccount = () => {
|
||||
query.value.status = 2;
|
||||
reload();
|
||||
};
|
||||
const clearSyncDataTimer = () => {
|
||||
if (syncDataTimer) {
|
||||
clearInterval(syncDataTimer);
|
||||
syncDataTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getData();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
clearSyncDataTimer();
|
||||
});
|
||||
|
||||
provide('handleSyncData', handleSyncData);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: var(--Brand-Brand-6, #6d4cfe);
|
||||
font-family: $font-family-medium;
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@ -71,6 +71,10 @@
|
||||
&.red {
|
||||
color: #F64B31;
|
||||
}
|
||||
&.disabled {
|
||||
color: #C5B7FF;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-wrap {
|
||||
|
||||
Reference in New Issue
Block a user