Commit 034d0c1b authored by 黄同智's avatar 黄同智

广告账户分析、团队分析、直播管理等页面的交互和布局

parent 0983a8bd
......@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@vue-flow/additional-components": "^1.3.3",
......
......@@ -110,6 +110,11 @@ const menus = [
icon: 'redraw',
},
{
name: '直播管理',
path: '/zhibo',
icon: 'map',
},
{
name: '任务协作',
path: '/task',
icon: 'map',
......
......@@ -30,6 +30,19 @@ const routes = [
]
},
{
path: '/zhibo',
component: Layout,
children: [
{
path: 'zhibo',
alias: '',
name: 'Zhibo',
component: import('@/views/zhibo/index.vue'),
meta: { title: '直播管理', icon: 'House', affix: true }
}
]
},
{
path: '/task',
component: Layout,
children: [
......
<template>
<div>
<div class="bgmr vh100 over-y-auto">
<section class="bg-fff px-20 py-10 analysis-tabs">
<div class="flex-items-center gap-16">
<div
v-for="item in tabs"
:key="item.value"
class="px-12 py-8 round-6 font-bold pointer"
:class="activeTab === item.value ? 'bg-main color-fff' : 'color-000'"
@click="activeTab = item.value"
>
{{ item.label }}
</div>
</div>
</section>
<div class="p-16">
<component :is="currentComponent" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import Tab1 from './components/AD/Tab1.vue';
import Tab2 from './components/AD/Tab2.vue';
import Tab3 from './components/AD/Tab3.vue';
import Tab4 from './components/AD/Tab4.vue';
const activeTab = ref('tab1');
const tabs = [
{ label: '广告账户数据', value: 'tab1', component: Tab1 },
{ label: '广告账户财务报表', value: 'tab2', component: Tab2 },
{ label: '投放状态报表', value: 'tab3', component: Tab3 },
{ label: 'TikTok店铺', value: 'tab4', component: Tab4 },
];
const currentComponent = computed(() => tabs.find((item) => item.value === activeTab.value)?.component);
</script>
<style scoped lang="scss">
</style>
\ No newline at end of file
.analysis-tabs {
border-bottom: 1px solid #dfe7f2;
}
</style>
<template>
<div class="ad-report">
<el-card class="report-card round-12" shadow="never">
<div class="flex-between tab-row">
<div class="flex gap-30">
<button v-for="item in platformTabs" :key="item" class="text-tab"
:class="{ active: platform === item }" @click="platform = item">
{{ item }}
</button>
</div>
<div class="w-300">
<el-date-picker v-model="sForm.range" type="daterange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期" style="width: 100%;" />
</div>
</div>
<div class="mt-16 flex gap-10">
<el-radio-group v-model="metric" class="mr-10">
<el-radio-button v-for="(item, index) in metricTabs" :label="item" :value="item" :key="index" />
</el-radio-group>
<el-select v-model="sForm.department" placeholder="请选择部门" style="width: 200px">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="sForm.group" placeholder="请选择分组" style="width: 200px">
<el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="sForm.userId" placeholder="请选择账户" style="width: 200px">
<el-option v-for="item in options3" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="sForm.type" placeholder="请选择分类" style="width: 200px">
<el-option v-for="item in options4" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button @click="reset">重置</el-button>
</div>
</el-card>
<el-card class="report-card round-12 mt-16" shadow="never">
<div class="flex-between mb-16">
<el-radio-group v-model="promotion" class="mr-10">
<el-radio-button v-for="(item, index) in promotionTabs" :label="item" :value="item" :key="index" />
</el-radio-group>
<div class="flex-between gap-16">
<div class="toolbar-left">
<el-input v-model="sForm.advertiserId" class="w-170" placeholder="请输入广告主ID" clearable />
<el-input v-model="sForm.advertiserName" class="w-170" placeholder="请输入广告主名称" clearable />
<span class="muted">未绑定数据</span>
<el-button link type="primary" @click="openBind">点击前往</el-button>
<el-button @click="reset">重置</el-button>
</div>
<div class="toolbar-right">
<el-button @click="exportData">导出</el-button>
<el-button class="icon-button" @click="settingVisible = !settingVisible"></el-button>
</div>
</div>
</div>
<el-alert v-if="settingVisible" class="mb-10" title="已打开字段配置面板" type="info" show-icon :closable="false" />
<el-table v-if="metric === '直播间汇总'" :data="pagedRows" class="analysis-table" row-key="room">
<el-table-column fixed prop="room" label="直播间" min-width="200" show-overflow-tooltip />
<el-table-column prop="owner" label="负责人" width="130" align="center" />
<el-table-column prop="cost" label="消耗 ↑↓" width="140" align="center">
<template #default="{ row }"><span
:class="row.highlightCost ? 'color-main font-bold' : 'font-bold'">{{ row.cost
}}</span></template>
</el-table-column>
<el-table-column prop="roi" label="roi" width="110" align="center">
<template #default="{ row }"><span class="color-red font-bold">{{ row.roi }}</span></template>
</el-table-column>
<el-table-column prop="amount" label="成交金额" width="150" align="center" />
<el-table-column prop="coupon" label="智能优惠券" width="150" align="center" />
<el-table-column prop="conversion" label="转化数" width="120" align="center" />
<el-table-column prop="rate" label="转化率" width="120" align="center" />
<el-table-column prop="unitCost" label="转化成本" width="130" align="center" />
<el-table-column prop="show" label="展示数" width="150" align="center" />
</el-table>
<el-table v-else-if="metric === '人员数据'" :data="pagedRows" class="analysis-table" row-key="name">
<el-table-column fixed prop="name" label="人员名称" min-width="220">
<template #default="{ row }"><span :class="row.link ? 'link-text font-bold' : 'font-bold'">{{
row.name }}</span></template>
</el-table-column>
<el-table-column prop="accountCount" label="关联账户数" min-width="130" align="center" />
<el-table-column prop="cost" label="消耗" min-width="140" align="center">
<template #default="{ row }"><span
:class="row.highlightCost ? 'color-main font-bold' : 'font-bold'">{{ row.cost
}}</span></template>
</el-table-column>
<el-table-column prop="roi" label="roi" min-width="110" align="center">
<template #default="{ row }"><span class="color-red font-bold">{{ row.roi }}</span></template>
</el-table-column>
<el-table-column prop="amount" label="成交金额" min-width="150" align="center" />
<el-table-column prop="coupon" label="智能优惠券" min-width="150" align="center" />
<el-table-column prop="conversion" label="转化数" min-width="120" align="center" />
<el-table-column prop="rate" label="转化率" min-width="120" align="center" />
<el-table-column prop="unitCost" label="转化成本" min-width="130" align="center" />
</el-table>
<el-table v-else-if="metric === '分类数据'" :data="pagedRows" class="analysis-table" row-key="firstCategory">
<el-table-column fixed prop="firstCategory" label="一级分类" min-width="180" />
<el-table-column prop="secondCategory" label="二级分类" min-width="200" />
<el-table-column prop="accountCount" label="关联账户数" min-width="130" align="center" />
<el-table-column prop="cost" label="消耗" min-width="140" align="center">
<template #default="{ row }"><span
:class="row.highlightCost ? 'color-main font-bold' : 'font-bold'">{{ row.cost
}}</span></template>
</el-table-column>
<el-table-column prop="roi" label="roi" min-width="110" align="center">
<template #default="{ row }"><span class="color-red font-bold">{{ row.roi }}</span></template>
</el-table-column>
<el-table-column prop="amount" label="成交金额" min-width="150" align="center" />
<el-table-column prop="coupon" label="智能优惠券" min-width="150" align="center" />
<el-table-column prop="conversion" label="转化数" min-width="120" align="center" />
</el-table>
<el-table v-else :data="pagedRows" class="analysis-table" row-key="account">
<el-table-column fixed prop="account" label="广告账户" min-width="240">
<template #default="{ row }">
<div class="font-bold">{{ row.account }}</div>
<div v-if="row.id" class="fs-12 color-8aa">{{ row.id }}</div>
</template>
</el-table-column>
<el-table-column prop="department" label="部门" min-width="140">
<template #default="{ row }"><span :class="row.linkDepartment ? 'link-text' : ''">{{ row.department
}}</span></template>
</el-table-column>
<el-table-column prop="group" label="分组" min-width="140">
<template #default="{ row }"><span :class="row.linkGroup ? 'link-text' : ''">{{ row.group
}}</span></template>
</el-table-column>
<el-table-column prop="user" label="用户" min-width="120" />
<el-table-column prop="firstCategory" label="一级分类" min-width="150">
<template #default="{ row }"><span :class="row.linkCategory ? 'link-text' : ''">{{ row.firstCategory
}}</span></template>
</el-table-column>
<el-table-column prop="secondCategory" label="二级分类" min-width="150">
<template #default="{ row }"><span :class="row.linkCategory ? 'link-text' : ''">{{
row.secondCategory }}</span></template>
</el-table-column>
<el-table-column prop="cost" label="消耗 ↑↓" min-width="140" align="center">
<template #default="{ row }"><span
:class="row.highlightCost ? 'color-main font-bold' : 'font-bold'">{{ row.cost
}}</span></template>
</el-table-column>
<el-table-column prop="roi" label="roi" min-width="110" align="center">
<template #default="{ row }"><span class="color-red font-bold">{{ row.roi }}</span></template>
</el-table-column>
<el-table-column prop="amount" label="成交金额" min-width="150" align="center" />
<el-table-column prop="coupon" label="智能优惠券" min-width="150" align="center" />
</el-table>
<div class="table-footer">
<el-pagination v-model:current-page="pageData.currentPage" v-model:page-size="pageData.pageSize"
background layout="total, sizes, prev, pager, next, jumper" :total="currentTableRows.length"
:page-sizes="pageData.pageSizes" @size-change="handleSizeChange" />
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const platform = ref('巨量千川');
const promotion = ref('标准推广');
const metric = ref('直播间汇总');
const settingVisible = ref(false);
const pageData = reactive({
currentPage: 1,
pageSize: 50,
pageSizes: [10, 20, 50, 100],
});
const sForm = ref({
department: undefined,
group: undefined,
userId: undefined,
type: undefined,
range: undefined,
advertiserId: '',
advertiserName: ''
})
const platformTabs = ['巨量千川', '巨量广告', '巨量本地推', '磁力智投', '磁力金牛', '腾讯ADQ', 'TikTok', '百度营销', '淘宝超级短视频'];
const promotionTabs = ['标准推广', '直播全域推广', '商品全域推广'];
const metricTabs = ['直播间汇总', '人员数据', '分类数据', '广告主明细'];
const options1 = ref([
{ label: 'A部门', value: 1 },
{ label: 'B部门', value: 2 },
{ label: '外部部门', value: 3 },
{ label: '未绑定部门分组', value: 4 },
])
const options2 = ref([
{ label: '核心二组', value: 1 },
{ label: 'B部门', value: 2 },
{ label: '外部部门', value: 3 },
{ label: '未绑定部门分组', value: 4 },
])
const options3 = ref([
{ label: '张伟', value: 1 },
{ label: '瑶光', value: 2 },
{ label: '徐振', value: 3 },
])
const options4 = ref([
{ label: '美妆护肤', value: 1 },
{ label: '3C数码', value: 2 },
])
const liveRoomRows = [
{ room: '总计', owner: '-', cost: '406200.50', roi: '3.58', amount: '¥1,454,649.9', coupon: '¥90,400', conversion: '13,020', rate: '-', unitCost: '-', show: '8,250,000', highlightCost: true },
{ room: '未绑定直播间', owner: '-', cost: '0.00', roi: '0.00', amount: '¥0', coupon: '¥0', conversion: '0', rate: '-', unitCost: '-', show: '0' },
{ room: '直播间-美妆旗舰店爆品 (ID: 89021)', owner: '展展', cost: '185400.50', roi: '3.92', amount: '¥726,769.9', coupon: '¥42,100', conversion: '6,180', rate: '4.85%', unitCost: '30.00', show: '3,820,000' },
{ room: '直播间-3C数码科技专场 (ID: 89035)', owner: '李强', cost: '124000.00', roi: '3.45', amount: '¥427,800', coupon: '¥28,500', conversion: '3,950', rate: '4.12%', unitCost: '31.39', show: '2,450,000' },
{ room: '直播间-服装鞋包新品发布 (ID: 89048)', owner: '王丹', cost: '96800.00', roi: '3.10', amount: '¥300,080', coupon: '¥19,800', conversion: '2,890', rate: '3.80%', unitCost: '33.49', show: '1,980,000' },
];
const peopleRows = [
{ name: '总计', accountCount: '28', cost: '481500.50', roi: '3.62', amount: '¥1,740,760', coupon: '¥102,500', conversion: '15,380', rate: '-', unitCost: '-', highlightCost: true },
{ name: '张伟 (投放主管)', accountCount: '12 个', cost: '215000.00', roi: '3.88', amount: '¥834,200', coupon: '¥48,000', conversion: '6,980', rate: '4.65%', unitCost: '30.80', link: true },
{ name: '李娜 (高级优化师)', accountCount: '8 个', cost: '168000.50', roi: '3.52', amount: '¥591,360', coupon: '¥35,000', conversion: '5,320', rate: '4.30%', unitCost: '31.58', link: true },
{ name: '王磊 (主播运营)', accountCount: '6 个', cost: '98500.00', roi: '3.20', amount: '¥315,200', coupon: '¥19,500', conversion: '3,080', rate: '3.90%', unitCost: '31.98', link: true },
{ name: '未绑定人员账号', accountCount: '2 个', cost: '0.00', roi: '0.00', amount: '¥0', coupon: '¥0', conversion: '0', rate: '-', unitCost: '-', link: true },
];
const categoryRows = [
{ firstCategory: '总计', secondCategory: '-', accountCount: '32', cost: '476000.00', roi: '3.63', amount: '¥1,728,230', coupon: '¥96,500', conversion: '15,020', highlightCost: true },
{ firstCategory: '美妆护肤', secondCategory: '面部精华/霜', accountCount: '15 个', cost: '245000.00', roi: '3.95', amount: '¥967,750', coupon: '¥52,000', conversion: '7,850' },
{ firstCategory: '3C数码', secondCategory: '智能穿戴与耳机', accountCount: '9 个', cost: '142000.00', roi: '3.40', amount: '¥482,800', coupon: '¥28,000', conversion: '4,420' },
{ firstCategory: '个人护理', secondCategory: '身体洗护', accountCount: '6 个', cost: '89000.00', roi: '3.12', amount: '¥277,680', coupon: '¥16,500', conversion: '2,750' },
{ firstCategory: '未绑定分类', secondCategory: '未绑定分类', accountCount: '2 个', cost: '0.00', roi: '0.00', amount: '¥0', coupon: '¥0', conversion: '0' },
];
const advertiserRows = [
{ account: '总计', id: '', department: '-', group: '-', user: '-', firstCategory: '-', secondCategory: '-', cost: '292800.50', roi: '3.55', amount: '¥1,038,165.7', coupon: '¥63,400', highlightCost: true },
{ account: '直播-铃音-牧唐-芜湖1', id: '1787869271614468', department: '未绑定分组..', group: '未绑定分组', user: '-', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', cost: '0.00', roi: '0.00', amount: '¥0', coupon: '¥0', linkDepartment: true, linkGroup: true, linkCategory: true },
{ account: '直播-颜姿2-牧唐-芜湖', id: '1783526642794571', department: '未绑定分组..', group: '未绑定分组', user: '-', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', cost: '0.00', roi: '0.00', amount: '¥0', coupon: '¥0', linkDepartment: true, linkGroup: true, linkCategory: true },
{ account: '千川直通车-美妆03', id: '1792182049182310', department: 'A部门', group: '核心二组', user: '张伟', firstCategory: '美妆护肤', secondCategory: '精华面霜', cost: '142500.00', roi: '3.85', amount: '¥548,625', coupon: '¥31,200', linkDepartment: true, linkGroup: true, linkCategory: true },
{ account: '千川直播号-数码01', id: '1795123019823122', department: 'C部门', group: '管理层组', user: '李娜', firstCategory: '3C数码', secondCategory: '蓝牙耳机', cost: '98200.50', roi: '3.42', amount: '¥335,845.7', coupon: '¥21,000', linkDepartment: true, linkGroup: true, linkCategory: true },
{ account: '巨量信息流-洗护备用', id: '1781203912039102', department: '外部部门', group: '分组一', user: 'zs_test', firstCategory: '个人护理', secondCategory: '洗发护发', cost: '52100.00', roi: '2.95', amount: '¥153,695', coupon: '¥11,200', linkDepartment: true, linkGroup: true, linkCategory: true },
];
const currentTableRows = computed(() => {
if (metric.value === '人员数据') return peopleRows;
if (metric.value === '分类数据') return categoryRows;
if (metric.value === '广告主明细') return advertiserRows;
return liveRoomRows;
});
const pagedRows = computed(() => {
const start = (pageData.currentPage - 1) * pageData.pageSize;
return currentTableRows.value.slice(start, start + pageData.pageSize);
});
const reset = () => {
sForm.value.department = undefined
sForm.value.group = undefined
sForm.value.userId = undefined
sForm.value.type = undefined
sForm.value.range = undefined
sForm.value.advertiserId = ''
sForm.value.advertiserName = ''
};
const openBind = () => ElMessage.info('跳转到未绑定数据处理页');
const exportData = () => ElMessage.success('已生成导出任务');
const setMetric = (value: string) => {
metric.value = value;
pageData.currentPage = 1;
};
const handleSizeChange = () => {
pageData.currentPage = 1;
};
</script>
<style scoped lang="scss">
.ad-report {
color: #10233f;
}
.report-card {
border: 1px solid #dfe7f2;
}
.tab-row {
border-bottom: 1px solid #edf0f5;
}
.metric-tabs {
gap: 34px;
}
.text-tab {
height: 44px;
padding: 0;
border: 0;
border-bottom: 2px solid transparent;
background: transparent;
color: #3f5574;
cursor: pointer;
&.small {
height: 40px;
font-size: 13px;
}
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.toolbar-left,
.toolbar-right {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.w-150 {
width: 150px;
}
.w-170 {
width: 170px;
}
.muted {
color: #607086;
font-size: 13px;
}
.icon-button {
width: 34px;
padding: 0;
}
.color-main {
color: rgba(var(--main-color), 1);
}
.color-red {
color: #ff0055;
}
.scroll-bar {
height: 5px;
margin: 10px 0 14px;
border-radius: 999px;
background: #dfe7f2;
}
.table-footer {
display: flex;
justify-content: flex-end;
padding-top: 10px;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
</style>
<template>
<div class="finance-report">
<el-card class="report-card" shadow="never">
<div class="platform-tabs">
<button v-for="item in platforms" :key="item" class="text-tab" :class="{ active: platform === item }"
@click="platform = item">
{{ item }}
</button>
</div>
<div class="flex items-center gap-10 flex-wrap mt-16">
<el-input v-model="keyword" class="w-180" placeholder="请输入账户名称/ID" clearable />
<el-select v-model="department" class="w-150" placeholder="请选择部门">
<el-option label="A部门" value="A部门" />
<el-option label="未绑定部门" value="未绑定部门" />
</el-select>
<el-select v-model="group" class="w-150" placeholder="请选择分组">
<el-option label="核心一组" value="核心一组" />
<el-option label="未绑定分组" value="未绑定分组" />
</el-select>
<el-select v-model="account" class="w-150" placeholder="请选择账号">
<el-option label="张伟" value="张伟" />
<el-option label="未绑定账号" value="未绑定账号" />
</el-select>
<el-select v-model="category" class="w-150" placeholder="请选择分类">
<el-option label="美妆护肤" value="美妆护肤" />
<el-option label="洗面奶" value="洗面奶" />
</el-select>
<div class="">
<el-date-picker v-model="date" class="w-170 mr-10" type="date" placeholder="选择时间" />
<el-button type="primary" @click="query">查询</el-button>
<el-button @click="reset">重置</el-button>
</div>
</div>
</el-card>
<el-card class="report-card mt-16" shadow="never">
<div class="flex-between pb-12 px-6">
<span class="font-bold">详细数据</span>
<el-button @click="exportData">导出</el-button>
</div>
<el-table :data="pagedRows" class="analysis-table" row-key="name">
<el-table-column fixed prop="name" label="广告账户" min-width="220">
<template #default="{ row }">
<div class="font-bold">{{ row.name }}</div>
<div v-if="row.id" class="fs-12 color-8aa">{{ row.id }}</div>
</template>
</el-table-column>
<el-table-column prop="department" label="部门" width="110">
<template #default="{ row }"><span class="link-text">{{ row.department }}</span></template>
</el-table-column>
<el-table-column prop="group" label="分组" width="110">
<template #default="{ row }"><span class="link-text">{{ row.group }}</span></template>
</el-table-column>
<el-table-column prop="user" label="关联用户" width="110">
<template #default="{ row }"><span class="link-text">{{ row.user }}</span></template>
</el-table-column>
<el-table-column prop="firstCategory" label="一级分类" width="120">
<template #default="{ row }"><span class="link-text">{{ row.firstCategory }}</span></template>
</el-table-column>
<el-table-column prop="secondCategory" label="二级分类" width="120">
<template #default="{ row }"><span class="link-text">{{ row.secondCategory }}</span></template>
</el-table-column>
<el-table-column prop="totalCost" label="总消耗 ⓘ ↑↓" width="140" align="right" />
<el-table-column prop="walletCost" label="非赠款消耗 ⓘ ↑↓" width="150" align="right" />
<el-table-column prop="grantCost" label="赠款消耗 ⓘ ↑↓" width="140" align="right" />
<el-table-column prop="refund" label="消返红包消耗 ⓘ ↑↓" width="160" align="right" />
<el-table-column prop="sharedWallet" label="共享钱包消耗 ⓘ ↑↓" width="160" align="right" />
<el-table-column prop="deposit" label="总存入 ⓘ ↑↓" width="130" align="right">
<template #default="{ row }"><span class="color-main font-bold">{{ row.deposit }}</span></template>
</el-table-column>
<el-table-column prop="transferIn" label="总转入 ⓘ ↑↓" width="130" align="right">
<template #default="{ row }"><span class="color-main font-bold">{{ row.transferIn
}}</span></template>
</el-table-column>
<el-table-column prop="transferOut" label="总转出 ↑↓" width="130" align="right" />
<el-table-column prop="balance" label="总余额 ↑↓" width="140" align="right">
<template #default="{ row }"><span class="color-green font-bold">{{ row.balance }}</span></template>
</el-table-column>
<el-table-column prop="grantBalance" label="赠款余额 ↑↓" width="140" align="right" />
<el-table-column prop="cashBalance" label="非赠款余额 ↑↓" width="150" align="right" />
<el-table-column prop="standardCost" label="标准消耗" width="130" align="right" />
<el-table-column prop="globalCost" label="全域消耗" width="130" align="right" />
<el-table-column prop="remark" label="备注" width="100" align="center" />
</el-table>
<div class="flex justify-end">
<el-pagination v-model:current-page="pageData.currentPage" v-model:page-size="pageData.pageSize"
background layout="total, sizes, prev, pager, next, jumper" :total="tableRows.length"
:page-sizes="pageData.pageSizes" @size-change="handleSizeChange" />
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
const platform = ref('巨量千川');
const keyword = ref('');
const department = ref('');
const group = ref('');
const account = ref('');
const category = ref('');
const date = ref('');
const pageData = reactive({
currentPage: 1,
pageSize: 50,
pageSizes: [10, 20, 50, 100],
});
const platforms = ['巨量千川', '巨量广告'];
const tableRows = [
{ name: '总计', id: '', department: '-', group: '-', user: '-', firstCategory: '-', secondCategory: '-', totalCost: '323165.40', walletCost: '319208.88', grantCost: '3956.52', refund: '0.00', sharedWallet: '0.00', deposit: '3588.16', transferIn: '566500.00', transferOut: '100000.00', balance: '1638168.22', grantBalance: '70439.87', cashBalance: '1567728.35', standardCost: '0.00', globalCost: '103725.62', remark: '-' },
{ name: '青简-元素-持之以恒', id: '1862066464789898', department: '未绑定部门', group: '未绑定分组', user: '未绑定账号', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', totalCost: '92393.53', walletCost: '90523.42', grantCost: '1870.11', refund: '0', sharedWallet: '0', deposit: '1870.11', transferIn: '100000.00', transferOut: '0.00', balance: '88527.53', grantBalance: '0.00', cashBalance: '88527.53', standardCost: '0', globalCost: '0.00', remark: '-' },
{ name: '书意-RICH-扶摇直上九万里', id: '1839701482129801', department: '未绑定部门', group: '未绑定分组', user: '未绑定账号', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', totalCost: '68531.65', walletCost: '68202.75', grantCost: '328.90', refund: '0', sharedWallet: '0', deposit: '328.90', transferIn: '100000.00', transferOut: '0.00', balance: '140294.48', grantBalance: '0.00', cashBalance: '140294.48', standardCost: '0', globalCost: '68351.04', remark: '-' },
{ name: '兰君-母理-绿单888', id: '1843476828396544', department: '未绑定部门', group: '未绑定分组', user: '未绑定账号', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', totalCost: '38148.62', walletCost: '37734.20', grantCost: '414.42', refund: '0', sharedWallet: '0', deposit: '414.42', transferIn: '52500.00', transferOut: '0.00', balance: '55533.17', grantBalance: '0.00', cashBalance: '55533.17', standardCost: '0', globalCost: '0.00', remark: '-' },
{ name: '素棉-母理-绿米花', id: '1868511341739399', department: '未绑定部门', group: '未绑定分组', user: '未绑定账号', firstCategory: '未绑定分类..', secondCategory: '未绑定分类', totalCost: '21948.51', walletCost: '21948.51', grantCost: '0.00', refund: '0', sharedWallet: '0', deposit: '0.00', transferIn: '0.00', transferOut: '0.00', balance: '38610.93', grantBalance: '0.00', cashBalance: '38610.93', standardCost: '0', globalCost: '0.00', remark: '-' },
{ name: '初理-惠理-跑量', id: '1851290312019482', department: 'A部门', group: '核心一组', user: '张伟', firstCategory: '美妆护肤', secondCategory: '洗面奶', totalCost: '102143.09', walletCost: '100800.00', grantCost: '1343.09', refund: '0', sharedWallet: '0', deposit: '974.73', transferIn: '314000.00', transferOut: '100000.00', balance: '1315202.11', grantBalance: '70439.87', cashBalance: '1244762.24', standardCost: '0', globalCost: '35374.58', remark: '-' },
];
const pagedRows = computed(() => {
const start = (pageData.currentPage - 1) * pageData.pageSize;
return tableRows.slice(start, start + pageData.pageSize);
});
const query = () => ElMessage.success('查询完成');
const exportData = () => ElMessage.success('已生成导出任务');
const reset = () => {
keyword.value = '';
department.value = '';
group.value = '';
account.value = '';
category.value = '';
date.value = '';
};
const handleSizeChange = () => {
pageData.currentPage = 1;
};
</script>
<style scoped lang="scss">
.report-card {
border: 1px solid #dfe7f2;
border-radius: 8px;
}
.platform-tabs {
display: flex;
gap: 34px;
border-bottom: 1px solid #edf0f5;
}
.text-tab {
padding-bottom: 10px;
border: 0;
border-bottom: 2px solid transparent;
background: transparent;
color: #3f5574;
font-weight: 700;
cursor: pointer;
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.w-150 {
width: 150px;
}
.w-170,
.w-180 {
width: 180px;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
.color-main {
color: rgba(var(--main-color), 1);
}
.color-green {
color: #00a86b;
}
.color-8aa {
color: #8aa0bf;
}
.scroll-bar {
height: 5px;
margin: 10px 0 14px;
border-radius: 999px;
background: #dfe7f2;
}
</style>
<template>
<div class="status-report flex-col gap-12">
<el-card class="report-card round-8" shadow="never">
<div class="top-line flex-between gap-16">
<div class="flex-items-center gap-26 flex-wrap">
<button v-for="item in dataTabs" :key="item" class="text-tab pointer border-none bg-none font-bold" :class="{ active: dataTab === item }" @click="dataTab = item">
{{ item }}
</button>
</div>
<div class="date-filter flex-items-center gap-8">
<span>计划搭建时间</span>
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="2025/03/31" end-placeholder="2025/04/14" />
</div>
</div>
<div class="flex-items-center justify-start gap-16 pt-14">
<div class="flex-items-center gap-26 flex-wrap">
<button v-for="item in platforms" :key="item.value" class="platform-item flex-items-center gap-6 pointer border-none bg-none" :class="{ active: platform === item.value }" @click="platform = item.value">
<span class="w-10 h-10 round-2" :style="{ backgroundColor: item.color }"></span>
{{ item.label }}
</button>
</div>
<el-select v-model="department" class="w-180" placeholder="请选择部门">
<el-option label="抖音投放" value="抖音投放" />
<el-option label="测试指定员工可见-部门" value="测试指定员工可见-部门" />
<el-option label="未绑定" value="未绑定" />
</el-select>
</div>
</el-card>
<el-card class="report-card chart-card round-8" shadow="never">
<div class="flex-between gap-16">
<el-segmented v-model="statusType" :options="statusTabs" />
<div class="flex-items-center gap-26 flex-wrap">
<span v-for="item in legendItems" :key="item.name" class="flex-items-center gap-6">
<span class="legend-dot w-10 h-10 round" :style="{ borderColor: item.color }"></span>{{ item.name }}
</span>
</div>
</div>
<div class="h-250 mt-10">
<v-chart class="w-full h-full" :option="lineOption" autoresize />
</div>
</el-card>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between gap-16 pb-12">
<span class="font-bold">详细数据</span>
<el-button class="w-34 p-0" @click="settingVisible = !settingVisible"></el-button>
</div>
<el-alert v-if="settingVisible" class="mb-10" title="已打开字段配置面板" type="info" show-icon :closable="false" />
<el-table :data="tableRows" class="analysis-table" row-key="department">
<el-table-column prop="department" label="部门" min-width="260">
<template #default="{ row }"><span :class="row.link ? 'link-text font-bold' : 'font-bold'">{{ row.department }}</span></template>
</el-table-column>
<el-table-column prop="built" label="搭建计划总数 ↑↓" align="center" />
<el-table-column prop="running" label="投放中 ↑↓" align="center">
<template #default="{ row }"><span class="color-green font-bold">{{ row.running }}</span></template>
</el-table-column>
<el-table-column prop="notStarted" label="未投放 ⓘ ↑↓" align="center" />
<el-table-column prop="ended" label="已终止 ↑↓" align="center" />
<el-table-column prop="finished" label="已完成 ↑↓" align="center" />
<el-table-column prop="deleted" label="已删除 ↑↓" align="center">
<template #default="{ row }"><span class="color-8aa">{{ row.deleted }}</span></template>
</el-table-column>
</el-table>
<div class="scroll-bar mt-14"></div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import VChart from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components';
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent]);
const dataTab = ref('部门数据');
const platform = ref('ad');
const department = ref('');
const range = ref<[Date, Date] | ''>('');
const statusType = ref('搭建计划总数');
const settingVisible = ref(false);
const dataTabs = ['部门数据', '分组数据', '个人数据', '直播间数据', '广告主明细数据'];
const statusTabs = ['搭建计划总数', '投放中', '未投放'];
const platforms = [
{ label: '巨量广告', value: 'ad', color: '#7c3aed' },
{ label: '巨量千川', value: 'qianchuan', color: '#2d8cff' },
];
const legendItems = [
{ name: '未绑定', color: '#2d8cff' },
{ name: '测试指定员工可见-部门', color: '#00b578' },
{ name: '抖音投放', color: '#ff9800' },
];
const dates = ['2025-03-31', '2025-04-02', '2025-04-04', '2025-04-06', '2025-04-08', '2025-04-10', '2025-04-12', '2025-04-14'];
const tableRows = [
{ department: '总计', built: 18, running: 5, notStarted: 8, ended: 3, finished: 2, deleted: 0 },
{ department: '抖音投放', built: 8, running: 3, notStarted: 3, ended: 1, finished: 1, deleted: 0, link: true },
{ department: '测试指定员工可见-部门', built: 6, running: 2, notStarted: 3, ended: 1, finished: 0, deleted: 0, link: true },
{ department: '未绑定', built: 4, running: 0, notStarted: 2, ended: 1, finished: 1, deleted: 0, link: true },
];
const lineOption = computed(() => ({
color: legendItems.map((item) => item.color),
tooltip: { trigger: 'axis' },
legend: { show: false },
grid: { left: 38, right: 24, top: 26, bottom: 34 },
xAxis: {
type: 'category',
boundaryGap: false,
data: dates,
axisLine: { lineStyle: { color: '#e8eef6' } },
axisLabel: { color: '#8aa0bf' },
},
yAxis: {
type: 'value',
min: 0,
max: statusType.value === '搭建计划总数' ? 1 : 5,
splitLine: { lineStyle: { color: '#edf3fb', type: 'dashed' } },
axisLabel: { color: '#8aa0bf' },
},
series: [
{ name: '未绑定', type: 'line', smooth: true, data: [0, 0, 0, 0, 0, 0, 0, 0], symbolSize: 8 },
{ name: '测试指定员工可见-部门', type: 'line', smooth: true, data: [0, 0, 0, 0, 0, 0, 0, 0], symbolSize: 8 },
{ name: '抖音投放', type: 'line', smooth: true, data: [0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05], symbolSize: 8 },
],
}));
</script>
<style scoped lang="scss">
.report-card {
border: 1px solid #dfe7f2;
}
.top-line {
border-bottom: 1px solid #edf0f5;
}
.text-tab {
height: 38px;
padding: 0;
border-bottom: 2px solid transparent;
color: #3f5574;
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.date-filter {
color: #607086;
}
.platform-item {
color: #3f5574;
&.active {
color: rgba(var(--main-color), 1);
}
}
.chart-card {
min-height: 310px;
}
.legend-dot {
border: 2px solid;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
.link-text {
color: rgba(var(--main-color), 1);
}
.color-green {
color: #00a86b;
}
.color-8aa {
color: #8aa0bf;
}
.scroll-bar {
height: 5px;
border-radius: 999px;
background: #dfe7f2;
}
</style>
<template>
<div class="shop-report flex-col gap-14">
<el-card class="report-card round-8" shadow="never">
<div class="flex-between flex-wrap gap-12 pb-14">
<div class="flex-items-center gap-12">
<span class="font-bold">授权店铺</span>
<el-button type="primary" @click="authorizeShop">授权店铺</el-button>
</div>
<el-button link @click="expanded = !expanded">{{ expanded ? '收起' : '展开' }}</el-button>
</div>
<div v-show="expanded" class="shop-grid flex flex-wrap gap-16">
<div v-for="shop in shops" :key="shop.code" class="shop-card p-16 round-8 pointer" :class="{ selected: selectedShop === shop.code }" @click="selectedShop = shop.code">
<div class="flex-items-center gap-12">
<div class="tiktok-logo flex-center w-40 h-40 round-8 bg-000 color-fff fs-26 font-bold"></div>
<div class="minw-0">
<div class="flex-items-center gap-8 font-bold">
{{ shop.name }}
<span class="shop-tag px-6 py-2 round-4 fs-12">{{ shop.type }}</span>
</div>
<div class="shop-code mt-8 fs-12">{{ shop.code }}</div>
</div>
</div>
<div class="flex-between gap-8 mt-16">
<el-button type="primary" @click.stop="analyzeShop(shop.code)" size="small">数据分析</el-button>
<el-button @click.stop="moreShopActions(shop.code)" size="small">更多操作</el-button>
</div>
</div>
</div>
</el-card>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between flex-wrap gap-12 pb-14">
<span class="font-bold">数据概览</span>
<div class="flex-items-center gap-12 flex-wrap">
<el-select v-model="selectedShop" class="w-180">
<el-option v-for="shop in shops" :key="shop.code" :label="shop.name" :value="shop.code" />
</el-select>
<el-segmented v-model="granularity" :options="granularityTabs" />
<el-select v-model="timezone" class="w-90">
<el-option label="GMT-8" value="GMT-8" />
<el-option label="GMT+8" value="GMT+8" />
</el-select>
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="2025/04/15" end-placeholder="2025/04/15" />
</div>
</div>
<div class="metric-grid">
<div v-for="item in metrics" :key="item.label" class="metric-card minh-88 p-14 round-8">
<div class="metric-label fs-12">{{ item.label }}</div>
<div class="metric-value mt-8 fs-20 fw-800">{{ item.value }}</div>
<div class="metric-compare mt-8 fs-12">较上一周期 <span>--</span></div>
</div>
</div>
</el-card>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between flex-wrap gap-12 pb-14">
<div class="flex-items-center gap-12">
<button v-for="item in tableTabs" :key="item" class="text-tab border-none bg-none font-bold pointer" :class="{ active: tableTab === item }" @click="tableTab = item">
{{ item }}
</button>
</div>
<el-button type="primary" @click="exportData">导出</el-button>
</div>
<el-table :data="tableRows" class="analysis-table" row-key="name">
<el-table-column label="店铺" align="center">
<el-table-column prop="name" label="名称" min-width="260" />
<el-table-column prop="code" label="code" min-width="180" />
</el-table-column>
<el-table-column label="Statements" align="center">
<el-table-column prop="statementProcessing" label="Processing 累计处理中" min-width="160" align="right" />
<el-table-column prop="statementPaid" label="Paid 总支付" min-width="150" align="right" />
<el-table-column prop="statementFailed" label="Failed 支付失败" min-width="150" align="right" />
</el-table-column>
<el-table-column label="Payouts" align="center">
<el-table-column prop="payoutPaid" label="Paid 总支付" min-width="150" align="right" />
<el-table-column prop="payoutProcessing" label="Processing 待入账" min-width="160" align="right" />
<el-table-column prop="payoutFailed" label="Failed 入账失败" min-width="150" align="right" />
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
const expanded = ref(true);
const selectedShop = ref('USLCHNEAYD');
const granularity = ref('按日');
const timezone = ref('GMT-8');
const range = ref<[Date, Date] | ''>('');
const tableTab = ref('店铺数据');
const granularityTabs = ['按日', '按周', '按月'];
const tableTabs = ['店铺数据', '订单数据'];
const shops = [
{ name: 'SANDBOX7397710...', code: 'USLCHNEAYD', type: '本地' },
{ name: 'GLOBAL_STORE_UK_01', code: 'UK88291045', type: '跨境' },
];
const metrics = [
{ label: 'Statements-Processing', value: '$0' },
{ label: 'Statements-Paid', value: '$0' },
{ label: 'Statements-Failed', value: '$0' },
{ label: 'Payouts-Paid', value: '$0' },
{ label: 'Payouts-Processing', value: '$0' },
{ label: 'Payouts-Failed', value: '$0' },
{ label: '总订单数', value: '0' },
{ label: '总订单金额', value: '$0' },
{ label: '未支付订单数', value: '0' },
{ label: '未支付订单金额', value: '$0' },
{ label: '取消订单数', value: '0' },
{ label: '取消订单金额', value: '$0' },
{ label: '退货退款订单数', value: '0' },
{ label: '退货退款订单金额', value: '$0' },
];
const tableRows = [
{ name: 'SANDBOX7397710...', code: 'USLCHNEAYD', statementProcessing: '$0.00', statementPaid: '$0.00', statementFailed: '$0.00', payoutPaid: '$0.00', payoutProcessing: '$0.00', payoutFailed: '$0.00' },
{ name: 'GLOBAL_STORE_UK_01', code: 'UK88291045', statementProcessing: '$0.00', statementPaid: '$0.00', statementFailed: '$0.00', payoutPaid: '$0.00', payoutProcessing: '$0.00', payoutFailed: '$0.00' },
];
const authorizeShop = () => ElMessage.success('已打开店铺授权流程');
const analyzeShop = (code: string) => ElMessage.success(`正在分析店铺 ${code}`);
const moreShopActions = (code: string) => ElMessage.info(`已打开 ${code} 的更多操作`);
const exportData = () => ElMessage.success('已生成导出任务');
</script>
<style scoped lang="scss">
.report-card {
border: 1px solid #dfe7f2;
}
.shop-card {
flex: 0 0 calc((100% - 64px) / 5);
border: 1px solid #eadffd;
background: #faf7ff;
&.selected {
box-shadow: 0 0 0 1px rgba(var(--main-color), .45) inset;
}
}
.tiktok-logo {
flex-shrink: 0;
}
.minw-0 {
min-width: 0;
}
.shop-tag {
background: #d9fbe8;
color: #00875a;
}
.shop-code {
color: #607086;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(5, minmax(180px, 1fr));
gap: 12px;
}
.metric-card {
border: 1px solid #dfe7f2;
background: #fbfdff;
}
.metric-label {
color: #48607f;
}
.metric-value {
color: #001a3d;
}
.metric-compare {
color: #8aa0bf;
}
.text-tab {
height: 34px;
padding: 0;
border-bottom: 2px solid transparent;
color: #3f5574;
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.analysis-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
@media (max-width: 1600px) {
.shop-card {
flex-basis: calc((100% - 48px) / 4);
}
}
@media (max-width: 1000px) {
.shop-card {
flex-basis: calc((100% - 32px) / 3);
}
}
@media (max-width: 1200px) {
.metric-grid {
grid-template-columns: repeat(2, minmax(240px, 1fr));
}
}
</style>
<template>
<div class="flex-col gap-16">
<section class="insight-hero flex-between gap-16 p-20 round-12 color-fff">
<div class="flex-items-center gap-16">
<button v-for="item in insightTabs" :key="item.value"
class="hero-tab flex-items-center gap-8 px-18 py-10 round-10 border-none color-fff font-bold pointer"
:class="{ active: insightType === item.value }" @click="insightType = item.value">
<span>{{ item.icon }}</span>{{ item.label }}
</button>
</div>
<div class="flex-col items-center gap-10">
<div class="flex-items-center gap-8">
<el-select v-model="sourceRole" class="hero-select w-160">
<el-option label="致上运营" value="致上运营" />
<el-option label="默认部门" value="默认部门" />
</el-select>
<span class="px-12 py-6 round-10">VS</span>
<el-select v-model="targetRole" class="hero-select w-160">
<el-option label="抖音1" value="抖音1" />
<el-option label="B部门" value="B部门" />
</el-select>
</div>
<el-button class="hero-action px-22" round @click="showLeaderInsight">领导力洞察</el-button>
</div>
<div class="w-300">
<el-date-picker v-model="range" class="hero-date" type="daterange" range-separator="至"
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 100%;" />
</div>
</section>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between pb-12">
<div class="flex-items-center gap-8">
<span class="font-bold fs-16">雷达图分析</span>
<span class="fs-12 color-8aa">{{ sourceRole }} 对比 {{ targetRole }}</span>
</div>
<el-button type="primary" @click="exportData">导出</el-button>
</div>
<div class="radar-wrap flex-center">
<v-chart class="radar-chart" :option="radarOption" autoresize />
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import VChart from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { RadarChart } from 'echarts/charts';
import { LegendComponent, TooltipComponent } from 'echarts/components';
import { ElMessage } from 'element-plus';
use([CanvasRenderer, RadarChart, TooltipComponent, LegendComponent]);
const insightType = ref('personal');
const sourceRole = ref('致上运营');
const targetRole = ref('抖音1');
const range = ref<[Date, Date] | ''>('');
const insightTabs = [
{ label: '个人数据洞察', value: 'personal', icon: '♙' },
{ label: '小组数据洞察', value: 'team', icon: '♧' },
];
const radarOption = computed(() => ({
color: ['#3f82ff', '#8b5cf6'],
tooltip: {},
legend: {
bottom: 8,
itemWidth: 12,
itemHeight: 12,
data: [sourceRole.value, targetRole.value],
textStyle: { color: '#24405f', fontWeight: 700 },
},
radar: {
center: ['50%', '47%'],
radius: 150,
splitNumber: 5,
axisName: { color: '#48607f', fontWeight: 700 },
splitLine: { lineStyle: { color: '#e2ebf7', type: 'dashed' } },
splitArea: { areaStyle: { color: ['rgba(63,130,255,.02)', 'rgba(63,130,255,.04)'] } },
axisLine: { lineStyle: { color: '#d9e4f2' } },
indicator: [
{ name: '消耗力', max: 100 },
{ name: '贡献度', max: 100 },
{ name: '多样性', max: 100 },
{ name: '爆片率', max: 100 },
{ name: '勤奋度', max: 100 },
{ name: '创造力', max: 100 },
],
},
series: [
{
type: 'radar',
symbolSize: 8,
areaStyle: { opacity: 0.18 },
data: [
{ name: sourceRole.value, value: [35, 78, 38, 45, 86, 78] },
{ name: targetRole.value, value: [48, 36, 46, 38, 42, 28] },
],
},
],
}));
const showLeaderInsight = () => ElMessage.success('已生成领导力洞察');
const exportData = () => ElMessage.success('已生成导出任务');
</script>
<style scoped lang="scss">
.insight-hero {
min-height: 110px;
background: linear-gradient(110deg, #3f82f6 0%, #8a4ce6 100%);
box-shadow: 0 12px 28px rgba(63, 130, 246, .18);
}
.hero-tab {
background: transparent;
&.active {
background: rgba(255, 255, 255, .16);
box-shadow: 0 0 0 1px rgba(255, 255, 255, .35) inset;
}
}
.hero-select {
:deep(.el-select__wrapper) {
background: rgba(255, 255, 255, .18);
border: 1px solid rgba(255, 255, 255, .35);
box-shadow: none;
}
:deep(.el-select__selected-item),
:deep(.el-select__placeholder),
:deep(.el-select__caret) {
color: #fff;
}
}
.vs-pill,
.hero-action {
background: rgba(255, 255, 255, .18);
color: #fff;
border: 1px solid rgba(255, 255, 255, .35);
}
.hero-date {
:deep(.el-range-input),
:deep(.el-range-separator),
:deep(.el-icon) {
color: #fff;
}
:deep(.el-input__wrapper) {
background: rgba(255, 255, 255, .18);
box-shadow: 0 0 0 1px rgba(255, 255, 255, .35) inset;
}
}
.report-card {
border: 1px solid #dfe7f2;
}
.radar-wrap {
min-height: 470px;
}
.radar-chart {
width: 560px;
height: 430px;
}
.color-8aa {
color: #8aa0bf;
}
</style>
<template>
<div class="analysis-panel bg-fff round-8 p-24">
<div class="flex-items-center gap-34 page-tabs pb-14">
<button v-for="item in dataTabs" :key="item" class="text-tab border-none bg-none font-bold pointer" :class="{ active: dataTab === item }" @click="dataTab = item">
{{ item }}
</button>
</div>
<div class="flex-between flex-wrap gap-14 py-24">
<div class="flex-items-center gap-12 flex-wrap">
<el-segmented v-model="contentType" :options="contentTabs" />
<el-select v-model="department" class="w-160" placeholder="请选择部门">
<el-option v-for="item in departments" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="flex-items-center gap-12">
<el-select v-model="summaryType" class="w-90">
<el-option label="汇总" value="汇总" />
<el-option label="明细" value="明细" />
</el-select>
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
</div>
</div>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between chart-tabs pb-10">
<div class="flex-items-center gap-26">
<button v-for="item in chartTabs" :key="item" class="text-tab small border-none bg-none font-bold pointer" :class="{ active: chartTab === item }" @click="chartTab = item">
{{ item }}
</button>
</div>
<el-button link type="primary" @click="collapsed = !collapsed">{{ collapsed ? '展开' : '收起' }}</el-button>
</div>
<div v-show="!collapsed" class="pt-16">
<div class="flex-between gap-12 mb-16">
<div class="flex-items-center gap-10">
<el-segmented v-model="metric" :options="metricTabs" />
<el-select v-model="rankLimit" class="w-200">
<el-option label="显示top5" value="top5" />
<el-option label="显示top10" value="top10" />
</el-select>
</div>
<div class="flex-items-center gap-18 flex-wrap">
<span v-for="item in legendItems" :key="item.name" class="flex-items-center gap-6 fs-12">
<span class="w-8 h-8 round" :style="{ backgroundColor: item.color }"></span>{{ item.name }}
</span>
</div>
</div>
<div class="chart-box round-8">
<v-chart class="w-full h-full" :option="lineOption" autoresize />
</div>
</div>
</el-card>
<div class="flex-between mt-30 mb-14">
<span class="font-bold fs-16">详细数据</span>
<div class="flex-items-center gap-8">
<el-button @click="exportData">导出数据</el-button>
<el-button class="w-34 p-0" @click="fieldVisible = !fieldVisible"></el-button>
</div>
</div>
<el-alert v-if="fieldVisible" class="mb-10" title="已打开字段配置面板" type="info" show-icon :closable="false" />
<el-table :data="tableRows" class="analysis-table" row-key="department">
<el-table-column prop="date" label="数据时间" min-width="190" />
<el-table-column prop="department" label="部门名称" min-width="160">
<template #default="{ row }"><span class="color-main font-bold">{{ row.department }}</span></template>
</el-table-column>
<el-table-column prop="uploadPeople" label="上传人数 ⓘ ↑↓" align="center" />
<el-table-column prop="uploadCount" label="上传次数 ↑↓" align="center" />
<el-table-column prop="downloadCount" label="下载次数 ↑↓" align="center" />
<el-table-column prop="downloadPeople" label="被下载人数 ↑↓" align="center" />
<el-table-column prop="pushPeople" label="被推送人数 ↑↓" align="center" />
<el-table-column prop="copyPeople" label="被复制剪映人数 ↑↓" align="center" />
<el-table-column prop="usage" label="作品被使用率 ⓘ ↑↓" align="center" />
<el-table-column prop="viral" label="爆款视频数 ↑↓" align="center">
<template #default="{ row }"><span class="color-8aa">{{ row.viral }}</span></template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import VChart from 'vue-echarts';
import { use } from 'echarts/core';
import { LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { ElMessage } from 'element-plus';
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]);
const dataTab = ref('部门数据');
const contentType = ref('全部');
const department = ref('');
const summaryType = ref('汇总');
const range = ref<[Date, Date] | ''>('');
const chartTab = ref('时间趋势');
const metric = ref('上传');
const rankLimit = ref('top5');
const collapsed = ref(false);
const fieldVisible = ref(false);
const dataTabs = ['部门数据', '分组数据', '个人数据'];
const contentTabs = ['全部', '成片', '素材', '第三方', '图片', '音频'];
const chartTabs = ['时间趋势', '占比分析'];
const metricTabs = ['上传', '下载', '复制到剪映'];
const departments = ['默认部门', 'B部门', 'Ian部门1', '7-20部门2', 'A部门'];
const legendItems = [
{ name: '默认部门', color: '#6366f1' },
{ name: 'B部门', color: '#10b981' },
{ name: 'Ian部门1', color: '#f59e0b' },
{ name: '7-20部门2', color: '#ef4444' },
{ name: 'A部门', color: '#06b6d4' },
];
const dates = ['2025-04-06', '2025-04-08', '2025-04-10', '2025-04-12', '2025-04-14', '2025-04-16', '2025-04-18', '2025-04-20'];
const tableRows = [
{ date: '2025-04-06-2025-04-21', department: '默认部门', uploadPeople: 1, uploadCount: 47, downloadCount: 120, downloadPeople: 22, pushPeople: 8, copyPeople: 12, usage: '59.57', viral: 0 },
{ date: '2025-04-06-2025-04-21', department: 'B部门', uploadPeople: 1, uploadCount: 6, downloadCount: 1, downloadPeople: 0, pushPeople: 1, copyPeople: 1, usage: '33.33', viral: 0 },
{ date: '2025-04-06-2025-04-21', department: 'Ian部门1', uploadPeople: 1, uploadCount: 3, downloadCount: 0, downloadPeople: 0, pushPeople: 0, copyPeople: 0, usage: '0.00', viral: 0 },
{ date: '2025-04-06-2025-04-21', department: '7-20部门2', uploadPeople: 1, uploadCount: 3, downloadCount: 0, downloadPeople: 0, pushPeople: 0, copyPeople: 0, usage: '0.00', viral: 0 },
{ date: '2025-04-06-2025-04-21', department: 'A部门', uploadPeople: 0, uploadCount: 0, downloadCount: 0, downloadPeople: 0, pushPeople: 0, copyPeople: 0, usage: '0.00', viral: 0 },
];
const lineOption = computed(() => ({
color: legendItems.map((item) => item.color),
tooltip: { trigger: 'axis' },
legend: { show: false },
grid: { left: 54, right: 40, top: 35, bottom: 42 },
xAxis: { type: 'category', boundaryGap: false, data: dates, axisLabel: { color: '#8aa0bf' }, axisLine: { lineStyle: { color: '#edf3fb' } } },
yAxis: { type: 'value', splitLine: { lineStyle: { color: '#edf3fb', type: 'dashed' } }, axisLabel: { color: '#8aa0bf' } },
series: [
{ name: '默认部门', type: 'line', smooth: true, symbolSize: 7, areaStyle: { opacity: .18 }, data: [0, 4, 2, 1, 0, 6, 35, 1] },
{ name: 'B部门', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0] },
{ name: 'Ian部门1', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0] },
{ name: '7-20部门2', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0] },
{ name: 'A部门', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0] },
],
}));
const exportData = () => ElMessage.success('已生成导出任务');
</script>
<style scoped lang="scss">
.analysis-panel {
border: 1px solid #dfe7f2;
}
.page-tabs,
.chart-tabs {
border-bottom: 1px solid #edf0f5;
}
.text-tab {
height: 36px;
padding: 0;
border-bottom: 2px solid transparent;
color: #10233f;
&.small {
height: 34px;
}
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.report-card,
.chart-box {
border: 1px solid #dfe7f2;
}
.chart-box {
height: 240px;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: 700;
}
}
.color-8aa {
color: #8aa0bf;
}
</style>
<template>
<div class="analysis-panel bg-fff round-8 p-24">
<div class="flex-items-center gap-34 page-tabs pb-14">
<button v-for="item in dataTabs" :key="item" class="text-tab border-none bg-none font-bold pointer" :class="{ active: dataTab === item }" @click="dataTab = item">
{{ item }}
</button>
</div>
<div class="flex-between flex-wrap gap-14 py-24">
<div class="flex-items-center gap-12 flex-wrap">
<el-segmented v-model="taskSource" :options="sourceTabs" />
<el-segmented v-model="taskStatus" :options="statusTabs" />
<el-select v-model="department" class="w-200" placeholder="请选择部门">
<el-option v-for="item in departments" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="flex-items-center gap-12">
<el-select v-model="timeField" class="w-200">
<el-option label="下单日期" value="下单日期" />
<el-option label="发布日期" value="发布日期" />
</el-select>
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="2025/04/06" end-placeholder="2025/04/21" />
</div>
</div>
<el-card class="report-card round-8" shadow="never">
<div class="flex-between chart-tabs pb-10">
<div class="flex-items-center gap-26">
<button v-for="item in chartTabs" :key="item" class="text-tab small border-none bg-none font-bold pointer" :class="{ active: chartTab === item }" @click="chartTab = item">
{{ item }}
</button>
</div>
<el-button link type="primary" @click="collapsed = !collapsed">{{ collapsed ? '展开' : '收起' }}</el-button>
</div>
<div v-show="!collapsed" class="pt-16">
<div class="flex-between gap-12 mb-16">
<div class="flex-items-center gap-10">
<el-segmented v-model="metric" :options="metricTabs" />
<el-select v-model="rankLimit" class="w-200">
<el-option label="显示top5" value="top5" />
<el-option label="显示top10" value="top10" />
</el-select>
</div>
<div class="flex-items-center gap-18 flex-wrap">
<span v-for="item in legendItems" :key="item.name" class="flex-items-center gap-6 fs-12">
<span class="w-8 h-8 round" :style="{ backgroundColor: item.color }"></span>{{ item.name }}
</span>
</div>
</div>
<div class="chart-box round-8">
<v-chart class="w-full h-full" :option="lineOption" autoresize />
</div>
</div>
</el-card>
<div class="flex-between mt-30 mb-14">
<span class="font-bold fs-16">详细数据</span>
<div class="flex-items-center gap-8">
<el-button @click="exportData">导出数据</el-button>
<el-button class="w-34 p-0" @click="fieldVisible = !fieldVisible"></el-button>
</div>
</div>
<el-alert v-if="fieldVisible" class="mb-10" title="已打开字段配置面板" type="info" show-icon :closable="false" />
<el-table :data="tableRows" class="analysis-table" row-key="department">
<el-table-column prop="department" label="部门名称" min-width="180">
<template #default="{ row }"><span class="color-main font-bold">{{ row.department }}</span></template>
</el-table-column>
<el-table-column prop="taskCount" label="任务数 ↑↓" align="center" />
<el-table-column prop="reached" label="已达标 ↑↓" align="center" />
<el-table-column prop="pending" label="待完成 ↑↓" align="center" />
<el-table-column prop="orders" label="下单数 ↑↓" align="center" />
<el-table-column prop="published" label="出片数 ↑↓" align="center">
<template #default="{ row }"><span class="color-main font-bold">{{ row.published }}</span></template>
</el-table-column>
<el-table-column label="操作" min-width="190" align="center">
<template #default="{ row }">
<div class="flex-center gap-12">
<el-button link type="primary" @click="openStats(row)">统计</el-button>
<el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
<el-button link type="primary">数据洞察</el-button>
</div>
</template>
</el-table-column>
</el-table>
<TaskStatsDialog v-model:visible="statsVisible" :row="currentRow" />
<TaskDetailDialog v-model:visible="detailVisible" :department="currentRow?.department" />
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import VChart from 'vue-echarts';
import { use } from 'echarts/core';
import { LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { ElMessage } from 'element-plus';
import TaskStatsDialog from './TaskStatsDialog.vue';
import TaskDetailDialog from './TaskDetailDialog.vue';
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent]);
type TaskRow = {
department: string;
taskCount: number;
reached: number;
pending: number;
orders: number;
published: number;
};
const dataTab = ref('部门数据');
const taskSource = ref('发布的任务');
const taskStatus = ref('全部状态');
const department = ref('');
const timeField = ref('下单日期');
const range = ref<[Date, Date] | ''>('');
const chartTab = ref('时间趋势');
const metric = ref('任务数');
const rankLimit = ref('top5');
const collapsed = ref(false);
const fieldVisible = ref(false);
const statsVisible = ref(false);
const detailVisible = ref(false);
const currentRow = ref<TaskRow>();
const dataTabs = ['部门数据', '分组数据', '个人数据'];
const sourceTabs = ['发布的任务', '被指派的任务', '下单运营'];
const statusTabs = ['全部状态', '待完成', '已达标'];
const chartTabs = ['时间趋势', '占比分析', '视频状态分布'];
const metricTabs = ['任务数', '下单数', '出片数'];
const departments = ['B部门', '电商运营部', 'A部门', 'Ian部门1', '默认部门'];
const legendItems = [
{ name: 'B部门', color: '#6366f1' },
{ name: '默认部门', color: '#10b981' },
{ name: 'A部门', color: '#f59e0b' },
{ name: 'C部门', color: '#ef4444' },
{ name: '抖音投放', color: '#06b6d4' },
];
const dates = ['2025-04-06', '2025-04-07', '2025-04-08', '2025-04-10', '2025-04-12', '2025-04-14', '2025-04-16', '2025-04-18', '2025-04-20'];
const tableRows: TaskRow[] = [
{ department: 'B部门', taskCount: 2, reached: 2, pending: 0, orders: 4, published: 4 },
{ department: '电商运营部', taskCount: 1, reached: 0, pending: 1, orders: 3, published: 0 },
{ department: 'A部门', taskCount: 0, reached: 0, pending: 0, orders: 0, published: 0 },
{ department: 'Ian部门1', taskCount: 0, reached: 0, pending: 0, orders: 0, published: 0 },
{ department: 'Ian部门1', taskCount: 0, reached: 0, pending: 0, orders: 0, published: 0 },
{ department: '默认部门', taskCount: 0, reached: 0, pending: 0, orders: 0, published: 0 },
];
const lineOption = computed(() => ({
color: legendItems.map((item) => item.color),
tooltip: { trigger: 'axis' },
legend: { show: false },
grid: { left: 54, right: 40, top: 35, bottom: 42 },
xAxis: { type: 'category', boundaryGap: false, data: dates, axisLabel: { color: '#8aa0bf' }, axisLine: { lineStyle: { color: '#edf3fb' } } },
yAxis: { type: 'value', splitLine: { lineStyle: { color: '#edf3fb', type: 'dashed' } }, axisLabel: { color: '#8aa0bf' } },
series: [
{ name: 'B部门', type: 'line', smooth: true, symbolSize: 7, areaStyle: { opacity: .18 }, data: [0, 2, 0, 0, 0, 0, 0, 0, 0] },
{ name: '默认部门', type: 'line', smooth: true, symbolSize: 7, areaStyle: { opacity: .16 }, data: [0, 1, 0, 0, 0, 0, 0, 0, 0] },
{ name: 'A部门', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
{ name: 'C部门', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
{ name: '抖音投放', type: 'line', smooth: true, symbolSize: 7, data: [0, 0, 0, 0, 0, 0, 0, 0, 0] },
],
}));
const exportData = () => ElMessage.success('已生成导出任务');
const openStats = (row: TaskRow) => {
currentRow.value = row;
statsVisible.value = true;
};
const openDetail = (row: TaskRow) => {
currentRow.value = row;
detailVisible.value = true;
};
</script>
<style scoped lang="scss">
.analysis-panel {
border: 1px solid #dfe7f2;
}
.page-tabs,
.chart-tabs {
border-bottom: 1px solid #edf0f5;
}
.text-tab {
height: 36px;
padding: 0;
border-bottom: 2px solid transparent;
color: #10233f;
&.small {
height: 34px;
}
&.active {
color: rgba(var(--main-color), 1);
border-bottom-color: rgba(var(--main-color), 1);
}
}
.report-card,
.chart-box {
border: 1px solid #dfe7f2;
}
.chart-box {
height: 240px;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: 700;
}
}
</style>
<template>
<Dialog v-model="visible" bg-color="#fff" width="900">
<div class="bg-fff round-12 hidden">
<div class="dialog-header h-64 px-26 flex-items-center">
<span class="color-main fs-18 mr-10"></span>
<span class="fs-16 font-bold color-000">任务详情列表 - {{ departmentName }}</span>
</div>
<div class="px-26 pt-16 pb-26">
<el-table :data="detailRows" class="detail-table" row-key="id">
<el-table-column prop="id" label="任务编号" min-width="130" />
<el-table-column prop="name" label="任务名称" min-width="170">
<template #default="{ row }">
<span class="color-main">{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="target" label="目标出片" width="80" align="center" />
<el-table-column prop="finished" label="实际完成" width="80" align="center">
<template #default="{ row }">
<span class="color-green">{{ row.finished }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="{ row }">
<span class="status-tag px-8 py-4 round-4" :class="row.status === '已达标' ? 'success' : 'pending'">
{{ row.status }}
</span>
</template>
</el-table-column>
<el-table-column prop="time" label="时间" width="200" align="center" />
</el-table>
<div class="flex justify-end mt-26">
<el-button type="primary" class="w-64" @click="visible = false">关闭</el-button>
</div>
</div>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import Dialog from '@/components/Dialog/index.vue';
const visible = defineModel<boolean>('visible', { required: true });
const props = defineProps<{
department?: string;
}>();
const departmentName = computed(() => props.department || '部门');
const detailRows = computed(() => [
{ id: 'TSK_20250410_01', name: '美妆搜索对比视频批量制作', target: 20, finished: 20, status: '已达标', time: '2025-04-07 10:15' },
{ id: 'TSK_20250415_02', name: '痛点拆解口播卡点视频', target: 15, finished: 8, status: '待完成', time: '2025-04-12 16:30' },
]);
</script>
<style scoped lang="scss">
.dialog-header {
border-bottom: 1px solid #edf0f5;
}
.detail-table {
border: 1px solid #dfe7f2;
border-radius: 8px;
overflow: hidden;
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: 700;
}
}
.color-green {
color: #00a86b;
}
.status-tag {
display: inline-block;
line-height: 18px;
&.success {
color: #00a86b;
background: #e9fbf3;
}
&.pending {
color: #246bfe;
background: #eef4ff;
}
}
</style>
<template>
<Dialog v-model="visible" bg-color="#fff" width="520">
<div class="bg-fff round-12 hidden">
<div class="dialog-header h-64 px-26 flex-items-center">
<span class="w-4 h-18 round-10 bg-main mr-10"></span>
<span class="fs-16 font-bold color-000">数据汇总</span>
</div>
<div class="px-26 pt-18 pb-24">
<div class="summary-table round-8 hidden">
<div class="summary-row">
<div class="summary-label px-18 py-12 font-bold">下单数量</div>
<div class="px-18 py-12 font-bold">{{ stats.orders }}</div>
</div>
<div class="summary-row">
<div class="summary-label px-18 py-12 font-bold">出片数量</div>
<div class="px-18 py-12 font-bold">{{ stats.published }}</div>
</div>
</div>
<div class="mt-18 mb-8 font-bold color-000">状态汇总</div>
<div class="summary-table round-8 hidden">
<div v-for="item in statusRows" :key="item.label" class="status-row">
<div class="summary-label px-18 py-10 font-bold">{{ item.label }}</div>
<div class="px-18 py-10 text-center">{{ item.rate }}</div>
<div class="summary-label px-18 py-10 text-center font-bold">数量</div>
<div class="px-18 py-10 text-center font-bold">{{ item.count }}</div>
</div>
</div>
<div class="flex justify-end mt-28">
<el-button type="primary" class="w-64" @click="visible = false">确定</el-button>
</div>
</div>
</div>
</Dialog>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import Dialog from '@/components/Dialog/index.vue';
const visible = defineModel<boolean>('visible', { required: true });
const props = defineProps<{
row?: {
orders?: number;
published?: number;
reached?: number;
pending?: number;
};
}>();
const stats = computed(() => ({
orders: props.row?.orders ?? 0,
published: props.row?.published ?? 0,
reached: props.row?.reached ?? 0,
pending: props.row?.pending ?? 0,
}));
const statusRows = computed(() => {
const total = stats.value.reached + stats.value.pending;
const rate = (value: number) => (total ? `${Math.round((value / total) * 100)}%` : '0%');
return [
{ label: '已上机', rate: rate(stats.value.reached), count: stats.value.reached },
{ label: '待审核', rate: rate(stats.value.pending), count: stats.value.pending },
];
});
</script>
<style scoped lang="scss">
.dialog-header {
border-bottom: 1px solid #edf0f5;
}
.summary-table {
border: 1px solid #dfe7f2;
}
.summary-row {
display: grid;
grid-template-columns: 1fr 1.6fr;
border-bottom: 1px solid #edf0f5;
&:last-child {
border-bottom: none;
}
}
.status-row {
display: grid;
grid-template-columns: 1fr 1fr .5fr .5fr;
border-bottom: 1px solid #edf0f5;
&:last-child {
border-bottom: none;
}
}
.summary-label {
background: #f8fbff;
color: #48607f;
}
</style>
......@@ -21,7 +21,8 @@
<el-option label="全部分类" value="all" />
<el-option label="服饰鞋包" value="clothes" />
</el-select>
<div class="w-240 mx-10">
<div class="w-400 mx-10 flex-items-center">
<div class="minw-70 color-888">消耗时间:</div>
<el-date-picker v-model="dateRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100% !important;" />
</div>
......@@ -35,28 +36,34 @@
<div class="pb-16 flex-between ratio-title">
<span class="fs-16 font-bold color-000">占比分析</span>
<el-button type="primary" link @click="collapsed = !collapsed">{{ collapsed ? '展开' : '收起'
}}</el-button>
}}</el-button>
</div>
<div v-show="!collapsed" class="p-20 flex gap-26">
<div class="flex-1 gap-14 card-grid">
<div v-for="item in ratioList" :key="item.name" class="p-18 round-6 flex-between metric-card">
<div class="flex-items-center">
<div class="flex-items-center w-full">
<span class="w-42 h-42 round-8 flex-center fs-20"
:style="{ color: item.color, backgroundColor: item.bg }">
{{ item.icon }}
</span>
<div class="ml-16">
<div class="ml-16 flex-1">
<div class="font-bold color-000">{{ item.name }}</div>
<div class="mt-8">数量: <span class="font-bold">{{ item.count.toLocaleString() }}</span>
</div>
<div class="mt-6">消耗: <span class="font-bold">¥{{ item.cost.toLocaleString() }}</span>
<div class="flex flex-between mt-10">
<div>
<div>数量: <span class="font-bold">{{ item.count.toLocaleString()
}}</span>
</div>
<div>消耗: <span class="font-bold">¥{{ item.cost.toLocaleString()
}}</span>
</div>
</div>
<div class="lh-24">
<div>数量占比: <span class="font-bold">{{ item.countRate }}%</span></div>
<div>消耗占比: <span class="font-bold">{{ item.costRate }}%</span></div>
</div>
</div>
</div>
</div>
<div class="lh-24">
<div>数量占比: <span class="font-bold">{{ item.countRate }}%</span></div>
<div>消耗占比: <span class="font-bold">{{ item.costRate }}%</span></div>
</div>
</div>
</div>
<div class="flex-1 minw-400 flex-items-center justify-center chart-panel">
......@@ -87,7 +94,7 @@
<el-table :data="pagedTableData" class="analysis-table">
<el-table-column prop="account" label="广告账户" min-width="210" show-overflow-tooltip />
<el-table-column prop="subject" label="主体" min-width="180" show-overflow-tooltip />
<el-table-column prop="department" label="部门" width="130" />
<el-table-column prop="department" label="团队" width="130" />
<el-table-column prop="group" label="分组" width="120" />
<el-table-column prop="user" label="用户" width="100" />
<el-table-column prop="category1" label="一级分类" width="110" />
......@@ -104,7 +111,7 @@
</el-table-column>
<el-table-column prop="goodCost" label="优质素材消耗" width="140" align="center">
<template #default="{ row }"><span class="font-bold color-green">{{ row.goodCost
}}</span></template>
}}</span></template>
</el-table-column>
<el-table-column prop="low" label="低效素材" width="110" align="center">
<template #default="{ row }"><span class="font-bold color-main">{{ row.low }}</span></template>
......@@ -160,7 +167,7 @@ const platformTabs = [
{ label: '巨量千川', value: 'qianchuan', icon: 'Ⅲ' },
{ label: '巨量广告', value: 'ad', icon: '⊘' },
];
const scopes = ['汇总', '部门', '分组', '个人', '广告账户'];
const scopes = ['汇总', '团队', '分组', '个人', '广告账户'];
const ratioList = [
{ name: '首发素材', count: 1280, cost: 342800, countRate: 28.5, costRate: 32.4, color: '#5046e5', bg: '#f0efff', icon: '⌘' },
{ name: '优质素材', count: 860, cost: 415200, countRate: 19.1, costRate: 39.2, color: '#ef3d3d', bg: '#fff0f0', icon: '♙' },
......@@ -239,9 +246,9 @@ const handleSizeChange = () => {
.analysis-table {
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: bold;
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
......
<template>
<div class="p-6">
<el-card class="round-12 mt-10 filter-card" shadow="hover">
<div class="bg-fff pt-16 px-16 round-8">
<div class="flex-items-center gap-34 header-tabs">
<div v-for="item in platforms" :key="item" class="pb-16 font-bold pointer"
:class="{ active: platform === item }" @click="platform = item">{{ item }}</div>
</div>
<div class="py-14 sub-tabs">
<div class="flex-items-center gap-24">
<span v-for="item in scopes" :key="item" class="font-bold pointer"
:class="{ 'color-main': scope === item }" @click="scope = item">{{ item }}</span>
</div>
<div class="flex-items-center gap-10">
<div class="w-300 flex-items-center mr-10">
<div class="w-90">上传时间:</div>
<el-date-picker v-model="uploadRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100% !important;" />
</div>
<div class="w-300 flex-items-center mr-10">
<div class="w-90">投放时间:</div>
<el-date-picker v-model="deliveryRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100% !important;" />
</div>
</div>
<el-card class="round-12 mt-16 filter-card" shadow="hover">
<div class="flex-between pb-16 sub-tabs">
<div class="flex-items-center gap-30">
<span v-for="item in scopes" :key="item" class="pointer" :class="{ 'color-main': scope === item }"
@click="scope = item">{{ item }}</span>
</div>
<div class="flex-between mt-16">
<div class="">
<el-button type="primary">视频作者</el-button>
<el-button class="mr-10">下单运营</el-button>
<el-select v-model="department" class="w-180 mr-10" placeholder="请选择部门" />
<el-select v-model="category" class="w-180 mr-10" placeholder="请选择分类" />
<el-button type="primary" class="w-66" plain>查询</el-button>
<el-button>重置</el-button>
<div class="flex-items-center gap-10">
<div class="w-300 flex-items-center mr-10">
<div class="w-90">上传时间:</div>
<el-date-picker v-model="uploadRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100% !important;" />
</div>
<div>
<el-button>导出数据</el-button>
<el-button class="w-34 h-34"></el-button>
<div class="w-300 flex-items-center mr-10">
<div class="w-90">投放时间:</div>
<el-date-picker v-model="deliveryRange" type="daterange" range-separator="至"
start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100% !important;" />
</div>
</div>
</div>
</el-card>
<div class="flex-between my-16">
<el-radio-group v-model="radioVal" class="mr-10">
<el-radio-button label="视频作者" :value="1" />
<el-radio-button label="下单运营" :value="2" />
</el-radio-group>
<div class="flex">
<el-select v-model="department" class="w-180 mr-10" placeholder="请选择团队" />
<el-select v-model="category" class="w-180 mr-10" placeholder="请选择分类" />
<el-button type="primary" class="w-66" plain>查询</el-button>
<el-button>重置</el-button>
<el-button>导出数据</el-button>
<el-button class="w-34"></el-button>
</div>
</div>
<el-card class="round-12 mt-16" shadow="hover">
<el-table :data="pagedTableData" class="analysis-table">
<el-table-column prop="department" label="部门" min-width="160" />
<el-table-column prop="department" label="团队" min-width="160" />
<el-table-column prop="cost" label="消耗" width="120" align="center" />
<el-table-column prop="roi" label="roi" width="90" align="center" />
<el-table-column prop="totalAmount" label="总成交金额" width="130" align="center" />
......@@ -70,24 +71,25 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
const platform = ref('巨量广告');
const scope = ref('部门');
const scope = ref('团队');
const department = ref('');
const category = ref('');
const uploadRange = ref('');
const deliveryRange = ref('');
const radioVal = ref(1)
const pageData = reactive({
currentPage: 1,
pageSize: 10,
pageSizes: [10, 20, 50, 100],
});
const platforms = ['巨量广告', '巨量千川', '磁力智投', '磁力金牛', '百度营销', '小红书'];
const scopes = ['部门', '分组', '个人', '明细', '分日', '分月'];
const scopes = ['团队', '分组', '个人', '明细', '分日', '分月'];
const tableData = [
{ department: '总计', cost: '1375201.50', roi: '3.69', totalAmount: '¥5,074,860', amount: '¥4,740,000', coupon: '¥334,860', conversions: '44,240', rate: '4.54%', costRate: '¥31.09', show: '26,040,000', avgCost: '¥52.81', click: '974,510', clickRate: '3.74%', avgPrice: '¥1.41', play: '22,300,000' },
{ department: '项目1-爆款电商', cost: '342100.80', roi: '4.12', totalAmount: '¥1,409,450', amount: '¥1,320,000', coupon: '¥89,450', conversions: '11,400', rate: '5.10%', costRate: '¥30.01', show: '6,450,000', avgCost: '¥53.03', click: '223,500', clickRate: '3.46%', avgPrice: '¥1.53', play: '5,600,000' },
{ department: '达人测试部门', cost: '285400.00', roi: '3.85', totalAmount: '¥1,098,790', amount: '¥1,020,000', coupon: '¥78,790', conversions: '9,240', rate: '4.82%', costRate: '¥30.88', show: '5,120,000', avgCost: '¥55.74', click: '191,980', clickRate: '3.75%', avgPrice: '¥1.48', play: '4,320,000' },
{ department: '达人测试团队', cost: '285400.00', roi: '3.85', totalAmount: '¥1,098,790', amount: '¥1,020,000', coupon: '¥78,790', conversions: '9,240', rate: '4.82%', costRate: '¥30.88', show: '5,120,000', avgCost: '¥55.74', click: '191,980', clickRate: '3.75%', avgPrice: '¥1.48', play: '4,320,000' },
{ department: 'xx素颜霜项目', cost: '220500.00', roi: '3.68', totalAmount: '¥811,440', amount: '¥760,000', coupon: '¥51,440', conversions: '7,350', rate: '4.50%', costRate: '¥30.00', show: '4,100,000', avgCost: '¥53.78', click: '163,330', clickRate: '3.98%', avgPrice: '¥1.35', play: '3,680,000' },
{ department: '小真测试部门', cost: '196200.50', roi: '3.42', totalAmount: '¥671,000', amount: '¥630,000', coupon: '¥41,000', conversions: '6,150', rate: '4.25%', costRate: '¥31.90', show: '3,820,000', avgCost: '¥51.36', click: '144,700', clickRate: '3.78%', avgPrice: '¥1.35', play: '3,100,000' },
{ department: '小真测试团队', cost: '196200.50', roi: '3.42', totalAmount: '¥671,000', amount: '¥630,000', coupon: '¥41,000', conversions: '6,150', rate: '4.25%', costRate: '¥31.90', show: '3,820,000', avgCost: '¥51.36', click: '144,700', clickRate: '3.78%', avgPrice: '¥1.35', play: '3,100,000' },
{ department: '抖音投放一组', cost: '189000.20', roi: '3.52', totalAmount: '¥665,280', amount: '¥620,000', coupon: '¥45,280', conversions: '5,980', rate: '4.15%', costRate: '¥31.60', show: '3,600,000', avgCost: '¥52.50', click: '144,000', clickRate: '4.00%', avgPrice: '¥1.31', play: '3,150,000' },
];
const pagedTableData = computed(() => {
......@@ -105,7 +107,6 @@ const handleSizeChange = () => {
border: 1px solid #dfe7f2;
}
.header-tabs,
.sub-tabs,
.table-footer {
border-bottom: 1px solid #edf0f5;
......@@ -119,13 +120,14 @@ const handleSizeChange = () => {
.header-tabs .active {
color: rgba(var(--main-color), 1);
border-bottom: 2px solid rgba(var(--main-color), 1);
box-sizing: content-box;
}
.analysis-table {
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: bold;
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
</style>
......@@ -208,9 +208,9 @@ const handleSizeChange = () => {
.analysis-table {
:deep(.el-table__header th) {
background: #f8fbff;
color: #48607f;
font-weight: bold;
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
</style>
......@@ -2,21 +2,30 @@
<div class="p-6">
<el-card class="bg-fff round-12 mt-10" shadow="hover">
<div class="flex-items-center gap-30 header-tabs">
<div v-for="item in dataTabs" :key="item" class="pb-16 font-bold pointer" :class="{ active: dataTab === item }" @click="dataTab = item">{{ item }}</div>
<div v-for="item in dataTabs" :key="item" class="pb-16 font-bold pointer"
:class="{ active: dataTab === item }" @click="dataTab = item">{{ item }}</div>
</div>
<div class="pt-16 flex-between">
<div class="flex-items-center">
<el-button type="primary">汇总</el-button>
<el-button class="mr-10">明细</el-button>
<el-select v-model="department" class="w-180 mr-10" placeholder="请选择部门"><el-option label="默认部门" value="默认部门" /></el-select>
<el-select v-model="group" class="w-180 mr-10" placeholder="请选择"><el-option label="华南电商一队" value="华南电商一队" /></el-select>
<div class="w-240 mr-10">
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="2022/04/27" end-placeholder="2025/05/12" style="width: 100% !important;" />
<div class="pt-16">
<div class="flex-between">
<div class="flex">
<el-radio-group v-model="radioVal" class="mr-10">
<el-radio-button label="汇总" :value="1" />
<el-radio-button label="明细" :value="2" />
</el-radio-group>
<el-select v-model="department" class="w-180 mr-10" placeholder="请选择团队"><el-option label="默认团队"
value="默认团队" /></el-select>
<el-select v-model="group" class="w-180 mr-10" placeholder="请选择"><el-option label="华南电商一队"
value="华南电商一队" /></el-select>
<el-button type="primary" plain>查询</el-button>
<el-button>重置</el-button>
</div>
<div class="w-240">
<el-date-picker v-model="range" type="daterange" range-separator="至" start-placeholder="开始时间"
end-placeholder="结束时间" style="width: 100% !important;" />
</div>
<el-button type="primary" plain>查询</el-button>
<el-button>重置</el-button>
</div>
</div>
</el-card>
......@@ -26,7 +35,7 @@
<el-button type="default" plain>导出数据</el-button>
</div>
<el-table :data="pagedTableData" class="analysis-table">
<el-table-column prop="department" label="部门" min-width="180" />
<el-table-column prop="department" label="团队" min-width="180" />
<el-table-column prop="cost" label="消耗" align="center" />
<el-table-column prop="conversions" label="转化数" align="center" />
<el-table-column prop="rate" label="转化率" align="center" />
......@@ -39,15 +48,9 @@
<el-table-column prop="complete" label="3S完播率" align="center" />
</el-table>
<div class="px-20 py-12 flex justify-end table-footer">
<el-pagination
v-model:current-page="pageData.currentPage"
v-model:page-size="pageData.pageSize"
background
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.length"
:page-sizes="pageData.pageSizes"
@size-change="handleSizeChange"
/>
<el-pagination v-model:current-page="pageData.currentPage" v-model:page-size="pageData.pageSize"
background layout="total, sizes, prev, pager, next, jumper" :total="tableData.length"
:page-sizes="pageData.pageSizes" @size-change="handleSizeChange" />
</div>
</el-card>
</div>
......@@ -55,19 +58,20 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
const dataTab = ref('部门数据');
const dataTab = ref('团队数据');
const department = ref('');
const group = ref('');
const range = ref('');
const radioVal = ref(1)
const pageData = reactive({
currentPage: 1,
pageSize: 10,
pageSizes: [10, 20, 50, 100],
});
const dataTabs = ['部门数据', '分组数据', '个人数据'];
const dataTabs = ['团队数据', '分组数据', '个人数据'];
const tableData = [
{ department: '总计', cost: '361051.50', conversions: '11,550', rate: '4.27%', costRate: '¥31.26', show: '6,880,000', click: '270,530', clickRate: '3.93%', avgPrice: '1.33', play: '5,860,000', complete: '20.6%' },
{ department: '默认部门', cost: '128450.50', conversions: '4,210', rate: '4.12%', costRate: '¥30.50', show: '2,450,000', click: '98,200', clickRate: '4.01%', avgPrice: '1.31', play: '2,100,000', complete: '22.5%' },
{ department: '默认团队', cost: '128450.50', conversions: '4,210', rate: '4.12%', costRate: '¥30.50', show: '2,450,000', click: '98,200', clickRate: '4.01%', avgPrice: '1.31', play: '2,100,000', complete: '22.5%' },
{ department: '华南电商一队', cost: '96200.80', conversions: '3,120', rate: '3.95%', costRate: '¥30.83', show: '1,820,000', click: '74,620', clickRate: '4.10%', avgPrice: '1.29', play: '1,560,000', complete: '21.2%' },
{ department: '华东投放二队', cost: '84300.00', conversions: '2,680', rate: '3.78%', costRate: '¥31.45', show: '1,590,000', click: '62,010', clickRate: '3.90%', avgPrice: '1.36', play: '1,320,000', complete: '20.4%' },
{ department: '海外品牌推广组', cost: '52100.20', conversions: '1,540', rate: '3.45%', costRate: '¥33.83', show: '1,020,000', click: '35,700', clickRate: '3.50%', avgPrice: '1.46', play: '880,000', complete: '18.5%' },
......@@ -82,8 +86,27 @@ const handleSizeChange = () => {
</script>
<style scoped lang="scss">
.header-tabs,.table-title,.table-footer { border-bottom: 1px solid #edf0f5; }
.table-footer { border-top: 1px solid #edf0f5; border-bottom: none; }
.header-tabs .active { color: rgba(var(--main-color), 1); border-bottom: 2px solid rgba(var(--main-color), 1); }
.analysis-table { :deep(.el-table__header th) { background: #f8fbff; color: #48607f; font-weight: bold; } }
.header-tabs,
.table-title,
.table-footer {
border-bottom: 1px solid #edf0f5;
}
.table-footer {
border-top: 1px solid #edf0f5;
border-bottom: none;
}
.header-tabs .active {
color: rgba(var(--main-color), 1);
border-bottom: 2px solid rgba(var(--main-color), 1);
}
.analysis-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
}
</style>
<template>
<div>
<div class="bgmr vh100 over-y-auto">
<section class="bg-fff px-20 py-10 analysis-tabs">
<div class="flex-items-center gap-16">
<div
v-for="item in tabs"
:key="item.value"
class="px-12 py-8 round-6 font-bold pointer"
:class="activeTab === item.value ? 'bg-main color-fff' : 'color-000'"
@click="activeTab = item.value"
>
{{ item.label }}
</div>
</div>
</section>
<div class="p-16">
<component :is="currentComponent" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import Tab1 from './components/Team/Tab1.vue';
import Tab2 from './components/Team/Tab2.vue';
import Tab3 from './components/Team/Tab3.vue';
const activeTab = ref('tab1');
const tabs = [
{ label: '数据洞察', value: 'tab1', component: Tab1 },
{ label: '创作分析', value: 'tab2', component: Tab2 },
{ label: '任务分析', value: 'tab3', component: Tab3 },
];
const currentComponent = computed(() => tabs.find((item) => item.value === activeTab.value)?.component);
</script>
<style scoped lang="scss">
</style>
\ No newline at end of file
.analysis-tabs {
border-bottom: 1px solid #dfe7f2;
}
</style>
......@@ -21,10 +21,10 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import AdPlatformTags from './components/AdPlatformTags.vue';
import TagAnalysis from './components/TagAnalysis.vue';
import TencentDeliveryReport from './components/TencentDeliveryReport.vue';
import DeliveryReport from './components/DeliveryReport.vue';
import AdPlatformTags from './components/video/AdPlatformTags.vue';
import TagAnalysis from './components/video/TagAnalysis.vue';
import TencentDeliveryReport from './components/video/TencentDeliveryReport.vue';
import DeliveryReport from './components/video/DeliveryReport.vue';
const activeTab = ref('platform');
const tabs = [
......
<template>
<div class="live-page bgmr vh100 over-y-auto p-24">
<section class="flex-between gap-20">
<div>
<h2 class="fs-16 color-000">直播账号总览</h2>
<p class="mt-4 color-888 fs-12">查看所有已绑定直播号的点赞、消耗与经营表现</p>
</div>
<el-button type="primary" class="h-36 px-20" @click="bindVisible = true">
<el-icon class="mr-6"><Link /></el-icon>
绑定直播号
</el-button>
</section>
<section class="summary-list flex bg-fff round-8 hidden mt-16">
<div v-for="item in summaryCards" :key="item.label" class="summary-card flex-1 px-22 py-12">
<div class="color-888 fs-12">{{ item.label }}</div>
<div class="mt-8 fs-18 font-bold" :class="item.color">{{ item.value }}</div>
</div>
</section>
<el-card class="mt-16 bg-fff round-8 account-table-wrap hidden" shadow="never">
<SeTable :data-list="accounts" class="account-table" :total="accounts.length" :default-page-size="50" size="default" @update:page="handlePageChange">
<el-table-column label="直播账号" min-width="300">
<template #default="{ row }">
<div class="flex-items-center minw-0">
<div class="live-icon flex-center wh-40 round-8 color-fff mr-14">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="minw-0">
<div class="font-bold color-000 ellipsis">{{ row.name }}</div>
<div class="mt-6 fs-12 color-8aa ellipsis">@{{ row.handle }} · {{ row.lastSync }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="rooms" label="直播间数" min-width="110" />
<el-table-column prop="likes" label="累计点赞" min-width="130">
<template #default="{ row }">
<span class="like-text font-bold">{{ row.likes.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="cost" label="投放消耗" min-width="130">
<template #default="{ row }">
<span class="font-bold">¥{{ row.cost.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="sales" label="成交销量" min-width="130">
<template #default="{ row }">
<span class="font-bold">{{ row.sales.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="amount" label="成交金额" min-width="130">
<template #default="{ row }">
<span class="font-bold">¥{{ row.amount.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="roi" label="综合 ROI" min-width="120">
<template #default="{ row }">
<span class="color-main font-bold">{{ row.roi.toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="140">
<template #default="{ row }">
<span class="status-tag px-12 py-2 round-20 font-bold fs-12" :class="getStatusClass(row.status)">
{{ row.status }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" min-width="190" align="center">
<template #default="{ row }">
<div class="flex-center gap-8">
<el-button class="w-34 h-34 p-0" @click="openDetail(row)">
<el-icon><View /></el-icon>
</el-button>
<el-button v-if="row.canSync" class="w-34 h-34 p-0" @click="syncAccount(row)">
<el-icon><Refresh /></el-icon>
</el-button>
<el-button class="w-34 h-34 p-0" @click="removeAccount(row)">
<el-icon><Delete /></el-icon>
</el-button>
</div>
</template>
</el-table-column>
</SeTable>
</el-card>
<Dialog v-model="bindVisible" bg-color="#fff" width="520">
<div class="bg-fff round-8 hidden">
<div class="dialog-header flex-items-center px-24 py-18">
<span class="w-4 h-16 round-10 bg-main mr-12"></span>
<span class="font-bold color-000">绑定直播号</span>
</div>
<div class="p-24">
<el-form :model="bindForm" label-width="92px">
<el-form-item label="直播号名称">
<el-input v-model="bindForm.name" placeholder="请输入直播号名称" />
</el-form-item>
<el-form-item label="账号ID">
<el-input v-model="bindForm.handle" placeholder="例如 MCBeauty_2026" />
</el-form-item>
<el-form-item label="授权状态">
<el-select v-model="bindForm.status" class="w-full">
<el-option label="已绑定" value="已绑定" />
<el-option label="首次同步中" value="首次同步中" />
<el-option label="授权即将到期" value="授权即将到期" />
</el-select>
</el-form-item>
</el-form>
<div class="flex justify-end gap-12 mt-20">
<el-button @click="bindVisible = false">取消</el-button>
<el-button type="primary" @click="confirmBind">确定绑定</el-button>
</div>
</div>
</div>
</Dialog>
<Dialog v-model="detailVisible" bg-color="#fff" width="900">
<div class="bg-fff round-8 hidden">
<div class="dialog-header flex-items-center px-24 py-18">
<span class="w-4 h-14 round-10 bg-main mr-12"></span>
<span class="font-bold color-000">直播账号详情</span>
</div>
<div v-if="currentAccount" class="detail-panel pt-4">
<div class="flex-items-center mb-20 px-24 pt-24">
<div class="live-icon flex-center wh-44 round-8 color-fff mr-14">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="minw-0">
<div class="font-bold fs-16 color-000">{{ currentAccount.name }}</div>
<div class="fs-12 color-8aa mt-4">@{{ currentAccount.handle }} · {{ currentAccount.lastSync }}</div>
</div>
<span class="status-tag px-12 py-6 round-20 font-bold fs-12 ml-18" :class="getStatusClass(currentAccount.status)">
{{ currentAccount.status }}
</span>
</div>
<div class="detail-metrics flex gap-12 mb-20 px-24">
<div v-for="item in detailItems" :key="item.label" class="detail-metric flex-1 p-16 round-6">
<div class="fs-12 color-8aa">{{ item.label }}</div>
<div class="mt-8 font-bold color-000 fs-20">{{ item.value }}</div>
</div>
</div>
<div class="font-bold color-000 mb-12 px-24">直播间销量与销售额</div>
<div class="px-24">
<SeTable :data-list="currentAccount.roomsData" class="detail-room-table" :total="currentAccount.roomsData.length" :default-page-size="10" size="default">
<el-table-column prop="room" label="直播间" min-width="170" />
<el-table-column prop="host" label="主播" min-width="120" />
<el-table-column prop="sales" label="销量" min-width="100" />
<el-table-column prop="amount" label="销售额" min-width="130">
<template #default="{ row }">
<span class="font-bold">¥{{ row.amount.toLocaleString() }}</span>
</template>
</el-table-column>
<el-table-column prop="roi" label="ROI" min-width="100">
<template #default="{ row }">
<span class="color-main font-bold">{{ row.roi.toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" min-width="120">
<template #default="{ row }">
<span class="room-status px-10 py-2 round-20 font-bold fs-12" :class="row.status === '直播中' ? 'live' : 'ended'">
{{ row.status }}
</span>
</template>
</el-table-column>
</SeTable>
</div>
</div>
<div class="dialog-footer flex justify-end p-24">
<el-button type="primary" class="w-64" @click="detailVisible = false">关闭</el-button>
</div>
</div>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import { Delete, Link, Refresh, VideoCamera, View } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
type LiveStatus = '已绑定' | '授权即将到期' | '首次同步中';
type LiveAccount = {
id: number;
name: string;
handle: string;
lastSync: string;
rooms: number;
likes: number;
cost: number;
sales: number;
amount: number;
roi: number;
status: LiveStatus;
canSync: boolean;
roomsData: LiveRoom[];
};
type LiveRoom = {
room: string;
host: string;
sales: number;
amount: number;
roi: number;
status: '直播中' | '已结束';
};
const bindVisible = ref(false);
const detailVisible = ref(false);
const currentAccount = ref<LiveAccount>();
const bindForm = reactive({
name: '',
handle: '',
status: '已绑定' as LiveStatus,
});
const accounts = ref<LiveAccount[]>([
{
id: 1,
name: '梦畅美妆旗舰店',
handle: 'MCBeauty_2026',
lastSync: '今天 10:26',
rooms: 3,
likes: 286400,
cost: 82640,
sales: 2158,
amount: 368420,
roi: 4.46,
status: '已绑定',
canSync: false,
roomsData: [
{ room: '秋季通勤穿搭', host: '梁清淇', sales: 498, amount: 88720, roi: 4.72, status: '直播中' },
{ room: '显瘦牛仔合集', host: '徐云卿', sales: 624, amount: 116800, roi: 4.96, status: '已结束' },
{ room: '晚间精华专场', host: '苏沐', sales: 1036, amount: 162900, roi: 4.28, status: '已结束' },
],
},
{
id: 2,
name: '梦畅服饰直播号',
handle: 'MCFashion_Live',
lastSync: '今天 10:24',
rooms: 2,
likes: 184600,
cost: 59320,
sales: 1462,
amount: 286900,
roi: 4.84,
status: '已绑定',
canSync: false,
roomsData: [
{ room: '秋季通勤穿搭', host: '梁清淇', sales: 498, amount: 88720, roi: 4.72, status: '直播中' },
{ room: '显瘦牛仔合集', host: '徐云卿', sales: 624, amount: 116800, roi: 4.96, status: '已结束' },
],
},
{
id: 3,
name: '梦畅家居好物',
handle: 'MCHome_Select',
lastSync: '昨天 23:58',
rooms: 1,
likes: 93200,
cost: 31860,
sales: 708,
amount: 128760,
roi: 4.04,
status: '授权即将到期',
canSync: true,
roomsData: [
{ room: '小户型收纳节', host: '周宁', sales: 708, amount: 128760, roi: 4.04, status: '已结束' },
],
},
{
id: 4,
name: '梦畅个护精选',
handle: 'MCCare_Select',
lastSync: '等待首次同步',
rooms: 1,
likes: 0,
cost: 0,
sales: 0,
amount: 0,
roi: 0,
status: '首次同步中',
canSync: true,
roomsData: [
{ room: '个护新品首场', host: '待分配', sales: 0, amount: 0, roi: 0, status: '直播中' },
],
},
]);
const statusMeta: Record<LiveStatus, { className: string }> = {
已绑定: { className: 'success' },
授权即将到期: { className: 'warning' },
首次同步中: { className: 'syncing' },
};
const getStatusClass = (status: LiveStatus) => statusMeta[status].className;
const summaryCards = computed(() => [
{ label: '正常同步', value: accounts.value.filter((item) => item.status === '已绑定').length, color: 'color-green' },
{ label: '首次同步中', value: accounts.value.filter((item) => item.status === '首次同步中').length, color: 'color-main' },
{ label: '授权待处理', value: accounts.value.filter((item) => item.status === '授权即将到期').length, color: 'color-orange' },
{ label: '当前直播中', value: accounts.value.filter((item) => item.rooms > 0).length, color: 'color-red' },
]);
const detailItems = computed(() => {
const item = currentAccount.value;
if (!item) return [];
return [
{ label: '成交销量', value: item.sales.toLocaleString() },
{ label: '销售额', value: ${item.amount.toLocaleString()}` },
{ label: '累计点赞', value: item.likes.toLocaleString() },
{ label: '投放消耗', value: ${item.cost.toLocaleString()}` },
];
});
const openDetail = (row: LiveAccount) => {
currentAccount.value = row;
detailVisible.value = true;
};
const syncAccount = (row: LiveAccount) => {
row.status = '已绑定';
row.canSync = false;
row.lastSync = '刚刚同步';
ElMessage.success(`${row.name} 已同步`);
};
const removeAccount = async (row: LiveAccount) => {
try {
await ElMessageBox.confirm(`确认删除直播号「${row.name}」吗?`, '删除确认', { type: 'warning' });
accounts.value = accounts.value.filter((item) => item.id !== row.id);
ElMessage.success('已删除直播号');
} catch {
ElMessage.info('已取消删除');
}
};
const confirmBind = () => {
const name = bindForm.name.trim() || '新绑定直播号';
const handle = bindForm.handle.trim() || `Live_${Date.now().toString().slice(-4)}`;
accounts.value.unshift({
id: Date.now(),
name,
handle,
lastSync: bindForm.status === '首次同步中' ? '等待首次同步' : '刚刚绑定',
rooms: 0,
likes: 0,
cost: 0,
sales: 0,
amount: 0,
roi: 0,
status: bindForm.status,
canSync: bindForm.status !== '已绑定',
roomsData: [],
});
bindVisible.value = false;
bindForm.name = '';
bindForm.handle = '';
bindForm.status = '已绑定';
ElMessage.success('直播号绑定成功');
};
const handlePageChange = () => {
// 当前为静态数据,预留分页事件用于后续接接口。
};
</script>
<style scoped lang="scss">
.page-header {
border-bottom: 1px solid #dfe7f2;
}
.summary-list {
border: 1px solid #dfe7f2;
}
.summary-card {
border-right: 1px solid #edf0f5;
&:last-child {
border-right: none;
}
}
.account-table-wrap {
border: 1px solid #dfe7f2;
}
.account-table {
:deep(.el-table__header th) {
background: #f5f5f5;
color: #888;
padding: 12px 0;
}
:deep(.el-table__row) {
height: 74px;
}
}
.live-icon {
flex-shrink: 0;
background: linear-gradient(135deg, #9d19ff, #7c3aed);
}
.status-tag {
display: inline-block;
&.success {
color: #00875a;
background: #e7fbf1;
}
&.warning {
color: #d97706;
background: #fff7e6;
}
&.syncing {
color: #8a22fd;
background: #fbf0ff;
}
}
.detail-metrics {
min-width: 0;
}
.detail-metric {
border: 1px solid #dfe7f2;
}
.detail-room-table {
border: 1px solid #dfe7f2;
border-radius: 8px;
overflow: hidden;
:deep(.el-table__header th) {
background: #f8fbff;
color: #8aa0bf;
font-weight: 700;
}
}
.room-status {
display: inline-block;
&.live {
color: #00875a;
background: #e7fbf1;
}
&.ended {
color: #48607f;
background: #f1f5f9;
}
}
.minw-0 {
min-width: 0;
}
.color-green {
color: #00a86b;
}
.color-orange {
color: #f59e0b;
}
.color-red,
.like-text {
color: #ff0055;
}
.dialog-header,
.dialog-footer {
border-top: 1px solid #edf0f5;
}
.dialog-header {
border-top: none;
border-bottom: 1px solid #edf0f5;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment