feat: 数据看板

This commit is contained in:
rd
2025-06-27 18:37:42 +08:00
parent 6a8cf54ccb
commit 80c6b30701
18 changed files with 907 additions and 7 deletions

View File

@ -0,0 +1,131 @@
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-27 18:08:04
-->
<template>
<div class="container">
<div class="action-row mb-12px flex justify-between">
<a-checkbox :model-value="checkedAll" :indeterminate="indeterminate" @change="handleChangeAll" />
<div class="flex items-center">
<a-button class="w-110px search-btn mr-12px" size="medium">
<template #icon> <icon-download /> </template>
<template #default>导出数据</template>
</a-button>
<a-button class="w-110px search-btn" size="medium">
<template #icon>
<img :src="icon1" width="14" height="14" />
</template>
<template #default>自定义列</template>
</a-button>
</div>
</div>
<a-table
:data="dataSource"
row-key="id"
:row-selection="rowSelection"
:pagination="false"
class="custom-table w-100%"
bordered
>
<template #columns>
<a-table-column title="账号名称" data-index="name" />
<a-table-column title="手机号" data-index="mobile" />
<a-table-column title="账号ID" data-index="account_id" />
<a-table-column title="持有人" data-index="holder_name" />
<a-table-column title="平台" data-index="platform">
<template #cell="{ record }">
{{ record.platform === 0 ? '抖音' : record.platform === 1 ? '小红书' : '-' }}
</template>
</a-table-column>
<a-table-column title="分组" data-index="group.name" />
<a-table-column title="运营人员" data-index="operator.name" />
<a-table-column title="粉丝数" data-index="fans_number" />
<a-table-column title="点赞数" data-index="like_number" />
<a-table-column title="收藏数" data-index="collect_number" />
<a-table-column title="观看数" data-index="view_number" />
<a-table-column title="观看环比" data-index="view_chain">
<template #cell="{ record }"> {{ record.view_chain }}% </template>
</a-table-column>
<a-table-column title="点赞环比" data-index="like_chain">
<template #cell="{ record }"> {{ record.like_chain }}% </template>
</a-table-column>
<a-table-column title="状态" data-index="status">
<template #cell="{ record }">
<span v-if="record.status === 0">未授权</span>
<span v-else-if="record.status === 1">正常</span>
<span v-else-if="record.status === 2">异常</span>
<span v-else>-</span>
<span v-if="record.is_pause === 1" class="pause-tag">暂停</span>
</template>
</a-table-column>
<a-table-column title="操作" fixed="right" width="120">
<template #cell="{ record }">
<a-button type="text" size="small" @click="handleEdit(record)">详情</a-button>
</template>
</a-table-column>
</template>
</a-table>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import icon1 from '@/assets/img/media-account/icon-custom.png';
const props = defineProps({
dataSource: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['edit', 'delete', 'selectionChange']);
const selectedItems = ref([]);
const checkedAll = computed(
() => selectedItems.value.length > 0 && selectedItems.value.length === props.dataSource.length,
);
const indeterminate = computed(
() => selectedItems.value.length > 0 && selectedItems.value.length < props.dataSource.length,
);
const rowSelection = {
type: 'checkbox',
selectedRowKeys: selectedItems.value,
onChange: (selectedRowKeys, selectedRows) => {
selectedItems.value = selectedRowKeys;
emit('selectionChange', selectedRows);
},
};
const handleChangeAll = (checked) => {
if (checked) {
selectedItems.value = props.dataSource.map((item) => item.id);
} else {
selectedItems.value = [];
}
emit('selectionChange', checked ? props.dataSource : []);
};
const handleEdit = (record) => {
emit('edit', record);
};
const handleDelete = (record) => {
emit('delete', record);
};
</script>
<style scoped lang="scss">
@import './style.scss';
.custom-table {
:deep(.pause-tag) {
color: #f64b31;
margin-left: 4px;
font-size: 12px;
}
}
</style>

View File

@ -0,0 +1,10 @@
.container {
width: 100%;
.action-row {
:deep(.arco-btn) {
.arco-btn-icon {
line-height: 14px;
}
}
}
}

View File

@ -0,0 +1,129 @@
<!-- eslint-disable vue/no-mutating-props -->
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 14:02:40
-->
<template>
<div class="container">
<div class="filter-row flex mb-20px">
<div class="filter-row-item flex items-center">
<span class="label">账号名称</span>
<a-space size="medium" class="w-240px">
<a-input v-model="query.name" placeholder="请搜索..." size="medium" allow-clear @change="handleSearch">
<template #prefix>
<icon-search />
</template>
</a-input>
</a-space>
</div>
<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" />
</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>
</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>
</a-space>
</div>
</div>
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">时间筛选</span>
<a-space class="w-240px">
<a-select
v-model="query.date_range"
size="medium"
placeholder="全部"
class="w-120px"
allow-clear
@change="handleSearch"
>
<template #arrow-icon> <icon-calendar size="16" /> </template>
<a-option :value="7" label="近7天">近7天</a-option>
<a-option :value="14" label="近14天">近14天</a-option>
<a-option :value="30" label="近30天">近30天</a-option>
</a-select>
</a-space>
</div>
<a-button class="w-84px search-btn mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
</template>
<template #default>搜索</template>
</a-button>
<a-button class="w-84px reset-btn" size="medium" @click="handleReset">
<template #icon>
<icon-refresh />
</template>
<template #default>重置</template>
</a-button>
</div>
</div>
</template>
<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';
const props = defineProps({
query: {
type: Object,
required: true,
},
});
const emits = defineEmits('onSearch', 'onReset', 'update:query');
const tags = ref([]);
const groups = ref([]);
const operators = ref([]);
const handleSearch = () => {
emits('onSearch', props.query);
};
const handleReset = () => {
emits('onReset');
};
const getGroups = async () => {
const { code, data } = await fetchAccountGroups();
if (code === 200) {
groups.value = data;
}
};
const getOperators = async () => {
const { code, data } = await fetchAccountOperators();
if (code === 200) {
operators.value = data;
}
};
onMounted(() => {
// getGroups();
// getOperators();
});
</script>
<style scoped lang="scss">
@import './style.scss';
</style>

View File

@ -0,0 +1,36 @@
.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-item {
&:not(:last-child) {
margin-right: 24px;
}
.label {
margin-right: 8px;
color: #211f24;
font-family: 'Alibaba PuHuiTi';
font-size: 14px;
font-style: normal;
font-weight: 400;
flex-shrink: 0;
line-height: 22px; /* 157.143% */
}
:deep(.arco-space-item) {
width: 100%;
}
}
}
}

View File

@ -0,0 +1,64 @@
<!--
* @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>