feat: 账号健康状态、统一组件逻辑

This commit is contained in:
rd
2025-07-04 14:05:01 +08:00
parent 954e4bc308
commit 1d52fda0cd
37 changed files with 1362 additions and 571 deletions

View File

@ -2,152 +2,177 @@
* @Author: RenXiaoDong
* @Date: 2025-06-28 10:33:06
*/
export const TABLE_COLUMNS = [
{
title: '账号名称',
dataIndex: 'name',
width: 180,
fixed: 'left',
},
{
title: '项目分组',
dataIndex: 'group.name',
width: 180,
fixed: 'left',
},
{
title: '状态',
dataIndex: 'status',
width: 180,
fixed: 'left',
},
{
title: '运营人员',
dataIndex: 'operator.name',
width: 180,
},
{
title: 'AI评价',
dataIndex: 'ai_evaluation',
width: 260,
},
{
title: '粉丝量',
dataIndex: 'fans_number',
width: 180,
tooltip: '账号当前粉丝总数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
export const getDefaultColumns = (type = 'week') => {
const isWeek = type === 'week';
const viewChain = isWeek ? 'week_view_chain' : 'month_view_chain';
const likeChain = isWeek ? 'week_like_chain' : 'month_like_chain';
const viewChainText = isWeek ? '近7天观看量环比' : '近30天观看量环比';
const likeChainText = isWeek ? '近7天点赞量环比' : '近30天点赞量环比';
return [
{
title: '账号名称',
dataIndex: 'name',
prop: 'name',
width: 180,
fixed: 'left',
},
},
{
title: '总赞藏数',
dataIndex: 'like_collect_number',
width: 180,
tooltip: '账号所有内容的点赞和收藏总数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '项目分组',
dataIndex: 'group.name',
prop: 'group',
width: 180,
fixed: 'left',
},
},
{
title: '观看量',
dataIndex: 'view_number',
width: 180,
tooltip: '账号所有内容的总观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '状态',
dataIndex: 'status',
prop: 'status',
width: 180,
fixed: 'left',
},
},
{
title: '观看量环比',
dataIndex: 'view_chain',
width: 180,
tooltip: '相比上一周期的观看量变化百分比',
align: 'right',
suffix: '%',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '运营人员',
dataIndex: 'operator.name',
prop: 'operator',
width: 180,
},
},
{
title: '点赞量',
dataIndex: 'like_number',
width: 180,
tooltip: '账号所有内容的总点赞数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: 'AI评价',
dataIndex: 'ai_evaluate',
prop: 'ai_evaluate',
width: 260,
},
},
{
title: '点赞量环比',
dataIndex: 'like_chain',
width: 180,
tooltip: '相比上一周期的点赞量变化百分比',
align: 'right',
suffix: '%',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '粉丝量',
dataIndex: 'fans_number',
prop: 'fans_number',
width: 180,
tooltip: '账号当前粉丝总数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
},
{
title: '最新内容标题/日期',
dataIndex: 'like_chain1',
width: 240,
tooltip: '最新发布内容的标题和发布日期',
},
{
title: '最新作品观看数',
dataIndex: 'latest_view_number',
width: 180,
tooltip: '最新发布内容的观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '总赞藏数',
dataIndex: 'like_collect_number',
prop: 'like_collect_number',
width: 180,
tooltip: '账号所有内容的点赞和收藏总数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
},
{
title: '最新作品日增长',
dataIndex: 'latest_daily_growth',
width: 180,
tooltip: '最新作品每日观看量的增长情况',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '观看量',
dataIndex: 'view_number',
prop: 'view_number',
width: 180,
tooltip: '账号所有内容的总观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
},
{
title: '次新内容标题/日期',
dataIndex: 'like_chain4',
width: 240,
},
{
title: '次新作品观看数',
dataIndex: 'second_latest_view_number',
width: 180,
tooltip: '倒数第二个发布内容的观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: viewChainText,
dataIndex: viewChain,
prop: viewChain,
width: 180,
tooltip: '相比上一周期的观看量变化百分比',
align: 'right',
suffix: '%',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
},
{
title: '次新作品日增长',
dataIndex: 'second_latest_daily_growth',
width: 180,
tooltip: '倒数第二个作品每日观看量的增长情况',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
{
title: '点赞量',
dataIndex: 'like_number',
prop: 'like_number',
width: 180,
tooltip: '账号所有内容的总点赞数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
},
{
title: '操作',
dataIndex: 'operation',
width: 100,
fixed: 'right',
},
];
{
title: likeChainText,
dataIndex: likeChain,
prop: likeChain,
width: 180,
tooltip: '相比上一周期的点赞量变化百分比',
align: 'right',
suffix: '%',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '最新内容标题/日期',
dataIndex: 'newest_work_title_and_publish_time',
prop: 'newest_work_title_and_publish_time',
width: 240,
tooltip: '最新发布内容的标题和发布日期',
},
{
title: '最新作品观看数',
dataIndex: 'newest_work_view_number',
prop: 'newest_work_view_number',
width: 180,
tooltip: '最新发布内容的观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '最新作品日增长',
dataIndex: 'newest_work_view_grow_number',
prop: 'newest_work_view_grow_number',
width: 180,
tooltip: '最新作品每日观看量的增长情况',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '次新内容标题/日期',
dataIndex: 'second_new_work_title_and_publish_time',
prop: 'second_new_work_title_and_publish_time',
width: 240,
},
{
title: '次新作品观看数',
dataIndex: 'second_new_work_view_number',
prop: 'second_new_work_view_number',
width: 180,
tooltip: '倒数第二个发布内容的观看次数',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '次新作品日增长',
dataIndex: 'second_new_work_view_grow_number',
prop: 'second_new_work_view_grow_number',
width: 180,
tooltip: '倒数第二个作品每日观看量的增长情况',
align: 'right',
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
{
title: '操作',
dataIndex: 'operation',
width: 100,
fixed: 'right',
},
];
};

