feat: 新媒体账号状态处理

This commit is contained in:
rd
2025-07-24 18:30:33 +08:00
parent 050292b7cd
commit 458b4c0d60
11 changed files with 379 additions and 215 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

View File

@ -53,11 +53,7 @@
</div>
<p class="cts">
<template v-if="field.type === 'status'">
<div class="status-tag" :class="`status-tag-${detailData.status}`">
<span class="cts status-tag-text">{{
STATUS_LIST.find((item) => item.value === detailData.status)?.label
}}</span>
</div>
<StatusBox :item="detailData" class="w-fit" />
</template>
<template v-else-if="field.dataIndex === 'tags'">
<div v-if="detailData.tags?.length" class="flex items-center">
@ -138,8 +134,8 @@ import { useRoute } from 'vue-router';
import { formatTableField, formatNumberShow, exactFormatTime } from '@/utils/tools';
import { getAccountInfoFields } from '../../constants';
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
import { getPropPrefix } from '@/views/property-marketing/media-account/account-dashboard/constants';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
import { getAccountBoardDetail } from '@/api/all/propertyMarketing';

View File

@ -0,0 +1,166 @@
import {
EnumStatus,
EnumErrorStatus,
getStatusInfo,
} from '@/views/property-marketing/media-account/components/status-select/status-box';
import { Dropdown, Doption, Button, Tooltip } from '@arco-design/web-vue';
export default defineComponent({
name: 'FooterBtn',
props: {
item: {
type: Object,
default: () => ({}),
},
},
emits: ['syncData', 'handleReauthorize', 'handlePause', 'openDelete', 'openEdit'],
setup(props, { emit }) {
const statusInfo = computed(() => {
const { status, error_status, to_be_expire_for_cookie } = props.item;
return getStatusInfo(status, error_status, to_be_expire_for_cookie) ?? {};
});
const renderEditDoption = () => {
return (
<Doption class="color-#211F24" onClick={() => emit('openEdit', props.item)}>
</Doption>
);
};
const renderReauthorizeDoption = (text = '重新授权') => {
return (
<Doption class="color-#211F24" onClick={() => emit('handleReauthorize', props.item)}>
{text}
</Doption>
);
};
const renderPauseDoption = () => {
return (
<Doption class="color-#211F24" onClick={() => emit('handlePause', props.item)}>
</Doption>
);
};
const renderSyncBtn = () => {
return (
<Button type="outline" size="mini" onClick={() => emit('syncData', props.item)}>
</Button>
);
};
const renderDeleteDoption = () => {
return (
<Doption class="color-#F64B31" onClick={() => emit('openDelete', props.item)}>
</Doption>
);
};
const renderNormal = () => {
return (
<>
<Dropdown
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
</Button>
),
content: () => (
<>
{renderEditDoption()}
{renderReauthorizeDoption()}
{renderPauseDoption()}
{renderDeleteDoption()}
</>
),
}}
></Dropdown>
{renderSyncBtn()}
</>
);
};
const renderPause = () => {
return (
<>
<Dropdown
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
</Button>
),
content: () => (
<>
{renderEditDoption()}
{renderDeleteDoption()}
</>
),
}}
></Dropdown>
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
</Button>
</>
);
};
const renderAbnormal = () => {
const { error_status } = props.item;
const isMissing = error_status === EnumErrorStatus.MISSING;
const isUnauthorized = error_status === EnumErrorStatus.UNAUTHORIZED;
const renderSyncBtn = () => {
if (isMissing) {
renderSyncBtn();
} else if ([EnumErrorStatus.REQUEST, EnumErrorStatus.FREEZE].includes(error_status)) {
return (
<Tooltip content={statusInfo.value.disabledBtnTooltip}>
<Button type="outline" size="mini" disabled>
</Button>
</Tooltip>
);
} else {
return (
<Button type="outline" size="mini" onClick={() => emit('handleReauthorize', props.item)}>
{isUnauthorized ? '去授权' : '重新授权'}
</Button>
);
}
};
return (
<>
<Dropdown
trigger="hover"
v-slots={{
default: () => (
<Button type="outline" class="mr-8px" size="mini">
</Button>
),
content: () => (
<>
{renderEditDoption()}
{isMissing && renderReauthorizeDoption()}
{renderPauseDoption()}
{renderDeleteDoption()}
</>
),
}}
></Dropdown>
{renderSyncBtn()}
</>
);
};
const renderContent = () => {
const { status } = props.item;
if (status === EnumStatus.NORMAL) {
return renderNormal();
} else if (status === EnumStatus.PAUSE) {
return renderPause();
} else {
return renderAbnormal();
}
};
return () => renderContent();
},
});

