feat(property-marketing): 增加媒体账号同步状态展示与操作功能- 在账号管理表格中新增“更新状态”列,用于展示同步状态
This commit is contained in:
@ -99,7 +99,7 @@ export default defineComponent({
|
|||||||
watch([updateCount, scrollReachEnd, listRef], () => {
|
watch([updateCount, scrollReachEnd, listRef], () => {
|
||||||
if (props.autoScroll && unref(listRef) && unref(scrollReachEnd)) {
|
if (props.autoScroll && unref(listRef) && unref(scrollReachEnd)) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
console.log('自然滚动')
|
console.log('自然滚动');
|
||||||
unref(listRef)!.scrollTo({ top: unref(listRef)!.scrollHeight });
|
unref(listRef)!.scrollTo({ top: unref(listRef)!.scrollHeight });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ export default defineComponent({
|
|||||||
if (!props.autoScroll) return;
|
if (!props.autoScroll) return;
|
||||||
// 首次渲染:当有内容时滚到底部一次
|
// 首次渲染:当有内容时滚到底部一次
|
||||||
if (!didInitialAutoScroll.value && newLen > 0) {
|
if (!didInitialAutoScroll.value && newLen > 0) {
|
||||||
console.log('首次渲染滚动到底部-----')
|
console.log('首次渲染滚动到底部-----');
|
||||||
scrollToBottom('auto');
|
scrollToBottom('auto');
|
||||||
didInitialAutoScroll.value = true;
|
didInitialAutoScroll.value = true;
|
||||||
return;
|
return;
|
||||||
@ -142,7 +142,7 @@ export default defineComponent({
|
|||||||
el.scrollTo({ top: el.scrollHeight, behavior });
|
el.scrollTo({ top: el.scrollHeight, behavior });
|
||||||
setScrollReachEnd(true);
|
setScrollReachEnd(true);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 对外暴露能力
|
// 对外暴露能力
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
bordered
|
bordered
|
||||||
class="flex-1 w-100%"
|
class="flex-1 w-100%"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
:rowClassName="(record) => (isSyncing(record) || isSyncFailed(record) ? 'sync' : '')"
|
||||||
>
|
>
|
||||||
<template #emptyText>
|
<template #emptyText>
|
||||||
<NoData />
|
<NoData />
|
||||||
@ -23,6 +24,7 @@
|
|||||||
:minWidth="column.minWidth"
|
:minWidth="column.minWidth"
|
||||||
:sorter="column.sortable"
|
:sorter="column.sortable"
|
||||||
:width="column.width"
|
:width="column.width"
|
||||||
|
:className="column.class"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="cts mr-4px">{{ column.title }}</span>
|
<span class="cts mr-4px">{{ column.title }}</span>
|
||||||
@ -53,7 +55,7 @@
|
|||||||
<StatusBox :item="record" />
|
<StatusBox :item="record" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
|
<template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
|
||||||
<img :src="record.platform === 0 ? icon2 : icon3" height="16" width="16" />
|
<img :src="record.platform === 0 ? icon2 : icon3" alt="" height="16" width="16" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'last_synced_at'" #customRender="{ record }">
|
<template v-else-if="column.dataIndex === 'last_synced_at'" #customRender="{ record }">
|
||||||
<span class="cts num">{{ getLastSyncedAt(record) }}</span>
|
<span class="cts num">{{ getLastSyncedAt(record) }}</span>
|
||||||
@ -61,6 +63,25 @@
|
|||||||
<template v-else-if="column.dataIndex === 'last_authorized_at'" #customRender="{ record }">
|
<template v-else-if="column.dataIndex === 'last_authorized_at'" #customRender="{ record }">
|
||||||
<span class="cts num">{{ formatTime(record.last_authorized_at) }}</span>
|
<span class="cts num">{{ formatTime(record.last_authorized_at) }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="column.dataIndex === 'sync'" #customRender="{ record }">
|
||||||
|
<div v-if="isSyncing(record)" class="sync-col">
|
||||||
|
<Spin tip="更新数据中..." />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isSyncFailed(record)" class="flex items-center">
|
||||||
|
<div class="flex items-center box mr-16px">
|
||||||
|
<img :src="icon4" alt="" class="mr-8px" height="16" width="16" />
|
||||||
|
<span class="name !mb-0">{{ getErrorStatusText(record) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Button class="mr-8px !h-24px !px-12px" ghost size="small" type="primary" @click="handleCancel(record)">
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button v-if="showConfirmBtn(record)" ghost size="small" type="primary" @click="handleConfirm(record)"
|
||||||
|
>{{ getConfirmBtnText(record) }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
|
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<FooterBtn
|
<FooterBtn
|
||||||
@ -82,12 +103,17 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, inject } from 'vue';
|
import { ref, computed, inject } from 'vue';
|
||||||
import { Tooltip, Table, Tag } from 'ant-design-vue';
|
import { Tooltip, Table, Tag, Spin, Button } from 'ant-design-vue';
|
||||||
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
import StatusBox, {
|
||||||
|
EnumErrorStatus,
|
||||||
|
EnumStatus,
|
||||||
|
errorStatusMap,
|
||||||
|
} from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
|
||||||
|
|
||||||
const { Column } = Table;
|
const { Column } = Table;
|
||||||
|
|
||||||
import { formatTableField, exactFormatTime } from '@/utils/tools';
|
import { formatTableField, exactFormatTime } from '@/utils/tools';
|
||||||
|
import { deleteSyncStatus } from '@/api/all/propertyMarketing';
|
||||||
|
|
||||||
import TextOverTips from '@/components/text-over-tips';
|
import TextOverTips from '@/components/text-over-tips';
|
||||||
import FooterBtn from './footer-btn';
|
import FooterBtn from './footer-btn';
|
||||||
@ -95,6 +121,7 @@ import FooterBtn from './footer-btn';
|
|||||||
// 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/platform/icon-dy.png';
|
import icon2 from '@/assets/img/platform/icon-dy.png';
|
||||||
import icon3 from '@/assets/img/platform/icon-xhs.png';
|
import icon3 from '@/assets/img/platform/icon-xhs.png';
|
||||||
|
import icon4 from '@/assets/img/media-account/icon-warn.png';
|
||||||
|
|
||||||
const emits = defineEmits([
|
const emits = defineEmits([
|
||||||
'openEdit',
|
'openEdit',
|
||||||
@ -178,8 +205,89 @@ const rowSelection = {
|
|||||||
emits('selectAll', selected);
|
emits('selectAll', selected);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSyncing = (item) => {
|
||||||
|
if (!props.syncMediaAccounts.length) return false;
|
||||||
|
return getSyncMediaAccount(item)?.status === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSyncFailed = (item) => {
|
||||||
|
return getSyncMediaAccount(item)?.status === 2;
|
||||||
|
};
|
||||||
|
const getErrorStatusText = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
return `异常(${errorStatusMap.get(error_status)?.text ?? ''})`;
|
||||||
|
};
|
||||||
|
const handleCancel = async (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
await deleteSyncStatus(item.id);
|
||||||
|
|
||||||
|
item.status = EnumStatus.ABNORMAL;
|
||||||
|
item.error_status = error_status;
|
||||||
|
|
||||||
|
emits('updateSyncStatus', item);
|
||||||
|
};
|
||||||
|
const handleConfirm = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
if (error_status === EnumErrorStatus.MISSING) {
|
||||||
|
syncData(item);
|
||||||
|
}
|
||||||
|
if (error_status === EnumErrorStatus.LOGIN) {
|
||||||
|
emits('');
|
||||||
|
handleReauthorize(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const showConfirmBtn = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
return [EnumErrorStatus.MISSING, EnumErrorStatus.LOGIN].includes(error_status);
|
||||||
|
};
|
||||||
|
const getConfirmBtnText = (item) => {
|
||||||
|
const error_status = getSyncMediaAccount(item)?.error_status;
|
||||||
|
if (error_status === EnumErrorStatus.MISSING) {
|
||||||
|
return '重新更新';
|
||||||
|
}
|
||||||
|
if (error_status === EnumErrorStatus.LOGIN) {
|
||||||
|
return '重新授权';
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import './style.scss';
|
:deep(.ant-table) {
|
||||||
|
.ant-table-tbody {
|
||||||
|
.ant-table-row {
|
||||||
|
&.sync {
|
||||||
|
position: relative;
|
||||||
|
border-bottom: none !important;
|
||||||
|
|
||||||
|
.sync-row {
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 0.9;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 22;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sync-col {
|
||||||
|
.ant-spin {
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
.ant-spin-dot {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -49,6 +49,11 @@ export const TABLE_COLUMNS = [
|
|||||||
width: 200,
|
width: 200,
|
||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '更新状态',
|
||||||
|
dataIndex: 'sync',
|
||||||
|
class: 'sync-row',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
@ -94,6 +99,7 @@ export const TABLE_COLUMNS = [
|
|||||||
dataIndex: 'tags',
|
dataIndex: 'tags',
|
||||||
width: 180,
|
width: 180,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
|
|||||||
Reference in New Issue
Block a user