View File

@ -47,7 +47,7 @@
</template>
<template #columns>
<a-table-column
v-for="column in TABLE_COLUMNS"
v-for="column in tableColumns"
:key="column.dataIndex"
:data-index="column.dataIndex"
:fixed="column.fixed"
@ -60,7 +60,7 @@
>
<template #title>
<div class="flex items-center">
<img v-if="column.dataIndex === 'ai_evaluation'" width="16" height="16" :src="icon5" class="mr-4px" />
<img v-if="column.dataIndex === 'ai_evaluate'" width="16" height="16" :src="icon5" class="mr-4px" />
<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" />
@ -78,18 +78,18 @@
}}</span>
</div>
</template>
<template v-else-if="column.dataIndex === 'ai_evaluation'" #cell="{ record }">
<template v-else-if="column.dataIndex === 'ai_evaluate'" #cell="{ record }">
<div class="ai-evaluation-row flex">
<img
width="16"
height="16"
:src="record.ai_evaluation?.status === 1 ? icon4 : record.ai_evaluation?.status === 2 ? icon3 : icon2"
:src="record.ai_evaluate?.status === 1 ? icon4 : record.ai_evaluate?.status === 2 ? icon3 : icon2"
class="mr-8px icon"
/>
<div>
<p class="cts">{{ record.ai_evaluation?.text }}</p>
<p class="cts">{{ record.ai_evaluate?.advise }}</p>
<p class="cts text-12px lh-20px !color-#939499">
{{ `观看: ${record.ai_evaluation?.look_chain}% 点赞: ${record.ai_evaluation?.like_chain}%` }}
{{ `观看: ${record.week_view_chain}% 点赞: ${record.week_like_chain}%` }}
</p>
</div>
</div>
@ -101,14 +101,24 @@
<a-button type="outline" size="small" class="search-btn" @click="handleDetail(record)">详情</a-button>
</template>
<template v-else-if="['view_chain', 'like_chain'].includes(column.dataIndex)" #cell="{ record }">
<template
v-else-if="
['week_view_chain', 'month_view_chain', 'week_like_chain', 'month_like_chain'].includes(column.dataIndex)
"
#cell="{ record }"
>
<div class="flex items-center rate-row justify-end" :class="record[column.dataIndex] > 0 ? 'up' : 'down'">
<icon-arrow-up v-if="record[column.dataIndex] > 0" size="16" />
<icon-arrow-down v-else size="16" />
{{ formatTableField(column, record) }}
</div>
</template>
<template v-else-if="['like_chain1', 'like_chain4'].includes(column.dataIndex)" #cell="{ record }">
<template
v-else-if="
['newest_work_title_and_publish_time', 'second_new_work_title_and_publish_time'].includes(column.dataIndex)
"
#cell="{ record }"
>
<p class="cts cursor-pointer hover:!color-#6D4CFE">打工人的环游世界旅行计划(国内版)</p>
<p class="cts text-12px lh-20px !color-#939499">2025-06-18</p>
</template>
@ -120,16 +130,17 @@
</template>
</a-table>
<CustomTableColumnModal ref="customTableColumnModalRef" type="media_account" @success="onCustomColumnSuccess" />
<CustomTableColumnModal ref="customTableColumnModalRef" :type="CUSTOM_COLUMN_TYPE" @success="onCustomColumnSuccess" />
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { STATUS_LIST } from '../../constants';
import { getCustomColumns } from '@/api/all/common';
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
import { formatTableField, formatNumberShow } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import { getDefaultColumns } from './constants';
import CustomTableColumnModal from '@/components/custom-table-column-modal';
import icon1 from '@/assets/img/media-account/icon-custom.png';
@ -147,11 +158,14 @@ const props = defineProps({
const emit = defineEmits(['selectionChange', 'sorterChange', 'export']);
const CUSTOM_COLUMN_TYPE = 'media_account';
const router = useRouter();
const selectedItems = ref([]);
const tableRef = ref(null);
const customTableColumnModalRef = ref(null);
const selectedColumns = ref([]);
const checkedAll = computed(
() => selectedItems.value.length > 0 && selectedItems.value.length === props.dataSource.length,
@ -165,6 +179,18 @@ const rowSelection = computed(() => ({
showCheckedAll: true,
}));
const tableColumns = computed(() => {
const _result = [];
const _columns = getDefaultColumns();
selectedColumns.value.forEach((item) => {
const _column = _columns.find((_item) => _item.prop === item);
if (_column) {
_result.push(_column);
}
});
return _result;
});
const handleSelectAll = (checked) => {
if (checked) {
selectedItems.value = props.dataSource.map((item) => item.id);
@ -201,13 +227,26 @@ const openCustomColumn = () => {
customTableColumnModalRef.value.open();
};
const onCustomColumnSuccess = (selectedColumns) => {
console.log(selectedColumns);
const onCustomColumnSuccess = (columns) => {
selectedColumns.value = columns;
};
const getSelectedColumns = () => {
getCustomColumns({ type: CUSTOM_COLUMN_TYPE }).then((res) => {
const { code, data } = res;
if (code === 200) {
selectedColumns.value = data.selected_columns;
}
});
};
defineExpose({
resetTable,
});
onMounted(() => {
getSelectedColumns();
});
</script>
<style scoped lang="scss">

View File

@ -19,27 +19,19 @@
<div class="filter-row-item flex items-center">
<span class="label">分组</span>
<a-space class="w-200px">
<group-select v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
<GroupSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">状态</span>
<a-space class="w-180px">
<a-select v-model="query.status" size="medium" placeholder="全部" allow-clear @change="handleSearch">
<a-option v-for="(item, index) in STATUS_LIST" :key="index" :value="item.value" :label="item.text">{{
item.text
}}</a-option>
</a-select>
<StatusSelect v-model="query.status" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<a-select v-model="query.operator_id" size="medium" placeholder="全部" allow-clear @change="handleSearch">
<a-option v-for="(item, index) in operators" :key="index" :value="item.id" :label="item.name">{{
item.name
}}</a-option>
</a-select>
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
</a-space>
</div>
@ -62,8 +54,9 @@
<script setup>
import { reactive, defineEmits, defineProps } from 'vue';
import { fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
import GroupSelect from '../group-select/index.vue';
import { STATUS_LIST } from '@/views/property-marketing/media-account/account-dashboard/constants';
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
const props = defineProps({
query: {
@ -100,8 +93,8 @@ const getOperators = async () => {
};
onMounted(() => {
// getGroups();
// getOperators();
getGroups();
getOperators();
});
</script>

View File

@ -1,64 +0,0 @@
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 14:02:40
-->
<template>
<a-select
v-model="selectedGroups"
:multiple="multiple"
size="medium"
:placeholder="placeholder"
allow-clear
@change="handleChange"
>
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
{{ item.name }}
</a-option>
</a-select>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
modelValue: {
type: [Array, String, Number],
default: () => [],
},
multiple: {
type: Boolean,
default: true,
},
placeholder: {
type: String,
default: '全部',
},
options: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(['update:modelValue', 'change']);
const selectedGroups = ref(props.multiple ? [] : '');
// 监听外部传入的值变化
watch(
() => props.modelValue,
(newVal) => {
selectedGroups.value = newVal;
},
{ immediate: true },
);
// 监听内部值变化,向外部发送更新
watch(selectedGroups, (newVal) => {
emits('update:modelValue', newVal);
});
const handleChange = (value) => {
selectedGroups.value = value;
emits('change', value);
};
</script>

View File

@ -43,54 +43,3 @@ export const INITIAL_QUERY = {
column: '',
order: '',
};
export enum EnumStatus {
NORMAL = 1,
PAUSE = 3,
UNAUTHORIZED = 0,
ABNORMAL = 2,
ABNORMAL_LOGIN = 4,
ABNORMAL_REQUEST = 5,
ABNORMAL_FREEZE = 6,
}
export const STATUS_LIST = [
{
text: '正常',
label: '正常',
value: EnumStatus.NORMAL,
},
{
text: '暂停同步',
label: '暂停同步',
value: EnumStatus.PAUSE,
},
{
text: '未授权',
label: '未授权',
value: EnumStatus.UNAUTHORIZED,
},
{
text: '异常',
label: '异常',
value: EnumStatus.ABNORMAL,
},
{
text: '异常-登录状态失效',
label: '异常',
value: EnumStatus.ABNORMAL_LOGIN,
tooltip: '登录状态失效,需重新扫码授权',
},
{
text: '异常-请求过于频繁',
label: '异常',
value: EnumStatus.ABNORMAL_REQUEST,
tooltip: '请求过于频繁需等待24小时后重试',
},
{
text: '异常-账号被冻结/封禁',
label: '异常',
value: EnumStatus.ABNORMAL_FREEZE,
tooltip: '账号被冻结/封禁',
},
];