View File

@ -24,7 +24,7 @@
</a-tooltip>
<div class="field-row">
<span class="label">状态</span>
<StatusBox :status="item.status" />
<StatusBox :item="item" />
</div>
<div class="field-row">
<span class="label">数据更新时间</span>
@ -75,24 +75,14 @@
</div>
</div>
<div class="operate-row">
<a-dropdown trigger="hover">
<a-button class="w-52px search-btn mr-8px" size="mini">
<template #default>更多</template>
</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
>
<a-doption class="color-#F64B31" @click="openDelete(item)">删除</a-doption>
</template>
<a-button class="search-btn" size="mini" @click="onBtnClick(item)">
<template #default>{{ getBtnText(item) }}</template>
</a-button>
</a-dropdown>
<FooterBtn
:item="item"
@handleReauthorize="handleReauthorize"
@syncData="syncData"
@openEdit="openEdit"
@openDelete="openDelete"
@handlePause="handlePause"
/>
</div>
</div>
<div v-if="isSyncFailed(item)" class="mask">
@ -115,14 +105,15 @@
<script setup>
import { defineProps, ref, computed, inject } from 'vue';
import { useRouter } from 'vue-router';
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
import { EnumErrorStatus } from '@/views/property-marketing/media-account/components/status-select/status-box';
import { deleteSyncStatus } from '@/api/all/propertyMarketing';
import { exactFormatTime } from '@/utils/tools';
import PauseAccountPatchModal from './pause-account-patch';
import StatusBox from '../status-box';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
import ReauthorizeAccountModal from '../reauthorize-account-modal';
import AuthorizedAccountModal from '../authorized-account-modal';
import FooterBtn from './footer-btn';
import icon1 from '@/assets/img/media-account/icon-dy.png';
import icon2 from '@/assets/img/media-account/icon-xhs.png';
@ -186,8 +177,8 @@ const openDelete = (item) => {
};
const handleReauthorize = (item) => {
const { id, platform, status } = item;
const isUnauthorized = isUnauthorizedStatus(status);
const { id, platform, error_status } = item;
const isUnauthorized = isUnauthorizedStatus(error_status);
if (isUnauthorized) {
authorizedAccountModalRef.value?.open(id, platform);
} else {
@ -199,66 +190,12 @@ const handlePause = (item) => {
pauseAccountPatchModalRef.value?.open(item);
};
const showPauseButton = (status) => {
return ![EnumStatus.PAUSE, EnumStatus.UNAUTHORIZED].includes(status);
const isUnauthorizedStatus = (error_status) => {
return [EnumErrorStatus.UNAUTHORIZED].includes(error_status);
};
const showSyncDataButton = (status) => {
return [EnumStatus.NORMAL, EnumStatus.ABNORMAL_MISSING].includes(status);
};
const isUnauthorizedStatus = (status) => {
return [EnumStatus.UNAUTHORIZED].includes(status);
};
// 三种异常情况
const isAbnormalStatus = (status) => {
return [
EnumStatus.ABNORMAL,
EnumStatus.ABNORMAL_LOGIN,
EnumStatus.ABNORMAL_REQUEST,
EnumStatus.ABNORMAL_FREEZE,
].includes(status);
};
// const getTooltipText = (status) => {
// return STATUS_LIST.find((v) => v.value === status)?.tooltip ?? '-';
// };
const syncData = inject('handleSyncData');
const onBtnClick = (item) => {
if (showSyncDataButton(item.status)) {
syncData(item);
return;
}
if (isUnauthorizedStatus(item.status)) {
handleReauthorize(item);
return;
}
if ([EnumStatus.PAUSE].includes(item.status) || isAbnormalStatus(item.status)) {
handleReauthorize(item);
return;
}
handlePause(item);
};
const getBtnText = (item) => {
if (showSyncDataButton(item.status)) {
return '更新数据';
}
if (isUnauthorizedStatus(item.status)) {
return '去授权';
}
if ([EnumStatus.PAUSE].includes(item.status) || isAbnormalStatus(item.status)) {
return '重新授权';
}
return '暂停同步';
};
const goDetail = (item) => {
router.push(`/media-account/detail/${item.id}`);
};

View File

@ -23,7 +23,7 @@ import TagSelect from '@/views/property-marketing/media-account/components/tag-s
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import AuthorizedAccountModal from '../authorized-account-modal';
// import ImportPromptModal from '../import-prompt-modal';
import StatusBox from '../status-box';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
import SyncDataModal from '../sync-data-modal';
// import { downloadByUrl } from '@/utils/tools';
@ -341,7 +341,7 @@ export default {
<Input v-model={form.value.account_id} placeholder="请输入..." size="large" disabled />
</FormItem>
<FormItem label="状态" field="status">
<StatusBox status={form.value.status} />
<StatusBox item={form.value} />
</FormItem>
</>
)}

View File

@ -1,97 +0,0 @@
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 15:31:15
-->
<template>
<div class="status-box" :class="`status-box-${status}`">
<span class="text">{{ statusText }}</span>
<a-tooltip v-if="showTooltip" :content="tooltipText">
<img v-if="showIcon" :src="iconSrc" width="12" height="12" class="ml-4px" />
</a-tooltip>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
import iconWarn1 from '@/assets/img/media-account/icon-warn-1.png';
import iconWarn2 from '@/assets/img/media-account/icon-warn-2.png';
const props = defineProps({
status: {
type: Number,
required: true,
},
});
const statusText = computed(() => {
return STATUS_LIST.find((v) => v.value === props.status)?.label ?? '-';
});
const showTooltip = computed(() => {
return isDisabledReauthorize(props.status);
});
const tooltipText = computed(() => {
return STATUS_LIST.find((v) => v.value === props.status)?.tooltip ?? '-';
});
const showIcon = computed(() => {
return ![EnumStatus.NORMAL, EnumStatus.UNAUTHORIZED].includes(props.status);
});
const iconSrc = computed(() => {
return props.status === EnumStatus.PAUSE ? iconWarn1 : iconWarn2;
});
// 判断是否为禁用重新授权的状态
const isDisabledReauthorize = (status) => {
return [EnumStatus.ABNORMAL_LOGIN, EnumStatus.ABNORMAL_REQUEST, EnumStatus.ABNORMAL_FREEZE].includes(status);
};
</script>
<style scoped lang="scss">
.status-box {
display: flex;
padding: 0px 8px;
align-items: center;
border-radius: 2px;
background: #f2f3f5;
.text {
color: var(--BG-700, #737478);
font-family: $font-family-medium;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
}
&-1 {
background: #ebf7f2;
.text {
color: #25c883;
}
}
&-2,
&-4,
&-5,
&-6,
&-7 {
background: #ffe7e4;
.text {
color: #f64b31;
}
}
&-3 {
background: #fff7e5;
color: #ffae00;
.text {
color: #ffae00;
}
}
}
</style>

View File

@ -132,8 +132,8 @@ import BatchGroupModal from './components/batch-group-modal';
import { INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
import { showImportResultNotification } from '@/utils/arcoD';
import { EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
import { getTaskStatus } from '@/api/all/common';
import { EnumStatus } from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
import {
getMediaAccounts,
getMediaAccountsHealth,
@ -168,7 +168,7 @@ const healthData = ref({});
const syncMediaAccounts = ref([]);
const isLoadingTaskStatus = ref(false); // 正在查询状态中
const isAbNormalStatus = computed(() => healthData.value?.total_abnormal_number > 0);
const isAbNormalStatus = computed(() => healthData.value?.abnormal_number > 0);
const isDisabledBatchSyncData = computed(() => selectedItems.value.some((item) => item.status !== EnumStatus.NORMAL));
const checkedAll = computed(() => selectedItems.value.length === dataSource.value.length);
@ -182,12 +182,11 @@ const tipLabel = computed(() => {
}
const {
total_abnormal_number = 0,
abnormal_number = 0,
login_invalid_number = 0,
too_many_requests_number = 0,
account_frozen_number = 0,
miss_data_number = 0,
abnormal_number = 0,
} = healthData.value;
// 定义异常类型映射
@ -202,7 +201,7 @@ const tipLabel = computed(() => {
// 过滤出有异常的项并格式化
const abnormalLabels = abnormalTypes.filter(({ count }) => count > 0).map(({ count, label }) => `${count}${label}`);
return `共有 ${total_abnormal_number} 个账号存在授权异常,其中:${abnormalLabels.join('')}`;
return `共有 ${abnormal_number} 个账号存在授权异常,其中:${abnormalLabels.join('')}`;
});
const getData = () => {

View File

@ -3,58 +3,74 @@
* @Date: 2025-07-04 11:18:11
*/
export enum EnumStatus {
UNAUTHORIZED = 0,
NORMAL = 1,
ABNORMAL = 3,
PAUSE = 2,
ABNORMAL_LOGIN = 4,
ABNORMAL_REQUEST = 5,
ABNORMAL_FREEZE = 6,
ABNORMAL_MISSING = 7,
UNAUTHORIZED = 0, // 未授权
NORMAL = 1, // 正常
PAUSE = 2, // 暂停同步
ABNORMAL = 3, // 全部异常
ABNORMAL_LOGIN = 4, // 异常(登录状态失效)
ABNORMAL_REQUEST = 5, // 异常(请求频繁)
ABNORMAL_FREEZE = 6, // 异常(账号被封)
ABNORMAL_MISSING = 7, // 异常(数据缺失)
ABNORMAL_EXPIRED = 8, // 即将过期
}
export const STATUS_LIST = [
{
text: '正常',
label: '正常',
value: EnumStatus.NORMAL,
},
{
text: '即将过期',
label: '即将过期',
value: EnumStatus.ABNORMAL_EXPIRED,
},
{
text: '暂停同步',
label: '暂停同步',
value: EnumStatus.PAUSE,
},
{
text: '未授权',
label: '未授权',
value: EnumStatus.UNAUTHORIZED,
},
{
text: '异常',
text: '全部异常',
label: '异常',
value: EnumStatus.ABNORMAL,
},
{
text: '数据缺失',
label: '数据缺失',
value: EnumStatus.ABNORMAL_MISSING,
text: '异常(未授权)',
label: '异常',
value: EnumStatus.UNAUTHORIZED,
tooltip: '未授权',
class: '!pl-24px',
},
{
text: '异常-登录状态失效',
text: '异常(数据缺失)',
label: '异常',
value: EnumStatus.ABNORMAL_MISSING,
tooltip: '数据缺失',
class: '!pl-24px',
},
{
text: '异常(登录状态失效)',
label: '异常',
value: EnumStatus.ABNORMAL_LOGIN,
tooltip: '登录状态失效,需重新扫码授权',
tooltip: '登录状态失效',
class: '!pl-24px',
},
{
text: '异常-请求过于频繁',
text: '异常请求频繁',
label: '异常',
value: EnumStatus.ABNORMAL_REQUEST,
tooltip: '请求过于频繁,等待24小时后重试',
tooltip: '请求频繁等待24小时后重试',
class: '!pl-24px',
},
{
text: '异常-账号被冻结/封禁',
text: '异常账号被封)',
label: '异常',
value: EnumStatus.ABNORMAL_FREEZE,
tooltip: '账号被冻结/封禁',
tooltip: '账号被',
class: '!pl-24px',
},
];

View File

@ -11,7 +11,7 @@
allow-clear
@change="handleChange"
>
<a-option v-for="(item, index) in STATUS_LIST" :key="index" :value="item.value" :label="item.text">
<a-option v-for="(item, index) in STATUS_LIST" :key="index" :value="item.value" :label="item.text" :class="item.class">
{{ item.text }}
</a-option>
</a-select>

View File

@ -0,0 +1,147 @@
import { defineComponent, computed } from 'vue';
import { Tooltip } from '@arco-design/web-vue';
// import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
import iconWarn1 from '@/assets/img/media-account/icon-warn-1.png';
import iconWarn2 from '@/assets/img/media-account/icon-warn-2.png';
import icon1 from '@/assets/img/media-account/icon-schedule.png';
export enum EnumErrorStatus {
NORMAL = 0, // 正常
UNAUTHORIZED = 1, // 未授权
LOGIN = 2, // 登录状态失效
REQUEST = 3, // 请求过于频繁
FREEZE = 4, // 账号被冻结/封禁
MISSING = 5, // 数据缺失
UNKNOWN = 6, // 未知异常
}
export enum EnumStatus {
ABNORMAL = 0, // 异常
NORMAL = 1, // 正常
PAUSE = 2, // 暂停同步
}
export enum EnumExpireForCookie {
NORMAL = 0, // 正常
EXPIRE = 1, // 即将过期
}
const normalStyle = {
color: '#25c883',
background: '#ebf7f2',
};
const abnormalStyle = {
color: '#f64b31',
background: '#ffe7e4',
};
const pauseStyle = {
color: '#ffae00',
background: '#fff7e5',
};
const tooltipMap = new Map([
[1, { tooltip: '未授权' }],
[2, { tooltip: '登录状态失效' }],
[3, { tooltip: '请求频繁等待24小时后重试', btnTooltip: '请求频繁等待24小时后重试' }],
[4, { tooltip: '账号被冻结', btnTooltip: '账号被封,解封后才能操作' }],
[5, { tooltip: '数据缺失' }],
[6, { tooltip: '未知错误' }],
]);
/**
*
* @param status 状态 0-异常1-正常2-暂停同步
* @param error_status 异常状态 0-无异常1-未授权2-登录状态失效3-请求过于频繁4-账号被冻结/封禁5-数据缺失6-未知错误
* @param to_be_expire_for_cookie cookie是否即将过期 0-否1-是
* @returns
*/
export const getStatusInfo = (status: EnumStatus, error_status: EnumErrorStatus, to_be_expire_for_cookie: EnumExpireForCookie) => {
const statusInfo = { color: '', background: '', label: '', tooltip: '', disabledBtnTooltip: '' };
if (status === EnumStatus.ABNORMAL) {
statusInfo.color = abnormalStyle.color;
statusInfo.background = abnormalStyle.background;
statusInfo.label = '异常';
const target = tooltipMap.get(error_status);
statusInfo.tooltip = target?.tooltip ?? '';
statusInfo.disabledBtnTooltip = target?.btnTooltip ?? '';
}
if (status === EnumStatus.NORMAL) {
if (to_be_expire_for_cookie === EnumExpireForCookie.NORMAL) {
statusInfo.color = normalStyle.color;
statusInfo.background = normalStyle.background;
statusInfo.label = '正常';
} else {
statusInfo.color = abnormalStyle.color;
statusInfo.background = abnormalStyle.background;
statusInfo.label = '即将过期';
}
}
if (status === EnumStatus.PAUSE) {
statusInfo.color = pauseStyle.color;
statusInfo.background = pauseStyle.background;
statusInfo.label = '暂停同步';
statusInfo.tooltip = '暂停同步';
}
return statusInfo;
};
export default defineComponent({
name: 'StatusBox',
props: {
item: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const statusInfo = computed(() => {
const { status, error_status, to_be_expire_for_cookie } = props.item;
return getStatusInfo(status, error_status, to_be_expire_for_cookie) ?? {};
});
const renderContent = () => {
const { to_be_expire_for_cookie, status } = props.item;
const { background, color, label } = statusInfo.value;
if (status === EnumStatus.NORMAL) {
return (
<div class="flex items-center">
{to_be_expire_for_cookie === EnumExpireForCookie.EXPIRE && (
<div class="flex items-center rounded-2px px-8px mr-8px" style={{ background, color }}>
<img src={icon1} width="12" height="12" class="mr-4px" />
<span class="text-12px lh-20px font-400">{label}</span>
</div>
)}
<div
class="flex items-center rounded-2px px-8px"
style={{ background: normalStyle.background, color: normalStyle.color }}
>
<span class="text-12px lh-20px font-400"></span>
</div>
</div>
);
}
return (
<div class="flex items-center rounded-2px px-8px" style={{ background, color }}>
<span class="text-12px lh-20px font-400">{label}</span>
{status === EnumStatus.PAUSE ? (
<img src={iconWarn1} width="12" height="12" class="ml-4px" />
) : (
<Tooltip content={statusInfo.value.tooltip}>
<img src={iconWarn2} width="12" height="12" class="ml-4px" />
</Tooltip>
)}
</div>
);
};
return () => renderContent();
},
});

View File

@ -75,7 +75,7 @@ const isDisabledReauthorize = (status) => {
}
}
&-2,
&-3,
&-4,
&-5,
&-6 {
@ -85,7 +85,7 @@ const isDisabledReauthorize = (status) => {
}
}
&-3 {
&-2{
background: #fff7e5;
color: #ffae00;
.text {