This commit is contained in:
xiaocp2009 2025-09-04 13:24:01 +08:00
parent a60e56d4c8
commit 2895ee40c0
4 changed files with 1335 additions and 67 deletions

View File

@ -390,7 +390,7 @@ const local: App.I18n.Schema = {
home: {
branchDesc:
'为了方便大家开发和更新合并我们对main分支的代码进行了精简只保留了首页菜单其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
greeting: '早安{userName}, 今天又是充满活力的一天!',
greeting: '您好{userName}, 今天又是充满活力的一天!',
weatherDesc: '今日多云转晴20℃ - 25℃!',
projectCount: '项目数',
todo: '待办',

View File

@ -1,68 +1,272 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useAppStore } from '@/store/modules/app';
import HeaderBanner from './modules/header-banner.vue';
import CardData from './modules/card-data.vue';
import CardDataSummary from './modules/card-data-summary.vue';
import CardDataMkt from './modules/card-data-mkt.vue';
import LineChart from './modules/line-chart.vue';
import LineChartSummary from './modules/line-chart-summary.vue';
import LineChartMkt from './modules/line-chart-mkt.vue';
import PieChart from './modules/pie-chart.vue';
import PieChartSummary from './modules/pie-chart-summary.vue';
import PieChartMkt from './modules/pie-chart-mkt.vue';
import ProjectNews from './modules/project-news.vue';
import CreativityBanner from './modules/creativity-banner.vue';
import { useAuthStore } from "@/store/modules/auth";
import dayjs from 'dayjs';
import { computed, onMounted, ref, watch, h } from 'vue' // h
import { useAppStore } from '@/store/modules/app'
import HeaderBanner from './modules/header-banner.vue'
//import CardData from './modules/card-data.vue'
import CardDataSummary from './modules/card-data-summary.vue'
import CardDataMkt from './modules/card-data-mkt.vue'
//import ProjectNews from './modules/project-news.vue'
//import CreativityBanner from './modules/creativity-banner.vue'
import { useAuthStore } from "@/store/modules/auth"
import {
NButton,
NCard,
NStatistic,
NDataTable,
NTag,
NDivider,
NH1,
NH2,
NP,
NNumberAnimation,
NProgress,
NGrid,
NGi,
NSpace,
NIcon,
NThing
} from 'naive-ui'
const appStore = useAppStore();
const { userInfo } = useAuthStore();
import dayjs from 'dayjs'
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const appStore = useAppStore()
const { userInfo } = useAuthStore()
const gap = computed(() => (appStore.isMobile ? 0 : 16))
//
let isAdmin = ref(false);
//
const dateRange = ref<[number, number]>([
dayjs().startOf('month').valueOf(), //
dayjs().endOf('month').valueOf() //
]);
])
//
const handleDateChange = (range: [number, number]) => {
dateRange.value = range;
};
dateRange.value = range
}
//
const setDateRange = (type: 'today' | 'week' | 'month') => {
let start, end;
let start, end
switch (type) {
case 'today':
start = dayjs().startOf('day');
end = dayjs().endOf('day');
break;
start = dayjs().startOf('day')
end = dayjs().endOf('day')
break
case 'week':
start = dayjs().startOf('week');
end = dayjs().endOf('week');
break;
start = dayjs().startOf('week')
end = dayjs().endOf('week')
break
case 'month':
start = dayjs().startOf('month');
end = dayjs().endOf('month');
break;
start = dayjs().startOf('month')
end = dayjs().endOf('month')
break
default:
start = dayjs().startOf('day');
end = dayjs().endOf('day');
start = dayjs().startOf('day')
end = dayjs().endOf('day')
}
dateRange.value = [start.valueOf(), end.valueOf()];
};
dateRange.value = [start.valueOf(), end.valueOf()]
}
//
/*onMounted(() => {
console.log(JSON.stringify(userInfo));
console.log("是否营销:" + userInfo.user.userCategory);
console.log("营销编号:" + userInfo.user.mktNo);
});*/
//
/*const marketingData = ref({
performance: {
totalAmount: 128456.78,
completedProjects: 24,
pendingProjects: 5,
successRate: 92.5,
growthRate: 12.5
},
pricingResults: [
{ id: 1, project: '品牌推广活动', amount: 24500.00, status: '已完成', date: '2023-10-15' },
{ id: 2, project: '社交媒体营销', amount: 18700.50, status: '进行中', date: '2023-10-18' },
{ id: 3, project: '季度促销活动', amount: 35600.00, status: '待审核', date: '2023-10-20' },
{ id: 4, project: '新产品发布会', amount: 28900.00, status: '已完成', date: '2023-10-22' }
],
errorResults: [
{ id: 1, project: '品牌推广活动', errorType: '数据不一致', status: '已处理', date: '2023-10-16' },
{ id: 2, project: '社交媒体营销', errorType: '计算错误', status: '待处理', date: '2023-10-19' },
{ id: 3, project: '季度促销活动', errorType: '规则冲突', status: '处理中', date: '2023-10-21' }
]
})*/
// ,
// ,
const adminData = ref({
// 4+
pricingSummary: [
],
// 5
recentActivities: [
]
})
//
/*const pricingColumns = [
{
title: '项目名称',
key: 'project',
render: (row: any) => {
return h('div', { class: 'project-cell' }, [
h('div', { class: 'project-name' }, row.project),
h('div', { class: 'project-date' }, row.date)
])
}
},
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-cell' }, row.amount.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}))
}
},
{
title: '状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已完成' ? 'success' :
row.status === '进行中' ? 'warning' : 'default'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]*/
//
/*const errorColumns = [
{ title: '项目名称', key: 'project' },
{ title: '差错类型', key: 'errorType' },
{
title: '处理状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已处理' ? 'success' :
row.status === '处理中' ? 'warning' : 'error'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]*/
//
const summaryColumns = [
{ title: '业务类别', key: 'category' },
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-with-trend' }, [
h('span', row.amount.toLocaleString('zh-CN')),
h(NIcon, {
size: '16',
color: row.trend === 'up' ? '#18a058' : '#d03050',
style: { 'margin-left': '8px' }
}, { default: () => '元' })
])
}
},
{
title: '占比',
key: 'percentage',
render: (row: any) => {
return h(NProgress, {
type: 'line',
percentage: row.percentage,
'indicator-placement': 'inside',
color: row.trend === 'up' ? '#18a058' : '#d03050'
})
}
}
]
//
const activityColumns = [
{
title: '用户',
key: 'user',
render: (row: any) => {
return h('div', { class: 'user-cell' }, [
/*h(NIcon, { size: '16', style: { 'margin-right': '8px' } }, { default: () => 'USER' }),*/
/*h(NIcon, { size: '16', style: { 'margin-right': '8px' } }),*/
h('span', row.user)
])
}
},
{ title: '操作', key: 'action' },
{
title: '时间',
key: 'time',
render: (row: any) => {
return h('div', { class: 'time-cell' }, [
/*h(NIcon, { size: '14', style: { 'margin-right': '6px' } }, { default: () => 'TIME' }),*/
h('span', row.time)
])
}
}
]
//
onMounted(() => {
//console.log(JSON.stringify(userInfo));
//console.log(":" + userInfo.user.userCategory);
//console.log(":" + userInfo.user.mktNo);
//console.log(":" + userInfo.dept.deptCategory);
//
isAdmin =
(
userInfo.dept.deptCategory === '251009999'
|| userInfo.dept.deptCategory === '251000099'
|| userInfo.dept.deptId === '100'
);
// ,,mock
if(isAdmin.value){
adminData.value.pricingSummary = [
{ category: '智E通业务', amount: 245600.00, percentage: 42, trend: 'up', growth: 8.2 },
{ category: '综合收单业务', amount: 187430.50, percentage: 32, trend: 'up', growth: 12.5 },
{ category: '代收业务', amount: 151209.06, percentage: 26, trend: 'down', growth: -3.4 },
{ category: '社保卡业务', amount: 151209.06, percentage: 26, trend: 'down', growth: -3.4 },
{ category: '其他', amount: 151209.06, percentage: 26, trend: 'down', growth: -3.4 }
];
adminData.value.recentActivities = [
{ user: '王凯', action: '录入营销数据', time: '5分钟前', type: 'update' },
{ user: '李延玲', action: '录入营销数据', time: '30分钟前', type: 'import' },
{ user: '柏学慧', action: '新增业务类型', time: '2小时前', type: 'submit' },
{ user: '吴新宇', action: '修改分成比例', time: '昨天', type: 'review' },
{ user: '赵清惠', action: '录入营销数据', time: '7天前', type: 'review' }
]
}else{// ,,mock
adminData.value.pricingSummary = [
{ category: '智E通业务', amount: 24560.00, percentage: 42, trend: 'up', growth: 8.2 },
{ category: '综合收单业务', amount: 18743.50, percentage: 32, trend: 'up', growth: 12.5 },
{ category: '代收业务', amount: 15120.06, percentage: 26, trend: 'down', growth: -3.4 },
{ category: '社保卡业务', amount: 15129.06, percentage: 26, trend: 'down', growth: -3.4 },
{ category: '其他', amount: 15129.06, percentage: 26, trend: 'down', growth: -3.4 }
];
adminData.value.recentActivities = [
{ user: userInfo.user.nickName, action: '录入营销数据', time: '5分钟前', type: 'update' },
{ user: userInfo.user.nickName, action: '录入营销数据', time: '30分钟前', type: 'import' },
{ user: userInfo.user.nickName, action: '新增业务类型', time: '2小时前', type: 'submit' },
{ user: userInfo.user.nickName, action: '修改分成比例', time: '昨天', type: 'review' },
{ user: userInfo.user.nickName, action: '录入营销数据', time: '7天前', type: 'review' }
]
}
});
</script>
<template>
@ -83,32 +287,138 @@ const setDateRange = (type: 'today' | 'week' | 'month') => {
@date-change="handleDateChange"
@quick-change="setDateRange"/>
<!-- 线图bar图 -->
<!-- 替换线图和饼图部分为数据卡片 -->
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:14">
<NCard :bordered="false" class="card-wrapper">
<LineChartMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
/>
<LineChartSummary
v-else
:date-range="dateRange"/>
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard :bordered="false" class="card-wrapper">
<PieChartMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
/>
<PieChartSummary
v-else
:date-range="dateRange"/>
</NCard>
</NGi>
<!-- 营销用户视图 -->
<!-- <template v-if="userInfo.user.userCategory === '0'">
<NGi span="24 s:24 m:14">
<NCard title="计价结果" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="pricingColumns"
:data="marketingData.pricingResults"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
查看全部
</NButton>
</template>
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard title="差错分析" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="errorColumns"
:data="marketingData.errorResults"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
处理差错
</NButton>
</template>
</NCard>
</NGi>
</template>-->
<!-- 管理用户视图 -->
<!-- <template v-else>-->
<!-- <template v-if="userInfo.user.userCategory === '9'">-->
<template v-if="userInfo.user">
<NGi span="24 s:24 m:14">
<NCard title="计价汇总" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="summaryColumns"
:data="adminData.pricingSummary"
:bordered="false"
/>
<!-- <template #header-extra>
<NButton text type="primary">
导出数据
</NButton>
</template>-->
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard title="最近活动" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="activityColumns"
:data="adminData.recentActivities"
:bordered="false"
/>
<!-- <template #header-extra>
<NButton text type="primary">
查看全部
</NButton>
</template>-->
</NCard>
</NGi>
</template>
</NGrid>
<!-- 额外信息卡片 -->
<!-- <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:12">
<NCard title="项目动态" :bordered="false" class="card-wrapper" size="small">
<ProjectNews />
</NCard>
</NGi>
<NGi span="24 s:24 m:12">
<NCard title="创意营销" :bordered="false" class="card-wrapper" size="small">
<CreativityBanner />
</NCard>
</NGi>
</NGrid>-->
</NSpace>
</template>
<style scoped></style>
<style scoped>
.card-wrapper {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
:deep(.n-card__content) {
padding: 16px;
}
:deep(.n-data-table) .n-data-table-th {
background-color: #f9fafb;
font-weight: 600;
}
:deep(.n-data-table) .n-data-table-td {
padding: 12px 16px;
}
.project-cell {
display: flex;
flex-direction: column;
}
.project-name {
font-weight: 500;
}
.project-date {
font-size: 12px;
color: #6b7280;
}
.amount-cell {
font-weight: 600;
color: #1f2937;
}
.amount-with-trend {
display: flex;
align-items: center;
font-weight: 600;
color: #1f2937;
}
.user-cell, .time-cell {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,571 @@
<script setup lang="ts">
import { computed, onMounted, ref, h } from 'vue'
import { useAuthStore } from '@/store/modules/auth'
import { NButton, NCard, NStatistic, NDataTable, NTag, NDivider, NH1, NH2, NP, NNumberAnimation, NProgress, NGrid, NGi, NSpace, NIcon } from 'naive-ui'
import dayjs from 'dayjs'
const authStore = useAuthStore()
const { userInfo } = authStore
// 用户角色判断
const isMarketingUser = computed(() => userInfo?.user?.userCategory === '9')
const isAdminUser = computed(() => userInfo?.user?.userCategory === '9')
// 营销用户数据
const marketingData = ref({
performance: {
totalAmount: 128456.78,
completedProjects: 24,
pendingProjects: 5,
successRate: 92.5
},
pricingResults: [
{ id: 1, project: '品牌推广活动', amount: 24500.00, status: '已完成', date: '2023-10-15' },
{ id: 2, project: '社交媒体营销', amount: 18700.50, status: '进行中', date: '2023-10-18' },
{ id: 3, project: '季度促销活动', amount: 35600.00, status: '待审核', date: '2023-10-20' },
{ id: 4, project: '新产品发布会', amount: 28900.00, status: '已完成', date: '2023-10-22' }
],
errorResults: [
{ id: 1, project: '品牌推广活动', errorType: '数据不一致', status: '已处理', date: '2023-10-16' },
{ id: 2, project: '社交媒体营销', errorType: '计算错误', status: '待处理', date: '2023-10-19' },
{ id: 3, project: '季度促销活动', errorType: '规则冲突', status: '处理中', date: '2023-10-21' }
]
})
// 管理用户数据
const adminData = ref({
systemOverview: {
totalUsers: 42,
activeProjects: 18,
totalRevenue: 584239.56,
systemLoad: 78.2,
errorRate: 5.3
},
pricingSummary: [
{ category: '标准套餐', amount: 245600.00, percentage: 42, trend: 'up' },
{ category: '高级套餐', amount: 187430.50, percentage: 32, trend: 'up' },
{ category: '定制方案', amount: 151209.06, percentage: 26, trend: 'down' }
],
recentActivities: [
{ user: '张经理', action: '更新了计价规则', time: '2小时前', type: 'update' },
{ user: '李主管', action: '导入了新数据', time: '5小时前', type: 'import' },
{ user: '王营销', action: '提交了营销数据', time: '昨天', type: 'submit' },
{ user: '赵助理', action: '审核了定价方案', time: '昨天', type: 'review' }
]
})
// 计价结果表格列
const pricingColumns = [
{
title: '项目名称',
key: 'project',
render: (row: any) => {
return h('div', { class: 'project-cell' }, [
h('div', { class: 'project-name' }, row.project),
h('div', { class: 'project-date' }, row.date)
])
}
},
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-cell' }, row.amount.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}))
}
},
{
title: '状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已完成' ? 'success' :
row.status === '进行中' ? 'warning' : 'default'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]
// 差错结果表格列
const errorColumns = [
{ title: '项目名称', key: 'project' },
{ title: '差错类型', key: 'errorType' },
{
title: '处理状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已处理' ? 'success' :
row.status === '处理中' ? 'warning' : 'error'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]
// 汇总数据表格列
const summaryColumns = [
{ title: '套餐类别', key: 'category' },
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-with-trend' }, [
h('span', row.amount.toLocaleString('zh-CN')),
h(NIcon, {
size: '16',
color: row.trend === 'up' ? '#18a058' : '#d03050',
style: { 'margin-left': '8px' }
}, { default: () => h(row.trend === 'up' ? TrendingUp : TrendingUp) })
])
}
},
{
title: '占比',
key: 'percentage',
render: (row: any) => {
return h(NProgress, {
type: 'line',
percentage: row.percentage,
'indicator-placement': 'inside',
color: row.trend === 'up' ? '#18a058' : '#d03050'
})
}
}
]
// 活动记录表格列
const activityColumns = [
{
title: '用户',
key: 'user',
render: (row: any) => {
return h('div', { class: 'user-cell' }, [
h(NIcon, { size: '16', style: { 'margin-right': '8px' } }, { default: () => h(Users) }),
h('span', row.user)
])
}
},
{ title: '操作', key: 'action' },
{
title: '时间',
key: 'time',
render: (row: any) => {
return h('div', { class: 'time-cell' }, [
h(NIcon, { size: '14', style: { 'margin-right': '6px' } }, { default: () => h(Time) }),
h('span', row.time)
])
}
}
]
// 初始设置为本月范围
const dateRange = ref<[number, number]>([
dayjs().startOf('month').valueOf(),
dayjs().endOf('month').valueOf()
])
// 处理日期变化
const handleDateChange = (range: [number, number]) => {
dateRange.value = range
}
// 设置快速日期范围
const setDateRange = (type: 'today' | 'week' | 'month') => {
let start, end
switch (type) {
case 'today':
start = dayjs().startOf('day')
end = dayjs().endOf('day')
break
case 'week':
start = dayjs().startOf('week')
end = dayjs().endOf('week')
break
case 'month':
start = dayjs().startOf('month')
end = dayjs().endOf('month')
break
default:
start = dayjs().startOf('day')
end = dayjs().endOf('day')
}
dateRange.value = [start.valueOf(), end.valueOf()]
}
</script>
<template>
<div class="dashboard-container">
<!-- 欢迎横幅 -->
<n-card class="welcome-card" :bordered="false">
<div class="header">
<div>
<n-h1 style="margin: 0; color: #1f2937;">营销计价系统</n-h1>
<n-p style="margin: 8px 0 0; color: #6b7280; font-size: 16px;">
{{ isMarketingUser ? '高效管理您的营销数据和计价结果' : '全面掌控系统数据和运营状况' }}
</n-p>
</div>
<n-tag type="primary" round size="large">
{{ isMarketingUser ? '营销用户' : '管理用户' }}
</n-tag>
</div>
</n-card>
<!-- 营销用户视图 -->
<template v-if="isMarketingUser">
<div class="section-header">
<n-h2 style="margin: 0;">营销工作台</n-h2>
<n-p style="margin: 8px 0 0; color: #6b7280;">欢迎回来!以下是您的营销数据概览和计价结果。</n-p>
</div>
<n-grid :cols="4" :x-gap="16" :y-gap="16" class="stats-grid">
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="总营销金额">
<n-number-animation :from="0" :to="marketingData.performance.totalAmount" />
<template #suffix>¥</template>
</n-statistic>
<n-space align="center">
<n-icon :component="TrendingUp" color="#18a058" size="20" />
<span style="color: #18a058; font-size: 14px;">+12.5%</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="完成项目数">
<n-number-animation :from="0" :to="marketingData.performance.completedProjects" />
</n-statistic>
<n-space align="center">
<n-icon :component="CheckmarkCircle" color="#18a058" size="20" />
<span style="color: #18a058; font-size: 14px;">92.5% 成功率</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="待处理项目">
<n-number-animation :from="0" :to="marketingData.performance.pendingProjects" />
</n-statistic>
<n-space align="center">
<n-icon :component="Time" color="#f0a020" size="20" />
<span style="color: #f0a020; font-size: 14px;">需要关注</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="差错项目">
<n-number-animation :from="0" :to="marketingData.errorResults.length" />
</n-statistic>
<n-space align="center">
<n-icon :component="AlertCircle" color="#d03050" size="20" />
<span style="color: #d03050; font-size: 14px;">{{ marketingData.errorResults.filter(e => e.status === '待处理').length }} 个待处理</span>
</n-space>
</n-space>
</n-card>
</n-gi>
</n-grid>
<n-grid :cols="2" :x-gap="16" :y-gap="16" class="data-grid">
<n-gi>
<n-card title="计价结果" class="data-card" size="small">
<n-data-table
:columns="pricingColumns"
:data="marketingData.pricingResults"
:bordered="false"
/>
</n-card>
</n-gi>
<n-gi>
<n-card title="差错分析" class="data-card" size="small">
<n-data-table
:columns="errorColumns"
:data="marketingData.errorResults"
:bordered="false"
/>
</n-card>
</n-gi>
</n-grid>
</template>
<!-- 管理用户视图 -->
<template v-else-if="isAdminUser">
<div class="section-header">
<n-h2 style="margin: 0;">管理控制台</n-h2>
<n-p style="margin: 8px 0 0; color: #6b7280;">系统概览和关键指标汇总。</n-p>
</div>
<n-grid :cols="5" :x-gap="16" :y-gap="16" class="stats-grid">
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="系统用户数">
<n-number-animation :from="0" :to="adminData.systemOverview.totalUsers" />
</n-statistic>
<n-space align="center">
<n-icon :component="Users" color="#2080f0" size="20" />
<span style="color: #2080f0; font-size: 14px;">+3 本月新增</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="活跃项目数">
<n-number-animation :from="0" :to="adminData.systemOverview.activeProjects" />
</n-statistic>
<n-space align="center">
<n-icon :component="Activity" color="#18a058" size="20" />
<span style="color: #18a058; font-size: 14px;">78% 完成率</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="总收入">
<n-number-animation :from="0" :to="adminData.systemOverview.totalRevenue" />
<template #suffix>¥</template>
</n-statistic>
<n-space align="center">
<n-icon :component="TrendingUp" color="#18a058" size="20" />
<span style="color: #18a058; font-size: 14px;">+18.2%</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="系统负载">
<n-number-animation :from="0" :to="adminData.systemOverview.systemLoad" />
<template #suffix>%</template>
</n-statistic>
<n-space align="center">
<n-icon :component="Activity" color="#f0a020" size="20" />
<span style="color: #f0a020; font-size: 14px;">运行正常</span>
</n-space>
</n-space>
</n-card>
</n-gi>
<n-gi>
<n-card class="stat-card" content-style="padding: 20px;">
<n-space vertical>
<n-statistic label="差错率">
<n-number-animation :from="0" :to="adminData.systemOverview.errorRate" />
<template #suffix>%</template>
</n-statistic>
<n-space align="center">
<n-icon :component="AlertCircle" color="#d03050" size="20" />
<span style="color: #d03050; font-size: 14px;">需关注</span>
</n-space>
</n-space>
</n-card>
</n-gi>
</n-grid>
<n-grid :cols="2" :x-gap="16" :y-gap="16" class="data-grid">
<n-gi>
<n-card title="计价汇总" class="data-card" size="small">
<n-data-table
:columns="summaryColumns"
:data="adminData.pricingSummary"
:bordered="false"
/>
<div class="chart-container">
<n-p>收入分布图表可视化</n-p>
</div>
</n-card>
</n-gi>
<n-gi>
<n-card title="最近活动" class="data-card" size="small">
<n-data-table
:columns="activityColumns"
:data="adminData.recentActivities"
:bordered="false"
/>
<div class="quick-actions">
<n-space>
<n-button type="primary" tertiary>
导入数据
</n-button>
<n-button type="info" tertiary>
规则管理
</n-button>
<n-button type="success" tertiary>
生成报告
</n-button>
</n-space>
</div>
</n-card>
</n-gi>
</n-grid>
</template>
</div>
</template>
<style scoped>
.dashboard-container {
padding: 24px;
max-width: 1400px;
margin: 0 auto;
background-color: #f8fafc;
min-height: 100vh;
}
.welcome-card {
margin-bottom: 24px;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.welcome-card :deep(.n-card__content) {
padding: 24px;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.section-header {
margin-bottom: 24px;
}
.stats-grid {
margin-bottom: 24px;
}
.stat-card {
border-radius: 12px;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.data-grid {
margin-bottom: 24px;
}
.data-card {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
height: 100%;
}
.data-card :deep(.n-card-header) {
border-bottom: 1px solid #f1f5f9;
padding: 16px 20px;
}
.data-card :deep(.n-card__content) {
padding: 20px;
}
.chart-container {
height: 200px;
background-color: #f9fafb;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
color: #6b7280;
}
.quick-actions {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #f1f5f9;
}
.project-cell {
display: flex;
flex-direction: column;
}
.project-name {
font-weight: 500;
}
.project-date {
font-size: 12px;
color: #6b7280;
}
.amount-cell {
font-weight: 600;
color: #1f2937;
}
.amount-with-trend {
display: flex;
align-items: center;
font-weight: 600;
color: #1f2937;
}
.user-cell, .time-cell {
display: flex;
align-items: center;
}
:deep(.n-data-table) .n-data-table-th {
background-color: #f9fafb;
font-weight: 600;
}
:deep(.n-data-table) .n-data-table-td {
padding: 12px 16px;
}
:deep(.n-statistic .n-statistic-value) {
font-size: 24px;
font-weight: 700;
}
:deep(.n-statistic .n-statistic-label) {
font-size: 14px;
color: #6b7280;
margin-bottom: 8px;
}
</style>

View File

@ -0,0 +1,387 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch, h } from 'vue' // 添加 h 导入
import { useAppStore } from '@/store/modules/app'
import HeaderBanner from './modules/header-banner.vue'
import CardData from './modules/card-data.vue'
import CardDataSummary from './modules/card-data-summary.vue'
import CardDataMkt from './modules/card-data-mkt.vue'
import ProjectNews from './modules/project-news.vue'
import CreativityBanner from './modules/creativity-banner.vue'
import { useAuthStore } from "@/store/modules/auth"
import {
NButton,
NCard,
NStatistic,
NDataTable,
NTag,
NDivider,
NH1,
NH2,
NP,
NNumberAnimation,
NProgress,
NGrid,
NGi,
NSpace,
NIcon,
NThing
} from 'naive-ui'
import dayjs from 'dayjs'
const appStore = useAppStore()
const { userInfo } = useAuthStore()
const gap = computed(() => (appStore.isMobile ? 0 : 16))
// 初始设置为本月范围
const dateRange = ref<[number, number]>([
dayjs().startOf('month').valueOf(), // 本月第一天
dayjs().endOf('month').valueOf() // 本月最后一天
])
// 处理日期变化
const handleDateChange = (range: [number, number]) => {
dateRange.value = range
}
// 设置快速日期范围
const setDateRange = (type: 'today' | 'week' | 'month') => {
let start, end
switch (type) {
case 'today':
start = dayjs().startOf('day')
end = dayjs().endOf('day')
break
case 'week':
start = dayjs().startOf('week')
end = dayjs().endOf('week')
break
case 'month':
start = dayjs().startOf('month')
end = dayjs().endOf('month')
break
default:
start = dayjs().startOf('day')
end = dayjs().endOf('day')
}
dateRange.value = [start.valueOf(), end.valueOf()]
}
// 营销用户数据
const marketingData = ref({
performance: {
totalAmount: 128456.78,
completedProjects: 24,
pendingProjects: 5,
successRate: 92.5,
growthRate: 12.5
},
pricingResults: [
{ id: 1, project: '品牌推广活动', amount: 24500.00, status: '已完成', date: '2023-10-15' },
{ id: 2, project: '社交媒体营销', amount: 18700.50, status: '进行中', date: '2023-10-18' },
{ id: 3, project: '季度促销活动', amount: 35600.00, status: '待审核', date: '2023-10-20' },
{ id: 4, project: '新产品发布会', amount: 28900.00, status: '已完成', date: '2023-10-22' }
],
errorResults: [
{ id: 1, project: '品牌推广活动', errorType: '数据不一致', status: '已处理', date: '2023-10-16' },
{ id: 2, project: '社交媒体营销', errorType: '计算错误', status: '待处理', date: '2023-10-19' },
{ id: 3, project: '季度促销活动', errorType: '规则冲突', status: '处理中', date: '2023-10-21' }
]
})
// 管理用户数据
const adminData = ref({
systemOverview: {
totalUsers: 42,
activeProjects: 18,
totalRevenue: 584239.56,
systemLoad: 78.2,
errorRate: 5.3
},
pricingSummary: [
{ category: '标准套餐', amount: 245600.00, percentage: 42, trend: 'up', growth: 8.2 },
{ category: '高级套餐', amount: 187430.50, percentage: 32, trend: 'up', growth: 12.5 },
{ category: '定制方案', amount: 151209.06, percentage: 26, trend: 'down', growth: -3.4 }
],
recentActivities: [
{ user: '张经理', action: '更新了计价规则', time: '2小时前', type: 'update' },
{ user: '李主管', action: '导入了新数据', time: '5小时前', type: 'import' },
{ user: '王营销', action: '提交了营销数据', time: '昨天', type: 'submit' },
{ user: '赵助理', action: '审核了定价方案', time: '昨天', type: 'review' }
]
})
// 计价结果表格列
const pricingColumns = [
{
title: '项目名称',
key: 'project',
render: (row: any) => {
return h('div', { class: 'project-cell' }, [
h('div', { class: 'project-name' }, row.project),
h('div', { class: 'project-date' }, row.date)
])
}
},
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-cell' }, row.amount.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}))
}
},
{
title: '状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已完成' ? 'success' :
row.status === '进行中' ? 'warning' : 'default'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]
// 差错结果表格列
const errorColumns = [
{ title: '项目名称', key: 'project' },
{ title: '差错类型', key: 'errorType' },
{
title: '处理状态',
key: 'status',
render: (row: any) => {
const type = row.status === '已处理' ? 'success' :
row.status === '处理中' ? 'warning' : 'error'
return h(NTag, {
type: type,
size: 'small',
round: true
}, { default: () => row.status })
}
}
]
// 汇总数据表格列
const summaryColumns = [
{ title: '套餐类别', key: 'category' },
{
title: '金额 (¥)',
key: 'amount',
render: (row: any) => {
return h('div', { class: 'amount-with-trend' }, [
h('span', row.amount.toLocaleString('zh-CN')),
h(NIcon, {
size: '16',
color: row.trend === 'up' ? '#18a058' : '#d03050',
style: { 'margin-left': '8px' }
}, { default: () => '123' })
])
}
},
{
title: '占比',
key: 'percentage',
render: (row: any) => {
return h(NProgress, {
type: 'line',
percentage: row.percentage,
'indicator-placement': 'inside',
color: row.trend === 'up' ? '#18a058' : '#d03050'
})
}
}
]
// 活动记录表格列
const activityColumns = [
{
title: '用户',
key: 'user',
render: (row: any) => {
return h('div', { class: 'user-cell' }, [
h(NIcon, { size: '16', style: { 'margin-right': '8px' } }, { default: () => 'USER' }),
h('span', row.user)
])
}
},
{ title: '操作', key: 'action' },
{
title: '时间',
key: 'time',
render: (row: any) => {
return h('div', { class: 'time-cell' }, [
h(NIcon, { size: '14', style: { 'margin-right': '6px' } }, { default: () => 'TIME' }),
h('span', row.time)
])
}
}
]
// 测试登录用户信息
/*onMounted(() => {
console.log(JSON.stringify(userInfo));
console.log("是否营销:" + userInfo.user.userCategory);
console.log("营销编号:" + userInfo.user.mktNo);
});*/
</script>
<template>
<NSpace vertical :size="16">
<!-- 欢迎banner -->
<HeaderBanner />
<!-- 数据卡 - 营销人员和其他人员看到的图表不同 -->
<CardDataMkt
v-if="userInfo.user.userCategory === '0'"
:date-range="dateRange"
@date-change="handleDateChange"
@quick-change="setDateRange"
/>
<CardDataSummary
v-else
:date-range="dateRange"
@date-change="handleDateChange"
@quick-change="setDateRange"/>
<!-- 替换线图和饼图部分为数据卡片 -->
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<!-- 营销用户视图 -->
<template v-if="userInfo.user.userCategory === '0'">
<NGi span="24 s:24 m:14">
<NCard title="计价结果" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="pricingColumns"
:data="marketingData.pricingResults"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
查看全部
</NButton>
</template>
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard title="差错分析" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="errorColumns"
:data="marketingData.errorResults"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
处理差错
</NButton>
</template>
</NCard>
</NGi>
</template>
<!-- 管理用户视图 -->
<template v-else>
<NGi span="24 s:24 m:14">
<NCard title="计价汇总" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="summaryColumns"
:data="adminData.pricingSummary"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
导出数据
</NButton>
</template>
</NCard>
</NGi>
<NGi span="24 s:24 m:10">
<NCard title="最近活动" :bordered="false" class="card-wrapper" size="small">
<NDataTable
:columns="activityColumns"
:data="adminData.recentActivities"
:bordered="false"
/>
<template #header-extra>
<NButton text type="primary">
查看全部
</NButton>
</template>
</NCard>
</NGi>
</template>
</NGrid>
<!-- 额外信息卡片 -->
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:24 m:12">
<NCard title="项目动态" :bordered="false" class="card-wrapper" size="small">
<ProjectNews />
</NCard>
</NGi>
<NGi span="24 s:24 m:12">
<NCard title="创意营销" :bordered="false" class="card-wrapper" size="small">
<CreativityBanner />
</NCard>
</NGi>
</NGrid>
</NSpace>
</template>
<style scoped>
.card-wrapper {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
:deep(.n-card__content) {
padding: 16px;
}
:deep(.n-data-table) .n-data-table-th {
background-color: #f9fafb;
font-weight: 600;
}
:deep(.n-data-table) .n-data-table-td {
padding: 12px 16px;
}
.project-cell {
display: flex;
flex-direction: column;
}
.project-name {
font-weight: 500;
}
.project-date {
font-size: 12px;
color: #6b7280;
}
.amount-cell {
font-weight: 600;
color: #1f2937;
}
.amount-with-trend {
display: flex;
align-items: center;
font-weight: 600;
color: #1f2937;
}
.user-cell, .time-cell {
display: flex;
align-items: center;
}
</style>