This commit is contained in:
2025-10-17 10:31:13 +08:00
commit e6e86f2ce0
1043 changed files with 1031839 additions and 0 deletions

384
src/views/Home/Index.vue Normal file
View File

@@ -0,0 +1,384 @@
<template>
<div>
<el-card shadow="never">
<el-skeleton :loading="loading" animated>
<el-row :gutter="16" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<el-avatar :src="avatar" :size="70" class="mr-16px">
<img src="@/assets/imgs/avatar.jpg" alt="" />
</el-avatar>
<div>
<div class="text-20px">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.project"
:duration="2600"
/>
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.todo"
:duration="2600"
/>
</div>
<el-divider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div>
<el-row class="mt-8px" :gutter="8" justify="space-between">
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.project') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="8"
:sm="24"
:xs="24"
>
<el-card shadow="hover">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-8px" />
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-16px text-14px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-16px flex justify-between text-12px text-gray-400">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-8px">
<el-skeleton :loading="loading" animated>
<el-row :gutter="20" justify="space-between">
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="pieOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="barOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.shortcutOperation') }}</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-8px" />
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
{{ item.name }}
</el-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-8px">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.notice') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-center">
<el-avatar :src="avatar" :size="35" class="mr-16px">
<img src="@/assets/imgs/avatar.jpg" alt="" />
</el-avatar>
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">
{{ item.type }} : {{ item.title }}
</Highlight>
</div>
<div class="mt-16px text-12px text-gray-400">
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider />
</div>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { formatTime } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import { useWatermark } from '@/hooks/web/useWatermark'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { pieOptions, barOptions } from './echarts-data'
defineOptions({ name: 'Home' })
const { t } = useI18n()
const userStore = useUserStore()
const { setWatermark } = useWatermark()
const loading = ref(true)
const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
// 获取统计数
let totalSate = reactive<WorkplaceTotal>({
project: 0,
access: 0,
todo: 0
})
const getCount = async () => {
const data = {
project: 40,
access: 2340,
todo: 10
}
totalSate = Object.assign(totalSate, data)
}
// 获取项目数
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
// 获取通知公告
let notice = reactive<Notice[]>([])
const getNotice = async () => {
const data = [
{
title: '系统升级版本',
type: '通知',
keys: ['通知', '升级'],
date: new Date()
},
{
title: '系统凌晨维护',
type: '公告',
keys: ['公告', '维护'],
date: new Date()
},
{
title: '系统升级版本',
type: '通知',
keys: ['通知', '升级'],
date: new Date()
},
{
title: '系统凌晨维护',
type: '公告',
keys: ['公告', '维护'],
date: new Date()
}
]
notice = Object.assign(notice, data)
}
// 获取快捷入口
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
url: 'github.io'
},
{
name: 'Vue',
icon: 'logos:vue',
url: 'vuejs.org'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
url: 'https://vitejs.dev/'
},
{
name: 'Angular',
icon: 'logos:angular-icon',
url: 'github.io'
},
{
name: 'React',
icon: 'logos:react',
url: 'github.io'
},
{
name: 'Webpack',
icon: 'logos:webpack',
url: 'github.io'
}
]
shortcut = Object.assign(shortcut, data)
}
// 用户来源
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: t(v.name),
value: v.value
}
})
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
// 周活跃量
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
}
const getAllApi = async () => {
await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getUserAccessSource(),
getWeeklyUserActivity()
])
loading.value = false
}
getAllApi()
</script>

1800
src/views/Home/Index10.vue Normal file

File diff suppressed because it is too large Load Diff

466
src/views/Home/Index11.vue Normal file
View File

@@ -0,0 +1,466 @@
<template>
<div class="w-100%" v-loading="loading">
<div class="search-top flex justify-right">
<!-- <div class="search-top flex justify-between"> -->
<!-- 租户搜索 -->
<!-- <div>
<span class="text-13px c-#656565"> 租户名称</span>
<el-select
v-model="zhName"
placeholder="请选择"
size="large"
style="width: 264px"
@change="selectChange"
>
<el-option
v-for="item in zhOption"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div> -->
<!-- 切换日期 -->
<div class="flex">
<div class="w-200px grid grid-cols-3 mr-20px items-center">
<div
class="text-13px text-center cursor-pointer"
:style="{ color: btnActive == item.name ? '#692AFB' : '#656565' }"
v-for="item in btnList"
:key="item.type"
@click="btnClick(item)"
>
{{ item.name }}
</div>
</div>
<div>
<el-date-picker
v-model="pickerValue"
type="daterange"
range-separator="~"
start-placeholder="Start date"
end-placeholder="End date"
value-format="YYYY-MM-DD"
size="default"
:editable="false"
@change="pirckerChange"
/>
</div>
</div>
</div>
<div class="mt-20px">
<div
class="text-13px c-#333333 font-700 bg-white h-45px pl-20px flex items-center b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:b-#6B7280 .dark:c-#6B7280 .dark:bg-#1D1E1F"
>
登录次数/使用人数对比分析
</div>
<Echart :options="login_option" width="100%" height="423px" />
</div>
<div class="grid grid-cols-2 gap-x-20px mt-30px">
<div class="bg-#fff b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid .dark:bg-#1D1E1F">
<div
class="h-45px flex items-center text-13px c-#333333 font-700 pl-20px b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:c-#6B7280 .dark:b-#6B7280"
style="font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif"
>
使用人数最多的模块top5
</div>
<div class="p-20px">
<avue-crud :option="mostOption" :data="mostData"></avue-crud>
</div>
</div>
<div class="bg-#fff b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid">
<div
class="h-45px flex items-center text-13px c-#333333 font-700 pl-20px b-1px b-solid b-#E4E4E4 b-t-0 b-l-0 b-r-0 .dark:c-#6B7280 .dark:b-#6B7280"
style="font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif"
>
使用人数最少的模块bottom5
</div>
<div class="p-20px">
<avue-crud :option="mostOption" :data="leastData"></avue-crud>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/store/modules/app'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { getTableList, batchGetTableList } from '@/api/design/report'
import { formatDate } from '@/utils/formatTime'
import Icon from '@/components/Icon/src/Icon.vue'
defineOptions({ name: 'Home11' })
interface LoginXdata {
value: string | number
textStyle: {
color: string
fontSize: number
}
}
interface TabData {
mk: string
pm: number
syyhs: number
zzf: string
zzfFlag: number
}
const cardCellColor = ref('rgb(0 0 0 / 64.7%)')
const loading = ref(false)
const zhName = ref<string>('')
// 租户字典
const zhOption = ref([
// {
// value: 'Option1',
// label: 'Option1'
// }
])
// 租户字典的change
const selectChange = (val) => {
}
// 获取日期
let now = new Date()
const pickerValue = ref<Date | [Date, Date] | [string, string]>()
const dayArr = ref<[string, string]>()
const weekArr = ref<[string, string]>()
const monthArr = ref<[string, string]>()
const getDay = () => {
let day = formatDate(now, 'YYYY-MM-DD')
dayArr.value = [day, day]
}
const getWeek = () => {
var firstDayOfWeek = new Date(now)
firstDayOfWeek.setDate(firstDayOfWeek.getDate() - firstDayOfWeek.getDay() + 1)
var lastDayOfWeek = new Date(firstDayOfWeek)
lastDayOfWeek.setDate(firstDayOfWeek.getDate() + 6)
var first =
firstDayOfWeek.getFullYear() +
'-' +
String(firstDayOfWeek.getMonth() + 1).padStart(2, '0') +
'-' +
String(firstDayOfWeek.getDate()).padStart(2, '0')
var last =
lastDayOfWeek.getFullYear() +
'-' +
String(lastDayOfWeek.getMonth() + 1).padStart(2, '0') +
'-' +
String(lastDayOfWeek.getDate()).padStart(2, '0')
weekArr.value = [first, last]
}
const getMonth = () => {
let month = now.getMonth()
let year = now.getFullYear()
var first = formatDate(new Date(year, month, 1), 'YYYY-MM-DD')
var last = formatDate(new Date(year, month + 1, 0), 'YYYY-MM-DD')
monthArr.value = [first, last]
}
// 切换日期
const btnList = ref([
{ name: '本日', type: 1 },
{ name: '本周', type: 2 },
{ name: '本月', type: 3 }
])
const btnActive = ref('本月')
const btnClick = (val) => {
btnActive.value = val.name
if (val.type == 1) {
pickerValue.value = dayArr.value
} else if (val.type == 2) {
pickerValue.value = weekArr.value
} else if (val.type == 3) {
pickerValue.value = monthArr.value
}
}
const pirckerChange = (val) => {
btnActive.value = ''
}
// 登录分析柱形图
const loginSeries1 = ref<number[]>([])
const loginSeries2 = ref<number[]>([])
const loginX = ref<any>([])
const loginXutil = ref({
color: 'rgba(0, 0, 0, 0.647058823529412)',
fontSize: 14
})
const loginXdata = ref<LoginXdata[]>([])
const login_option = ref({
backgroundColor: 'white',
grid: { left: '3%', right: '4%', bottom: '10%', containLabel: true },
tooltip: { trigger: 'item' },
legend: {
left: 'center',
bottom: '3%',
itemGap: 20,
itemWidth: 34,
itemHeight: 13,
textStyle: {
color: '#656565'
},
data: ['登录次数', '使用人数']
},
xAxis: {
axisTick: {
alignWithLabel: true
},
data: loginXdata.value
},
yAxis: {
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: '#E8E8E8'
}
}
},
series: [
{
name: '登录次数',
type: 'bar',
color: 'rgba(105, 42, 251, 1)',
itemStyle: {
borderRadius: [25, 25, 0, 0]
},
barGap: 0,
data: loginSeries1.value
},
{
name: '使用人数',
type: 'bar',
color: 'rgba(54, 203, 203, 0.996078431372549)',
itemStyle: {
borderRadius: [25, 25, 0, 0]
},
data: loginSeries2.value
}
]
})
const mostOption = ref({
menu: false,
header: false,
border: false,
height: 350,
column: [
{
// headerAlign: 'right',
label: '排名',
prop: 'pm'
},
{
// headerAlign: 'left',
label: '模块',
prop: 'mk'
},
{
// headerAlign: 'left',
label: '使用用户数',
prop: 'syyhs'
},
{
label: '模块使用数',
prop: 'mksys'
}
// {
// headerAlign: 'right',
// label: '周涨幅',
// prop: 'zzf',
// render: ({ row }) => {
// let icon
// if (row.zzfFlag == 1) {
// icon = h(Icon, { icon: 'ep:top', size: 20, color: '#00A854' })
// } else {
// icon = h(Icon, { icon: 'ep:bottom', size: 20, color: '#F04134' })
// }
// return h(
// 'div',
// {
// style: { display: 'flex', alignItems: 'center', justifyContent: 'right' }
// },
// [h('span', { style: { marginRight: '5px' } }, row.zzf), icon]
// )
// }
// }
]
})
const mostData = ref<TabData[]>()
const leastData = ref<TabData[]>()
const appStore = useAppStore()
const init = async () => {
let oneres = await batchGetTableList('example_sytj_syzdmk,example_sytj_syzsmk')
mostData.value = oneres.example_sytj_syzdmk.records
leastData.value = oneres.example_sytj_syzsmk.records
}
onMounted(async () => {
getDay()
getWeek()
getMonth()
let item = btnList.value.find((r) => r.name == btnActive.value)
if (item) {
let arr = [dayArr.value, weekArr.value, monthArr.value]
pickerValue.value = arr[item.type - 1]
} else {
pickerValue.value = monthArr.value
}
await init()
// 判断是否为暗色模式
const { wsCache } = useCache()
if (wsCache.get(CACHE_KEY.IS_DARK)) {
cardCellColor.value = ''
login_option.value.backgroundColor = '#1D1E1F'
login_option.value.legend.textStyle.color = '#6B7280'
loginXdata.value.forEach((ele) => {
ele.textStyle.color = '#6B7280'
})
}
})
watch(
() => appStore.isDark,
(val) => {
if (val) {
cardCellColor.value = ''
login_option.value.backgroundColor = '#1D1E1F'
login_option.value.legend.textStyle.color = '#6B7280'
loginXdata.value.forEach((ele) => {
ele.textStyle.color = '#6B7280'
})
} else {
cardCellColor.value = 'rgb(0 0 0 / 64.7%)'
login_option.value.backgroundColor = 'white'
login_option.value.legend.textStyle.color = '#656565'
loginXdata.value.forEach((ele) => {
ele.textStyle.color = 'rgba(0, 0, 0, 0.647058823529412)'
})
}
}
)
watch(
() => pickerValue.value,
async (val) => {
if (!val) return
let date = val?.[0] + ',' + val?.[1]
loading.value = true
let res = await getTableList('example_sytj_dlsyrs', { sj: date })
let dlsyrs = res.records
if (dlsyrs && dlsyrs.length > 0) {
loginXdata.value = []
loginX.value = []
loginSeries1.value = []
loginSeries2.value = []
dlsyrs.forEach((ele) => {
loginX.value.push(ele.sj)
loginSeries1.value.push(ele.login_num)
loginSeries2.value.push(ele.users_num)
})
loginX.value.forEach((ele) => {
if (val?.[0] == val?.[1]) {
loginXdata.value.push({
value: ele + ':00',
textStyle: loginXutil.value
})
} else {
loginXdata.value.push({
value: ele,
textStyle: loginXutil.value
})
}
})
login_option.value.xAxis.data = loginXdata.value
login_option.value.series[0].data = loginSeries1.value
login_option.value.series[1].data = loginSeries2.value
loading.value = false
}
}
)
</script>
<style lang="scss" scoped>
h5,
p {
padding: 0;
margin: 0;
}
.search-top {
::v-deep(.el-select) {
.el-select__wrapper {
min-height: 32px;
}
}
::v-deep(.el-date-editor) {
width: 280px;
.el-range__icon {
font-size: 12px;
}
.el-range-input {
font-size: 12px;
color: #666;
}
.el-range-separator {
font-size: 12px;
color: #999;
}
}
}
::v-deep(.avue-crud) {
.el-form {
.el-table__inner-wrapper {
.el-table__header-wrapper {
.el-table__cell {
height: 57px;
.cell {
font-family: 'Microsoft Tai Le Bold', 'Microsoft Tai Le Regular', 'Microsoft Tai Le',
sans-serif;
color: v-bind('cardCellColor');
}
}
}
.el-table__body {
.el-table__row {
height: 57px;
&:last-child {
.el-table__cell {
border-bottom: none;
}
}
}
}
&::before {
height: 0;
}
}
}
}
</style>

319
src/views/Home/Index2.vue Normal file
View File

@@ -0,0 +1,319 @@
<template>
<el-row :class="prefixCls" :gutter="20" justify="space-between">
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:peoples" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.newUser') }}
</div>
<CountTo
:duration="2600"
:end-val="102400"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:message" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.unreadInformation') }}
</div>
<CountTo
:duration="2600"
:end-val="81212"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:money" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.transactionAmount') }}
</div>
<CountTo
:duration="2600"
:end-val="9280"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:shopping" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.totalShopping') }}
</div>
<CountTo
:duration="2600"
:end-val="13600"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" justify="space-between">
<el-col :lg="10" :md="24" :sm="24" :xl="10" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" animated>
<Echart :height="300" :options="pieOptionsData" />
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="14" :md="24" :sm="24" :xl="14" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" animated>
<Echart :height="300" :options="barOptionsData" />
</el-skeleton>
</el-card>
</el-col>
<el-col :span="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="4" animated>
<Echart :height="350" :options="lineOptionsData" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { useDesign } from '@/hooks/web/useDesign'
import type { AnalysisTotalTypes } from './types'
import { barOptions, lineOptions, pieOptions } from './echarts-data'
defineOptions({ name: 'Home2' })
const { t } = useI18n()
const loading = ref(true)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('panel')
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
let totalState = reactive<AnalysisTotalTypes>({
users: 0,
messages: 0,
moneys: 0,
shoppings: 0
})
const getCount = async () => {
const data = {
users: 102400,
messages: 81212,
moneys: 9280,
shoppings: 13600
}
totalState = Object.assign(totalState, data)
}
// 用户来源
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
set(pieOptionsData, 'series.data', data)
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
// 周活跃量
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
}
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
// 每月销售总额
const getMonthlySales = async () => {
const data = [
{ estimate: 100, actual: 120, name: 'analysis.january' },
{ estimate: 120, actual: 82, name: 'analysis.february' },
{ estimate: 161, actual: 91, name: 'analysis.march' },
{ estimate: 134, actual: 154, name: 'analysis.april' },
{ estimate: 105, actual: 162, name: 'analysis.may' },
{ estimate: 160, actual: 140, name: 'analysis.june' },
{ estimate: 165, actual: 145, name: 'analysis.july' },
{ estimate: 114, actual: 250, name: 'analysis.august' },
{ estimate: 163, actual: 134, name: 'analysis.september' },
{ estimate: 185, actual: 56, name: 'analysis.october' },
{ estimate: 118, actual: 99, name: 'analysis.november' },
{ estimate: 123, actual: 123, name: 'analysis.december' }
]
set(
lineOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(lineOptionsData, 'series', [
{
name: t('analysis.estimate'),
smooth: true,
type: 'line',
data: data.map((v) => v.estimate),
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: t('analysis.actual'),
smooth: true,
type: 'line',
itemStyle: {},
data: data.map((v) => v.actual),
animationDuration: 2800,
animationEasing: 'quadraticOut'
}
])
}
const getAllApi = async () => {
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
loading.value = false
}
getAllApi()
</script>
<style lang="scss" scoped>
$prefix-cls: #{$namespace}-panel;
.#{$prefix-cls} {
&__item {
&--peoples {
color: #40c9c6;
}
&--message {
color: #36a3f7;
}
&--money {
color: #f4516c;
}
&--shopping {
color: #34bfa3;
}
&:hover {
:deep(.#{$namespace}-icon) {
color: #fff !important;
}
.#{$prefix-cls}__item--icon {
transition: all 0.38s ease-out;
}
.#{$prefix-cls}__item--peoples {
background: #40c9c6;
}
.#{$prefix-cls}__item--message {
background: #36a3f7;
}
.#{$prefix-cls}__item--money {
background: #f4516c;
}
.#{$prefix-cls}__item--shopping {
background: #34bfa3;
}
}
}
}
</style>

347
src/views/Home/Index3.vue Normal file
View File

@@ -0,0 +1,347 @@
<template>
<div class="w-100%">
<div class="flex justify-between items-center">
<div
class="w-[calc(50%-50px)] py-16px px-20px bg-#fff border-rd-10px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<h5 class="text">服务器基本信息</h5>
<div class="w-100% inline-grid grid-cols-4 gap-4 text-center">
<div class="left-bottom-box" v-for="(item, index) in topLeftData" :key="index">
<p>{{ item.title }}</p>
<div>
<span>{{ item.num }}</span> {{ item.dw }}
</div>
</div>
</div>
</div>
<div
class="flex w-[calc(50%-50px)] px-20px bg-#fff border-rd-10px justify-between items-center b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<div class="conter-right-box" v-for="(item, index) in arr" :key="index">
<p class="text-conter">{{ item.title }}</p>
<div class="chart">
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
</div>
</div>
<!-- <div class="conter-right-box">
<p class="text-conter">内存使用率</p>
<div class="chart">
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
</div>
</div>
<div class="conter-right-box">
<p class="text-conter">系统平均负载1m</p>
<div class="chart">
<Echart :options="syl_pie_option" width="140px" height="130px"></Echart>
</div>
</div> -->
</div>
</div>
<div class="flex justify-between mt-20px">
<div
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<Echart :options="cpu_line_option" width="100%" height="350px" />
</div>
<div
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<Echart :options="nc_line_option" width="100%" height="350px" />
</div>
</div>
<div class="flex justify-between mt-20px">
<div
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<Echart :options="ll_line_option" width="100%" height="350px" />
</div>
<div
class="w-[calc(50%-50px)] bg-#fff border-rd-10px p-20px b-1px .dark:bg-#1D1E1F .dark:b-#2A2B2C .dark:b-solid"
>
<Echart :options="cp_line_option" width="100%" height="350px" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/store/modules/app'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { batchGetTableList } from '@/api/design/report'
defineOptions({ name: 'Home3' })
interface TopLeftData {
title: string
dw: string
dictionary: string
num?: number
}
const topLeftData = ref<TopLeftData[]>([
{ title: '系统运行时间', dw: '天', dictionary: 'xtyxsj' },
{ title: 'cpu核心数', dw: '', dictionary: 'cpuhxs' },
{ title: '内存总量', dw: 'G', dictionary: 'nczl' },
{ title: '系统平均负数', dw: '', dictionary: 'xtpjfz' }
])
const arr = ref<any>([
{ title: 'CPU使用率1m', dictionary: 'cpusyl' },
{ title: '内存使用率', dictionary: 'ncsyl' },
{ title: '系统平均负载1m', dictionary: 'xtpjfzl' }
])
const syl_pie_option = ref<any>({
title: { text: '50%', left: 'center', top: 'center' },
series: [
{
type: 'pie',
stillShowZeroSum: false,
label: { show: false },
hoverAnimation: false,
data: [
{ value: 100, name: 'A' },
{ value: 0, name: 'B' }
],
itemStyle: {
normal: {
color: function (params) {
var colors = ['#e9e9e9', '#0099ff', '#51d351']
return colors[params.dataIndex % colors.length]
}
}
},
radius: ['50%', '70%']
}
]
})
const cpuLineXAxis = ref<string[]>([])
const cpuLineSeries = ref<string[]>([])
const cpu_line_option = reactive<any>({
title: {
text: 'CPU使用率',
textStyle: {}
},
tooltip: {},
legend: { data: ['使用率'], x: 'right', selectedMode: false },
xAxis: {
data: cpuLineXAxis.value
},
yAxis: { type: 'value' },
series: [
{
name: '使用率',
data: cpuLineSeries.value,
type: 'line',
stack: 'x',
areaStyle: { color: '#ffd7dc' },
itemStyle: { normal: { color: '#ffd7dc', lineStyle: { color: '#ff7a8c' } } }
}
]
})
const ncLineXAxis = ref<string[]>([])
const ncLineSeries = ref<string[]>([])
const nc_line_option = reactive<any>({
title: {
text: '内存使用率',
textStyle: {}
},
tooltip: {},
legend: { data: ['使用率'], x: 'right', selectedMode: false },
xAxis: {
data: ncLineXAxis.value
},
yAxis: { type: 'value' },
series: [
{
name: '使用率',
data: ncLineSeries.value,
type: 'line',
stack: 'x',
areaStyle: { color: '#b2e0ff' },
itemStyle: { normal: { color: '#b2e0ff', lineStyle: { color: '#0099ff' } } }
}
]
})
const llLineXAxis = ref<string[]>([])
const llLineSeries1 = ref<string[]>([])
const llLineSeries2 = ref<string[]>([])
const ll_line_option = reactive<any>({
title: {
text: '服务器流量',
textStyle: {}
},
tooltip: {},
legend: { data: ['上传', '下载'], x: 'right', selectedMode: false },
xAxis: { data: llLineXAxis.value },
yAxis: { type: 'value' },
series: [
{
name: '上传',
data: llLineSeries1.value,
type: 'line',
// stack: 'x',
areaStyle: { color: '#caf1ca' },
itemStyle: { normal: { color: '#51d351', lineStyle: { color: '#51d351' } } }
},
{
name: '下载',
data: llLineSeries2.value,
type: 'line',
// stack: 'x',
areaStyle: { color: '#ffe5c9' },
itemStyle: { normal: { color: '#ffb465', lineStyle: { color: '#ffb465' } } }
}
]
})
const cpLineXAxis = ref<string[]>([])
const cpLineSeries1 = ref<string[]>([])
const cpLineSeries2 = ref<string[]>([])
const cp_line_option = reactive<any>({
title: {
text: '服务器磁盘IO',
textStyle: {}
},
tooltip: {},
legend: { data: ['输出', '输入'], x: 'right', selectedMode: false },
xAxis: { data: cpLineXAxis.value },
yAxis: { type: 'value' },
series: [
{
name: '输出',
data: cpLineSeries1.value,
type: 'line',
// stack: 'x',
areaStyle: { color: '#bdf1f1' },
itemStyle: { normal: { color: '#78e3e4', lineStyle: { color: '#78e3e4' } } }
},
{
name: '输入',
data: cpLineSeries2.value,
type: 'line',
// stack: 'x',
areaStyle: { color: '#d9d1fc' },
itemStyle: { normal: { color: '#8167f5', lineStyle: { color: '#8167f5' } } }
}
]
})
const textStyle = reactive({
color: ''
})
const appStore = useAppStore()
const init = async () => {
let oneres = await batchGetTableList(
'example_systemmonitor_server_information,example_systemmonitor_cpu_utilization,example_systemmonitor_memory_utilization,example_systemmonitor_server_traffic,example_systemmonitor_disk_io'
)
let serverInformation = oneres.example_systemmonitor_server_information.records[0]
topLeftData.value = topLeftData.value.map((item) => {
return (item = {
...item,
num: serverInformation[item.dictionary]
})
})
arr.value = arr.value.map((item) => {
syl_pie_option.value.title.text = serverInformation[item.dictionary] + '%'
syl_pie_option.value.series[0].data[1].value = serverInformation[item.dictionary]
return item
})
//CPU使用率
let cpuUtilization = oneres.example_systemmonitor_cpu_utilization.records
cpuUtilization.forEach((ele) => {
cpuLineXAxis.value.push(ele.sj)
cpuLineSeries.value.push(ele.syl)
})
//内存使用率
let memoryUtilization = oneres.example_systemmonitor_memory_utilization.records
memoryUtilization.forEach((ele) => {
ncLineXAxis.value.push(ele.sj)
ncLineSeries.value.push(ele.syl)
})
//服务器流量
let serverTraffic = oneres.example_systemmonitor_server_traffic.records
serverTraffic.forEach((ele) => {
llLineXAxis.value.push(ele.sj)
llLineSeries1.value.push(ele.sc)
llLineSeries2.value.push(ele.xz)
})
//服务器磁盘IO
let diskIo = oneres.example_systemmonitor_disk_io.records
diskIo.forEach((ele) => {
cpLineXAxis.value.push(ele.sj)
cpLineSeries1.value.push(ele.sc)
cpLineSeries2.value.push(ele.sr)
})
}
onMounted(async () => {
await init()
// 判断是否为暗色模式
const { wsCache } = useCache()
if (wsCache.get(CACHE_KEY.IS_DARK)) {
textStyle.color = '#E5EAF3'
cpu_line_option.title.textStyle = textStyle
nc_line_option.title.textStyle = textStyle
ll_line_option.title.textStyle = textStyle
cp_line_option.title.textStyle = textStyle
}
})
watch(
() => appStore.isDark,
(val) => {
if (val) {
textStyle.color = '#E5EAF3'
} else {
textStyle.color = 'black'
}
cpu_line_option.title.textStyle = textStyle
nc_line_option.title.textStyle = textStyle
ll_line_option.title.textStyle = textStyle
cp_line_option.title.textStyle = textStyle
}
)
</script>
<style lang="scss" scoped>
h5,
p {
padding: 0;
margin: 0;
}
.conter-right-box {
width: 200px;
text-align: center;
.text-conter {
margin-top: 15px;
font-size: 14px;
}
.chart {
display: flex;
width: 100%;
justify-content: center;
}
}
.left-bottom-box {
margin: 20px 0;
font-size: 12px;
text-align: center;
span {
font-size: 28px;
font-weight: 600;
}
}
.text {
margin-bottom: 20px;
font-size: 14px;
color: #666;
}
</style>

601
src/views/Home/Index4.vue Normal file
View File

@@ -0,0 +1,601 @@
<template>
<div class="w-100%">
<div class="w-100% flex gap-x-20px">
<div class="el-card top-card">
<div class="card-box">
<div class="text text-gray-500">月销售金额()</div>
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
</div>
<span class="text-value">
<avue-count-up :end="topList?.yxsje.value"></avue-count-up>
</span>
<div class="card-box mt-20px">
<div class="text">
<span class="mr-5px text-gray-500">周同比</span>
<Icon
v-if="topList?.yxsje.ztbType == 'top'"
:size="12"
color="#F56C6C"
icon="ep:caret-top"
></Icon>
<Icon
v-else-if="topList?.yxsje.ztbType == 'bottom'"
:size="12"
color="#19BE6B"
icon="ep:caret-bottom"
></Icon>
<span>{{ topList?.yxsje.ztb }}</span>
</div>
<div class="text">
<span class="mr-5px text-gray-500">日环比</span>
<Icon
v-if="topList?.yxsje.rhbType == 'top'"
:size="12"
color="#F56C6C"
icon="ep:caret-top"
></Icon>
<Icon
v-else-if="topList?.yxsje.rhbType == 'bottom'"
:size="12"
color="#19BE6B"
icon="ep:caret-bottom"
></Icon>
<span>{{ topList?.yxsje.rhb }}</span>
</div>
</div>
</div>
<div class="el-card top-card">
<div class="card-box">
<div class="text text-gray-500">月回款金额()</div>
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
</div>
<span class="text-value">
<avue-count-up :end="topList?.yhkje.value"></avue-count-up>
</span>
<div class="card-box w-[calc(100%+20px)]! ml--10px">
<div class="pos-relative w-100%">
<Echart
class="w-100% mt-10px pos-absolute left-0 top--30px"
:options="returned_money_option"
width="100%"
height="60px"
/>
</div>
</div>
</div>
<div class="el-card top-card">
<div class="card-box">
<div class="text text-gray-500">本月成交订单</div>
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
</div>
<span class="text-value">
<avue-count-up :end="topList?.bycjdd.value"></avue-count-up>
</span>
<div class="card-box w-[calc(100%+20px)]! ml--10px">
<div class="pos-relative w-100%">
<Echart
class="w-100% mt-10px pos-absolute left-0 top--30px"
:options="submit_order_option"
width="100%"
height="60px"
/>
</div>
</div>
</div>
<div class="el-card top-card pos-relative">
<div class="card-box">
<div class="text text-gray-500">完成销售目标</div>
<Icon class="cursor-pointer" color="#bdbdbd" icon="quill:warning-alt" />
</div>
<div class="text-value">{{ topList?.wcxsmb.value }}%</div>
<div class="pos-absolute right-30px top-50px">
<el-progress
type="circle"
:percentage="completeValue"
:stroke-width="9"
:width="60"
:show-text="false"
:indeterminate="true"
></el-progress>
</div>
</div>
<div class="el-card top-card">
<div class="card-box">
<div class="text text-gray-500">回款达成率</div>
<Icon color="#bdbdbd" class="cursor-pointer" icon="quill:warning-alt" />
</div>
<div class="text-value mb-30px!">{{ topList?.hkdcl.value }}%</div>
<el-progress
:percentage="returnedValue"
:stroke-width="9"
:show-text="false"
class="jdt"
></el-progress>
</div>
</div>
<div class="w-100% flex gap-x-20px">
<div class="flex-grow-3 flex-shrink flex-basis-0">
<el-card header="数据简报">
<div class="data-bulletin grid grid-cols-4 gap-y-4 text-center py-10px">
<template v-for="(item, index) in dataBulletin" :key="index">
<div class="p-14px">
<div class="text-gray-500 text-14px">{{ item.label }}</div>
<div class="text-28px fw-bold c-#666 dark:c-[var(--el-text-color-primary)]">
<avue-count-up :end="item.value"></avue-count-up>
</div>
<div :style="{ color: item.type == 'pos' ? '#19BE6B' : '#F56C6C' }"
>{{ item.type == 'pos' ? '+' : '-' }}{{ item.percent }}</div
>
</div>
</template>
</div>
</el-card>
<el-card header="业绩目标">
<Echart :options="target_bar_option" width="100%" height="350px" />
</el-card>
<el-card header="销售预测">
<Echart :options="forecast_bar_option" width="100%" height="350px" />
</el-card>
<el-card header="销售漏斗">
<div id="forecast-funnel-chart" class="w-100% h-350px"></div>
</el-card>
</div>
<div class="flex-grow-2 flex-shrink flex-basis-0">
<el-card header="销售排名">
<div class="ranking-box">
<div class="flex items-center gap-x-10px text-left text-12px c-#999 px-14px mb-10px">
<div v-for="(item, index) in rankingTopList" :key="index" :class="item.width">{{
item.label
}}</div>
</div>
<template v-for="(item, index) in rankingList" :key="index">
<div
class="flex items-center gap-x-10px text-left text-12px c-#666 p-14px b-0 b-b-1px b-[var(--el-card-border-color)] b-solid"
>
<div class="w-10% text-left">
<div
class="w-23px h-23px text-center line-height-23px bg-#ccc border-rd-4px c-#fff"
:style="index < 3 ? { background: '#ff9900' } : ''"
>{{ index + 1 }}</div
>
</div>
<div class="w-20%">{{ item.name }}</div>
<div class="w-25%">{{ item.money }}</div>
<div class="w-45%">
<el-progress
:percentage="item.percent"
:stroke-width="8"
:color="rankingColor[index] || '#409EFF'"
></el-progress>
</div>
</div>
</template>
</div>
</el-card>
<el-card header="快捷菜单">
<div class="fast-menu-box grid grid-cols-3 gap-y-20px c-#666 dark:c-#cfd3dc">
<template v-for="(item, index) in fastMenuList" :key="index">
<div
class="flex justify-center items-center flex-col hover:bg-#f1f1f1 hover:b-#dedddd! hover:dark:bg-#1e1e1e cursor-pointer"
>
<div class="h-70px w-70px flex justify-center items-center">
<Icon :icon="item.icon" :size="item.size"></Icon>
</div>
<div class="mt-4px text-12px">{{ item.label }}</div>
</div>
</template>
</div>
</el-card>
<el-card header="系统通知">
<div class="notifier-box">
<template v-for="(item, index) in notifierList" :key="index">
<div class="flex items-center py-12px px-10px">
<div class="bg-#F56C6C w-4px h-4px border-rd-50% flex-shrink-0"></div>
<div class="text-14px flex-1 ml-5px mr-20px">{{ item.label }}</div>
<div class="text-gray-500 text-12px flex-shrink-0">{{ item.time }}</div>
</div>
</template>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { batchGetTableList } from '@/api/design/report'
defineOptions({ name: 'Home4' })
interface TopList {
bycjdd: {
data: number[]
value: string
}
hkdcl: {
value: number
}
wcxsmb: {
value: number
}
yhkje: {
data: number[]
value: string
}
yxsje: {
value: string
rhb: string
rhbType: string
ztb: string
ztbType: string
}
}
interface DataBulletin {
label: string
dictionary: string
value?: string
percent?: string
type?: string
}
interface RankingList {
name: string
money: string
percent: number
}
interface RankingTopList {
label: string
width: string
}
interface SalesTarget {
cjje: string
sj: string
mbje: string
}
interface SalesForecast {
yjxsje: string
sj: string
glje: string
}
interface SalesFunnelgraphic {
dictionary: string
name: string
value?: number
}
interface NotifierList {
label: string
time: string
}
let topList = ref<TopList>()
let returned_money_option = ref({
grid: { bottom: '60%', containLabel: false },
xAxis: {
show: false,
type: 'category',
boundaryGap: false
},
yAxis: {
show: false,
type: 'value'
},
series: [
{
data: topList.value?.yhkje.data,
type: 'line',
symbol: 'none',
smooth: true,
areaStyle: {
color: '#FF7A8C'
},
lineStyle: {
color: '#FF7A8C'
}
}
]
})
const submit_order_option = ref({
grid: { bottom: '60%', containLabel: false },
xAxis: {
show: false,
type: 'category'
},
yAxis: {
show: false,
type: 'value'
},
series: [
{
data: topList.value?.bycjdd.data,
type: 'bar',
itemStyle: {
color: '#52C1F5'
}
}
]
})
const completeValue = ref(0)
const returnedValue = ref(0)
const dataBulletin = ref<DataBulletin[]>([
{ label: '新增客户', dictionary: 'xzkh' },
{ label: '新增线索', dictionary: 'xzxs' },
{ label: '新增商机', dictionary: 'xzsj' },
{ label: '新增订单', dictionary: 'xzdd' },
{ label: '新增联系人', dictionary: 'xnlxr' },
{ label: '跟进次数', dictionary: 'gjcs' },
{ label: '处理任务', dictionary: 'clrw' },
{ label: '处理工单', dictionary: 'clgd' }
])
const salesTarget = ref<SalesTarget[]>()
let salesTargetXAxis = ref<string[]>([])
let salesTargetSeries1 = ref<string[]>([])
let salesTargetSeries2 = ref<string[]>([])
const target_bar_option = ref({
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
tooltip: { trigger: 'item' },
legend: { data: ['目标金额', '成交金额'] },
xAxis: {
data: salesTargetXAxis.value
},
yAxis: {},
series: [
{
name: '目标金额',
type: 'bar',
data: salesTargetSeries1.value,
color: '#6CCAF6'
},
{
name: '成交金额',
type: 'bar',
data: salesTargetSeries2.value,
color: '#93D99A'
}
]
})
const salesForecast = ref<SalesForecast[]>()
let salesForecastXAxis = ref<string[]>([])
let salesForecastSeries1 = ref<string[]>([])
let salesForecastSeries2 = ref<string[]>([])
const forecast_bar_option = ref({
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
tooltip: { trigger: 'item' },
legend: { data: ['预计销售金额', '概率金额'] },
xAxis: {
data: salesForecastXAxis.value
},
yAxis: {},
series: [
{
name: '预计销售金额',
type: 'bar',
data: salesForecastSeries1.value,
color: '#FF8E9D'
},
{
name: '概率金额',
type: 'bar',
data: salesForecastSeries2.value,
color: '#88AEFB'
}
]
})
const salesFunnelgraphic = ref<SalesFunnelgraphic[]>([
{ name: '初步洽谈', dictionary: 'cbqt' },
{ name: '深入沟通', dictionary: 'srgt' },
{ name: '产品报价', dictionary: 'cpbj' },
{ name: '成交商机', dictionary: 'cjsj' },
{ name: '流失商机', dictionary: 'lssj' }
])
const forecast_funnel_option = ref({
title: {
text: ''
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%'
},
series: [
{
name: 'Funnel',
type: 'funnel',
width: '90%',
height: '100%',
left: '5%',
top: '3%',
label: {
position: 'right'
},
data: salesFunnelgraphic.value
}
]
})
const rankingColor = ref(['#FB6260', '#FFA94C', '#4BCED0'])
const rankingTopList = ref<RankingTopList[]>([
{
label: '排名',
width: 'w-10%'
},
{
label: '姓名',
width: 'w-20%'
},
{
label: '销售金额',
width: 'w-25%'
},
{
label: '完成目标',
width: 'w-45%'
}
])
const rankingList = ref<RankingList[]>()
const fastMenuList = ref([
{ label: '客户管理', size: 65, icon: 'la:user-tie' },
{ label: '线索管理', size: 55, icon: 'akar-icons:light-bulb' },
{ label: '商机管理', size: 55, icon: 'hugeicons:money-add-01' },
{ label: '联系人管理', size: 45, icon: 'uiw:user' },
{ label: '写新跟进', size: 55, icon: 'ph:pen' },
{ label: '回款管理', size: 55, icon: 'fluent:calendar-reply-32-light' },
{ label: '发票管理', size: 55, icon: 'hugeicons:invoice-01' },
{ label: '费用管理', size: 50, icon: 'teenyicons:cost-estimate-outline' },
{ label: '报销管理', size: 55, icon: 'hugeicons:money-bag-02' },
{ label: '工作报告', size: 55, icon: 'line-md:document-report' },
{ label: '工单管理', size: 60, icon: 'material-symbols-light:order-approve-outline-sharp' },
{ label: '产品管理', size: 55, icon: 'fluent-mdl2:product' }
])
const notifierList = ref<NotifierList[]>()
const init = async () => {
let oneres = await batchGetTableList(
'example_client_month_data,example_client_data_briefs,example_client_salesman_rank,example_client_sales_target'
)
topList.value = oneres.example_client_month_data.records[0]
returned_money_option.value.series[0].data = topList.value?.yhkje.data
submit_order_option.value.series[0].data = topList.value?.bycjdd.data
completeValue.value = topList.value?.wcxsmb.value || 0
returnedValue.value = topList.value?.hkdcl.value || 0
dataBulletin.value = dataBulletin.value.map((item) => {
return (item = {
...item,
...oneres.example_client_data_briefs.records[0][item.dictionary]
})
})
rankingList.value = oneres.example_client_salesman_rank.records
salesTarget.value = oneres.example_client_sales_target.records
salesTarget.value?.forEach((ele) => {
salesTargetXAxis.value.push(ele.sj)
salesTargetSeries1.value.push(ele.mbje)
salesTargetSeries2.value.push(ele.cjje)
})
target_bar_option.value.xAxis.data = salesTargetXAxis.value
target_bar_option.value.series[0].data = salesTargetSeries1.value
target_bar_option.value.series[1].data = salesTargetSeries2.value
let endres = await batchGetTableList(
'example_client_sales_forecast,example_client_sales_funnelgraphic,example_client_system_notification'
)
salesForecast.value = endres.example_client_sales_forecast.records
salesForecast.value?.forEach((ele) => {
salesForecastXAxis.value.push(ele.sj)
salesForecastSeries1.value.push(ele.yjxsje)
salesForecastSeries2.value.push(ele.glje)
})
forecast_bar_option.value.xAxis.data = salesForecastXAxis.value
forecast_bar_option.value.series[0].data = salesForecastSeries1.value
forecast_bar_option.value.series[1].data = salesForecastSeries2.value
salesFunnelgraphic.value = salesFunnelgraphic.value.map((item) => {
return (item = {
...item,
value: endres.example_client_sales_funnelgraphic.records[0][item.dictionary]
})
})
forecast_funnel_option.value.series[0].data = salesFunnelgraphic.value
notifierList.value = endres.example_client_system_notification.records
}
onMounted(async () => {
await init()
let xsldId = document.getElementById('forecast-funnel-chart')
if (xsldId) {
let forecastFunnelChart = echarts.init(xsldId)
forecastFunnelChart.setOption(forecast_funnel_option.value)
}
const timer_1 = setInterval(() => {
completeValue.value = completeValue.value + 10
if (completeValue.value >= 75) {
completeValue.value = 75
clearInterval(timer_1)
}
}, 20)
const timer_2 = setInterval(() => {
returnedValue.value = returnedValue.value + 10
if (returnedValue.value >= 80) {
returnedValue.value = 80
clearInterval(timer_2)
}
}, 20)
})
</script>
<style lang="scss" scoped>
.top-card {
position: relative;
padding: 15px;
margin-top: 0 !important;
border-radius: 10px;
box-shadow: var(--el-box-shadow-light);
flex: 1;
.card-box {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
.text-value {
margin-bottom: 15px;
font-size: 30px;
font-weight: 700;
}
}
.text-value {
margin-bottom: 15px;
font-size: 28px;
font-weight: bold;
}
}
::v-deep(.el-card) {
margin-top: 20px;
border-radius: 8px;
.el-card__header {
font-weight: bold;
}
.el-card__body {
.data-bulletin {
& > div {
border-right: 1px solid var(--el-card-border-color);
&:nth-child(4n) {
border-right: none;
}
}
}
.ranking-box {
.el-progress__text {
font-size: 12px !important;
}
}
.fast-menu-box > div {
padding: 10px 0;
border: 1px solid transparent;
}
.notifier-box > div {
border-bottom: 1px solid var(--el-card-border-color);
}
.notifier-box > div:nth-child(1) {
padding-top: 0;
}
}
}
</style>

1102
src/views/Home/Index5.vue Normal file

File diff suppressed because it is too large Load Diff

913
src/views/Home/Index6.vue Normal file
View File

@@ -0,0 +1,913 @@
<template>
<div class="w-100%">
<div class="w-100% top-box">
<div
class="top-card bg-#fff .dark:bg-#1D1E1F! b-1px b-transparent .dark:b-#2A2B2C b-solid"
v-for="(item, index) in topCardList"
:class="item.border"
:key="index"
>
<div class="card-box">
<div class="title">{{ item.title }}</div>
<Icon
icon="akar-icons:info-fill"
width="48"
height="48"
class="cursor-pointer"
style="color: #bdbdbd"
/>
</div>
<span class="text-value .dark:c-#fff!">
<span v-if="item.unit == '¥'">{{ item.unit }}</span>
<avue-count-up :end="item.num"></avue-count-up>
<span v-if="item.unit == '家'" class="text-dw">{{ item.unit }}</span>
</span>
<div class="card-box mt-10px">
<div class="" style="display: flex; align-items: center; height: 12px">
<span v-if="item.type == 'pos'" class="icon">
<Icon icon="ep:top" width="12" height="12" style="color: #f56c6c" />
</span>
<span v-else-if="item.type == 'neg'" class="icons">
<Icon icon="ep:bottom" width="12" height="12" style="color: #19be6b" />
</span>
<span
class="text-ts"
:class="item.type == 'pos' ? 'text-zeng' : item.type == 'neg' ? 'text-jian' : ''"
>{{ item.percentage }}</span
>
<span class="mr-5px text"> {{ item.tendency }}</span>
</div>
</div>
</div>
</div>
<div class="content .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="content-left">
<p class="content-title .dark:c-#fff!">商户外拓</p>
<div class="jdt" v-for="(item, index) in jdtData" :key="index">
<div class="jdt-top">
<div style="display: flex; align-items: center" class="jdt-top-text">
<span class="acitive" :class="'bj' + (index >= 3 ? 0 : index)"></span>
{{ item.title }}
</div>
<div class=".dark:c-#fff!">
<span class="num .dark:c-#fff!">{{ item.leftNum }}</span>
<span class="num" style="font-size: 14px; font-weight: 400"
>/{{ item.rightNum }}</span
>
<span class="jdt-top-text .dark:c-#fff!">{{ item.dw }}</span>
</div>
</div>
<div class="jdt-bottom demo-progress">
<el-progress
:text-inside="true"
:stroke-width="12"
:percentage="item.jd"
:status="item.type"
/>
</div>
</div>
</div>
<div class="content-center">
<p class="content-title .dark:c-#fff!">商户分布</p>
<div id="main" style="width: 380px; height: 300px"></div>
</div>
<div class="content-right">
<p class="content-title .dark:c-#fff!">交易漏斗</p>
<div id="ldMain" style="width: 380px; height: 280px"></div>
</div>
</div>
<div class="eCharts">
<div
class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent .dark:c-#fff!"
>
<div class="eCharts-title .dark:c-#fff!">
<p>日交易额</p>
<p style="font-size: 14px" class="font">6,000,000</p>
</div>
<div id="zxtMain" style="width: 380px; height: 300px"></div>
</div>
<div class="eCharts-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="eCharts-title .dark:c-#fff!">
<p>周订单量</p>
<p style="font-size: 16px" class="font">10,000<span class="fontFamily"></span></p>
</div>
<div id="zddlMain" style="width: 380px; height: 300px"></div>
</div>
<div
class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent"
style="position: relative"
>
<div style="position: absolute">
<div class="eCharts-title .dark:c-#fff! w-100%">
<p>开拓商家</p>
<p style="margin-left: 235px; font-size: 14px">1,000</p>
</div>
</div>
<div id="ktsjMain" style="width: 360px; height: 335px" class=".dark:c-#fff!"></div>
</div>
</div>
<div class="eCharts-cjje .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="cjje-top">
<div class="cjje-top-left .dark:c-#fff!">成交金额趋势</div>
<div class="cjje-top-right">
<el-radio-group v-model="tabPosition" style="margin-bottom: 30px">
<el-radio-button
v-for="(item, index) in cjjeTabList"
:key="index"
:value="item.label"
@change="clickCjje(item.type)"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</div>
</div>
<div id="cjjeMain" style="width: 1400px; height: 300px"></div>
</div>
<div
class="eCharts-cjje .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent"
style="padding-top: 0"
>
<div class="cjje-top">
<div class="cjje-top-left .dark:c-#fff!" style="height: 60px; line-height: 60px">
成交金额趋势
</div>
</div>
<div>
<Table></Table>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import Table from './components/table.vue'
import { getTableList, batchGetTableList } from '@/api/design/report'
defineOptions({ name: 'Home6' })
interface TopCardList {
dictionary: string
border: string
tendency: string
title: string
unit: string
type?: string
num?: string
percentage?: string
}
interface JdtData {
dictionary: string
title: string
dw: string
jd?: number
type: any
leftNum?: number
rightNum?: number
}
interface OptionSeriesData {
name: string
value: number
}
type Ktsj = [string, string | number, string | number]
let topCardList = ref<TopCardList[]>([
{ title: '商户总数', dictionary: 'shzs', tendency: '环比上周', border: '', unit: '家' },
{ title: '开拓中商户', dictionary: 'ktzsh', tendency: '环比上周', border: '', unit: '家' },
{ title: '总访问量', dictionary: 'zfwl', tendency: '环比上周', border: '', unit: '' },
{ title: '总交易额', dictionary: 'zjye', tendency: '环比上周', border: '', unit: '¥' },
{ title: '总订单量', dictionary: 'zddl', tendency: '环比上周', border: '', unit: '' },
{ title: '客单价', dictionary: 'kdj', tendency: '环比上周', border: '', unit: '¥' }
])
const jdtData = ref<JdtData[]>([
{ title: '已计划', dictionary: 'yjh', dw: '家', type: '' },
{ title: '已完成', dictionary: 'ywc', dw: '家', type: 'exception' },
{ title: '开拓中', dictionary: 'ktz', dw: '家', type: 'warning' },
{ title: '洽谈中', dictionary: 'qtz', dw: '家', type: '' }
])
//环形图
let currentIndex = ref(0) // 当前高亮图形在饼图数据中的下标
const highlightPie = (option, myChart) => {
// 遍历饼图数据,取消所有图形的高亮效果
for (var idx in option) {
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: idx
})
}
// 高亮当前图形
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: currentIndex.value
})
}
// 饼图自动轮播
const handleChartLoop = (option, myChart) => {
if (!myChart) {
return
}
highlightPie(option, myChart)
// 用户鼠标悬浮到某一图形时,高亮鼠标悬浮的图形
myChart.on('mouseover', (params) => {
currentIndex.value = params.dataIndex
highlightPie(option, myChart)
})
}
let optionSeriesData = ref<OptionSeriesData[]>([])
const option = reactive({
tooltip: {
trigger: 'item'
},
legend: {
left: 'right',
orient: 'vertical',
align: 'left',
top: 'center',
icon: 'circle',
itemGap: 25,
// 图例文本格式化
formatter: function (name) {
// 在这里可以获取到对应数据项的值
var data = optionSeriesData.value
for (var i = 0; i < data.length; i++) {
if (data[i].name === name) {
return name + ' ' + data[i].value
}
}
return name
}
},
color: ['#50b5ff', '#ffc542', '#ff7474', '#50b5ff', '#8167f5'],
series: [
{
type: 'pie',
label: {
show: false,
position: 'center',
formatter: '{d}%\n\n{b}',
fontSize: 20
},
legendHoverLink: false,
center: ['30%', '45%'],
data: [],
labelLine: {
show: false // 关闭指示线
},
radius: ['48%', '70%'],
emphasis: {
label: {
show: true
}
}
}
]
})
//漏斗
const ldOption = reactive({
grid: {
left: 0,
right: 0,
top: 0,
bottom: 0,
containLabel: true
},
series: [
{
name: '',
type: 'funnel',
left: '10%',
top: 20,
bottom: 60,
width: '75%',
min: 0,
max: 50000,
minSize: '30%',
maxSize: '70%',
sort: 'descending',
gap: 0,
color: ['#5b8ff9', '#5ad8a6', '#5d7092', '#f6bd16'],
label: {
show: true,
position: 'inside',
formatter: '{b} : {c}'
},
itemStyle: {
borderWidth: 0
},
data: []
}
]
})
//日交易额
let dailyTurnoverXAxis = ref<string[]>([])
let dailyTurnoverSeries = ref<number[]>([])
const zxtOption = reactive({
grid: {
top: '10%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
bottom: '20%'
},
xAxis: {
type: 'category',
data: dailyTurnoverXAxis.value,
axisTick: {
show: false // 设置X轴不显示刻度
}
},
yAxis: {
type: 'value',
offset: -7,
max: '50000',
axisLabel: {
textStyle: {
fontSize: 12 // 增大字体大小
}
}
},
series: [
{
data: dailyTurnoverSeries.value,
type: 'line',
symbol: 'none',
smooth: false, // 是否平滑曲线显示
lineStyle: {
color: '#51d351', // 折线颜色
width: 2 // 折线宽度
}
}
]
})
// 周订单量
let weeklyOrdersXAxis = ref<string[]>([])
let weeklyOrdersSeries = ref<number[]>([])
const zddlOption = reactive({
grid: {
top: '10%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
bottom: '20%'
},
xAxis: {
type: 'category',
data: weeklyOrdersXAxis.value,
axisTick: {
show: false // 设置X轴不显示刻度
}
},
yAxis: {
type: 'value'
},
series: [
{
data: weeklyOrdersSeries.value,
type: 'bar',
barWidth: '35%',
itemStyle: {
// 设置柱形图的颜色
color: '#409eff',
borderRadius: [4, 4, 0, 0]
}
}
]
})
//开拓商家
let expansionNumberDataset = ref<Ktsj[]>([['product', '目标开拓数', '实际开拓数']])
const ktsjOption = reactive({
theme: 'dark',
grid: {
top: '15%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
bottom: '20%'
},
legend: {
icon: 'circle' // 设置图例标记为圆形
},
tooltip: {},
dataset: {
source: expansionNumberDataset.value
},
xAxis: {
type: 'category',
axisTick: {
show: false // 设置X轴不显示刻度
}
},
yAxis: {},
// Declare several bar series, each will be mapped
// to a column of dataset.source by default.
series: [
{
type: 'bar',
barWidth: '20%',
itemStyle: {
// 设置柱形图的颜色
color: '#52c1f5',
borderRadius: [4, 4, 0, 0]
}
},
{
type: 'bar',
barWidth: '20%',
itemStyle: {
// 设置柱形图的颜色
color: '#ff7a8c',
borderRadius: [5, 5, 0, 0]
}
}
]
})
//成交金额趋势
const tabPosition = ref('最近1周')
const cjjeTabList = ref([
{
label: '最近1周',
type: 1
},
{
label: '最近30天',
type: 2
},
{
label: '最近半年',
type: 3
},
{
label: '最近1年',
type: 4
}
])
let transactionAmountXAxis = ref<string[]>([])
let transactionAmountSeries1 = ref<string[]>([])
let transactionAmountSeries2 = ref<string[]>([])
const cjjeOption = ref({
grid: {
left: '4%',
top: '5%', // 调整顶部的空白间距,也可以使用像素值,例如 '50px'
bottom: '20%'
},
legend: {
top: 'bottom',
data: ['线上成交', '线下成交'],
icon: 'rectangle'
},
xAxis: {
data: transactionAmountXAxis.value
},
yAxis: {},
series: [
{
name: '线上成交',
data: transactionAmountSeries1.value,
type: 'line',
symbol: 'none',
smooth: false,
areaStyle: {
color: '#b2e0ff',
opacity: 0.8
}
// symbol: 'circle',
},
{
name: '线下成交',
data: transactionAmountSeries2.value,
type: 'line',
symbol: 'none',
smooth: false,
areaStyle: {
color: '#ffd7dc',
opacity: 0.8
}
// symbol: 'circle',
}
]
})
const clickCjje = async (type) => {
let res = await getTableList('example_trader_transaction_amount', { type: type })
transactionAmountXAxis.value = []
transactionAmountSeries1.value = []
transactionAmountSeries2.value = []
res.records.forEach((ele) => {
transactionAmountXAxis.value.push(ele.sj)
transactionAmountSeries1.value.push(ele.xscj)
transactionAmountSeries2.value.push(ele.xxcj)
})
cjjeOption.value.xAxis.data = transactionAmountXAxis.value
cjjeOption.value.series[0].data = transactionAmountSeries1.value
cjjeOption.value.series[1].data = transactionAmountSeries2.value
nextTick(() => {
const cjjeChart = echarts.init(document.getElementById('cjjeMain'))
cjjeChart.setOption(cjjeOption.value)
})
}
// 通用函数,用于初始化 ECharts 图表
const initEcharts = (domElementId, option) => {
const domElement = document.getElementById(domElementId)
if (domElement) {
const chart = echarts.init(domElement)
domElement.removeAttribute('_echarts_instance_')
chart.setOption(option)
if (domElementId == 'main') {
handleChartLoop(option.series[0].data, chart)
}
}
}
const echartFun = () => {
initEcharts('ldMain', ldOption)
initEcharts('zxtMain', zxtOption)
initEcharts('zddlMain', zddlOption)
initEcharts('ktsjMain', ktsjOption)
initEcharts('main', option)
initEcharts('cjjeMain', cjjeOption.value)
}
const init = async () => {
let oneres = await batchGetTableList(
'example_trader_count_data,example_trader_expansion,example_trader_merchant_distribution,example_trader_transaction_funneldiagram'
)
topCardList.value = topCardList.value.map((item, index) => {
const cardData = oneres.example_trader_count_data.records[0][item.dictionary]
item = { ...item, ...cardData, border: 'border' + index }
return item
})
jdtData.value = jdtData.value.map((item) => {
const cardData = oneres.example_trader_expansion.records[0][item.dictionary]
item = { ...item, ...cardData, jd: (cardData.leftNum / cardData.rightNum) * 100 }
return item
})
option.series[0].data = oneres.example_trader_merchant_distribution.records
optionSeriesData.value = oneres.example_trader_merchant_distribution.records
ldOption.series[0].data = oneres.example_trader_transaction_funneldiagram?.records
let endres = await batchGetTableList(
'example_trader_daily_turnover,example_trader_weekly_orders,example_trader_expansion_number,example_trader_transaction_amount'
)
endres.example_trader_daily_turnover?.records.forEach((ele) => {
dailyTurnoverXAxis.value.push(ele.sj)
dailyTurnoverSeries.value.push(ele.data)
})
zxtOption.xAxis.data = dailyTurnoverXAxis.value
zxtOption.series[0].data = dailyTurnoverSeries.value
endres.example_trader_weekly_orders.records.forEach((ele) => {
weeklyOrdersXAxis.value.push(ele.sj)
weeklyOrdersSeries.value.push(ele.data)
})
zddlOption.xAxis.data = weeklyOrdersXAxis.value
zddlOption.series[0].data = weeklyOrdersSeries.value
expansionNumberDataset.value.push(
...endres.example_trader_expansion_number.records.map((item) => [
item.sj,
item.mbkts,
item.sjkts
])
)
endres.example_trader_transaction_amount.records.forEach((ele) => {
transactionAmountXAxis.value.push(ele.sj)
transactionAmountSeries1.value.push(ele.xscj)
transactionAmountSeries2.value.push(ele.xxcj)
})
cjjeOption.value.xAxis.data = transactionAmountXAxis.value
cjjeOption.value.series[0].data = transactionAmountSeries1.value
cjjeOption.value.series[1].data = transactionAmountSeries2.value
}
onMounted(async () => {
await init()
echartFun()
})
</script>
<style lang="scss" scoped>
.real-time-data {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 12px;
font-weight: 400;
color: #999;
align-items: flex-end;
.title {
// color: #666666;
font-size: 15px;
font-weight: 700;
}
}
.top-box {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
}
.top-card {
flex-basis: calc(16.6% - 12px);
flex-shrink: 1;
min-width: 168px;
padding: 15px;
// border-radius: 10px;
box-shadow: var(--el-box-shadow-light);
box-sizing: border-box;
.card-box {
display: flex;
justify-content: space-between;
align-items: center;
.title {
font-family: '微软雅黑', sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 32px;
color: #999;
}
.tendency {
width: 24px;
height: 17px;
background-position: center;
background-repeat: no-repeat;
background-size: 100%;
}
.text-ts {
margin: 0 3px;
font-family: Montserrat, sans-serif;
font-size: 12px;
font-weight: 400;
line-height: 24px;
}
.text {
margin-left: 1px;
font-family: '微软雅黑', sans-serif;
font-size: 12px;
font-weight: 400;
line-height: 24px;
color: #999;
}
}
.text-value {
font-family: 'Montserrat Bold', Montserrat, sans-serif;
font-size: 24px;
font-weight: 700;
font-weight: bold;
line-height: 32px;
color: #333;
}
.card-box.top {
.text {
color: rgb(7 183 138);
}
.ascent {
background-image: url('@/assets/svgs/top.svg');
}
}
.card-box.bottom {
.text {
color: rgb(250 80 135);
}
.descent {
background-image: url('@/assets/svgs/bottom.svg');
}
}
.text-dw {
margin-left: 8px;
font-family: '微软雅黑', sans-serif;
font-size: 14px;
font-weight: 400;
color: #333;
}
}
.content {
display: flex;
padding: 10px 30px 0;
margin-top: 20px;
background: #fff;
justify-content: space-between;
p {
height: 60px;
padding: 0;
margin: 0;
line-height: 60px;
}
.content-title {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-style: normal;
font-weight: 700;
color: #666;
text-align: left;
}
.content-left {
width: 30%;
.jdt {
margin-top: 20px;
.jdt-top {
display: flex;
width: 100%;
justify-content: space-between;
font-size: 14px;
color: #666;
.jdt-top-text {
font-family: '微软雅黑', sans-serif;
font-size: 14px;
font-weight: 400;
color: #666;
}
.num {
font-family: 'Montserrat Bold', Montserrat, sans-serif;
font-size: 16px;
font-weight: 700;
}
.acitive {
display: block;
width: 10px;
height: 10px;
margin-right: 5px;
// background: red;
border-radius: 50%;
}
}
.jdt-bottom {
margin-top: 10px;
}
}
}
.content-center {
width: 30%;
}
.content-right {
width: 30%;
}
}
.eCharts {
display: flex;
margin-top: 20px;
justify-content: space-between;
.eCharts-title {
display: flex;
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 28px;
color: #666;
text-align: left;
justify-content: space-between;
p {
padding: 0;
margin: 0;
}
}
.eCharts-left {
width: 27.5%;
padding: 10px 30px 0;
background: #fff;
}
.eCharts-center {
width: 27.5%;
padding: 10px 30px 0;
background: #fff;
}
.eCharts-right {
width: 27.5%;
padding: 10px 30px 0;
background: #fff;
}
}
.eCharts-cjje {
padding: 10px 30px 30px;
margin-top: 20px;
background: #fff;
p {
padding: 0;
margin: 0;
}
.cjje-top {
display: flex;
justify-content: space-between;
.cjje-top-left {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 28px;
color: #686a6d;
text-align: left;
}
}
}
.border0 {
border-left: 5px solid #fb6260;
}
.border1 {
border-left: 5px solid #09f;
}
.border2 {
border-left: 5px solid #8167f5;
}
.border3 {
border-left: 5px solid #51d351;
}
.border4 {
border-left: 5px solid #ff7a8c;
}
.border5 {
border-left: 5px solid #ffa94c;
}
.text-zeng {
color: #f56c6c;
}
.text-jian {
color: #19be6b;
}
.demo-progress .el-progress--line {
max-width: 600px;
margin-bottom: 15px;
}
.bj0 {
background: #409eff;
}
.bj1 {
background: #f56c6c;
}
.bj2 {
background: #e6a23c;
}
.icon {
margin-top: 7px;
}
.icons {
margin-top: 4px;
}
.font {
font-family: 'Montserrat Bold', Montserrat, sans-serif !important;
}
.fontFamily {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important;
}
::v-deep .el-progress-bar__innerText {
margin-top: -8px !important;
line-height: 12px !important;
}
::v-deep .style {
top: -50px !important;
}
</style>

799
src/views/Home/Index7.vue Normal file
View File

@@ -0,0 +1,799 @@
<template>
<div class="w-100%">
<div class="box">
<div class="box-left">
<div class="top .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<p class="title .dark:c-#fff!">我的待办</p>
<div class="top-content">
<div class="top-content-box" v-for="(item, index) in data" :key="index">
<img :src="item.img" alt="" />
<div style="margin-left: 15px">
<p class="text .dark:c-#fff!" style="font-size: 28px">{{ item.num }}</p>
<p class="text font .dark:c-#ccc!">{{ item.title }}</p>
</div>
</div>
</div>
</div>
<div class="center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<p class="title .dark:c-#fff!">常用功能</p>
<div class="center-box">
<div class="content-box" v-for="(item, index) in cygnData" :key="index">
<div class="img" :class="'bj' + index">
<img :src="item.src" alt="" />
</div>
<div class="font .dark:c-#fff!" style="margin-top: 15px">{{ item.title }}</div>
</div>
</div>
</div>
<div class="bottom .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="bottom-top">
<p class="bottom-top-title .dark:c-#fff!">业务处理</p>
<el-menu
:default-active="cwclType"
:ellipsis="false"
mode="horizontal"
@select="handleSelect"
style="padding: 0 !important; margin: 0 !important"
>
<el-menu-item v-for="item in cwclTypeList" :key="item.type" :index="item.type">{{
item.name
}}</el-menu-item>
</el-menu>
</div>
<el-table
class="custom-row-gap"
:data="cwclTableData"
style="width: 100%"
:header-cell-style="{
height: '50px',
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
fontWeight: ' 700 !important',
color: '#666666 !important'
}"
:cell-style="{
height: '44.2px',
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
fontWeight: ' 400 !important',
color: '#666666 !important'
}"
>
<el-table-column prop="tjsj" label="提交时间" width="170" />
<el-table-column prop="bxlx" label="报销类型" width="140" />
<el-table-column prop="spdh" label="审批单号" width="140" />
<el-table-column prop="bxje" label="报销金额" width="140" />
<el-table-column prop="spdx" label="审批对象" width="140" />
<el-table-column fixed="right" label="操作" min-width="120" align="center">
<template #default>
<el-button link type="primary" size="small" @click="handleClick">
<Icon
icon="icon-park-solid:right-c"
width="12"
height="12"
style="color: #2391ff"
/>
<span style="margin-left: 5px; font-size: 14px">查看详情</span>
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="box-right">
<div class="box-right-top .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="title .dark:c-#fff!">预警信息</div>
<div class="list" v-for="(item, index) in yjxxdata" :key="index">
<span class="list-left .dark:c-#ccc!">{{ item.name }}</span>
<span class="list-right" :class="index % 2 == 0 ? 'text-color' : ''">{{
item.num
}}</span>
</div>
</div>
<div class="box-right-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div style="display: flex; justify-content: space-between">
<div class="title .dark:c-#fff!"> 年度经营目标</div>
<div class="title" style="font-size: 24px; color: #279df5">
<span class="fontFamily .dark:c-#fff!">{{ annualIndicators1?.ndjymb }}</span>
<span style="font-size: 14px; color: #666" class=".dark:c-#ccc!"> </span>
</div>
</div>
<div class="ndmb">
<el-progress
type="circle"
:percentage="annualIndicators1?.wcl"
:stroke-width="16"
:width="166"
>
<template #default>
<span class="percentage-label .dark:c-#ccc!" style="color: #20a0ff"> 完成率 </span>
<span class="percentage-value" style="color: #20a0ff">
{{ annualIndicators1?.wcl }}%
</span>
</template>
</el-progress>
</div>
<div class="text .dark:c-#ccc!">
已完成目标
<span class="fontFamily" style="font-size: 24px; font-weight: 700; color: #fbaf4f">
{{ annualIndicators1?.ywcmb }}
</span>
</div>
</div>
<div class="box-right-center .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div style="display: flex; justify-content: space-between">
<div class="title .dark:c-#fff!">年度回款目标</div>
<div class="title" style="font-size: 24px; color: #279df5">
<span class="fontFamily .dark:c-#fff!">{{ annualIndicators2?.ndhkmb }}</span>
<span style="font-size: 14px; color: #666" class=".dark:c-#ccc!"> </span>
</div>
</div>
<div class="ndmb">
<el-progress
type="circle"
:percentage="annualIndicators2?.wcl"
:stroke-width="16"
status="success"
:width="166"
>
<template #default>
<span class="percentage-label .dark:c-#ccc!">完成率</span>
<span class="percentage-value">{{ annualIndicators2?.wcl }}%</span>
</template>
</el-progress>
</div>
<div class="text .dark:c-#ccc!">
已完成目标
<span class="fontFamily" style="font-size: 24px; font-weight: 700; color: #fbaf4f">
{{ annualIndicators2?.ywcmb }}
</span>
</div>
</div>
</div>
</div>
<div class="w-100% eCharts">
<div class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="eCharts-left-top">
<div class="title .dark:c-#fff!">账户余额</div>
<div class="font .dark:c-#ccc!">
<span>合计</span>
<span
class="fontFamily .dark:c-#fff!"
style="font-size: 20px; font-weight: 700; color: #2b9ef7"
>{{ zhyeNum }}</span
>
<span style="margin-left: 5px"></span>
</div>
</div>
<div>
<div
style="display: flex; align-items: center; line-height: 50px"
v-for="(item, index) in zhyedata"
:key="index"
>
<div class="font-title .dark:c-#ccc!" style="width: 17%">{{ item.name }}</div>
<div style="width: 79%">
<el-progress :percentage="item.jdt" :stroke-width="10">
<span
style="
margin-left: 30px;
font-family: ArialMT, Arial, sans-serif;
font-size: 16px;
"
>{{ item.num }}</span
>
</el-progress>
</div>
</div>
</div>
</div>
<div class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="eCharts-right-top">
<div class="title .dark:c-#fff!">收支预测</div>
</div>
<div id="szycMain" style="width: 600px; height: 300px"></div>
</div>
</div>
<div class="w-100% eCharts">
<div class="eCharts-left .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="eCharts-left-top">
<div class="title .dark:c-#fff!">费用结构</div>
</div>
<div id="fyjgMain" style="width: 600px; height: 300px"></div>
</div>
<div class="eCharts-right .dark:b-#2A2B2C b-solid .dark:bg-#1D1E1F! b-1px b-transparent">
<div class="eCharts-right-top">
<div class="title .dark:c-#fff!">收入结构</div>
</div>
<div id="srjgMain" style="width: 600px; height: 300px"></div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from 'echarts'
import { getTableList, batchGetTableList } from '@/api/design/report'
import { ElMessage } from 'element-plus'
interface Data {
dictionary: string
img: string
title: string
num?: string
}
interface CwclTableData {
tjsj: string
bxlx: string
spdh: string
bxje: string
spdx: string
}
interface Yjxxdata {
dictionary: string
name: string
num?: string
}
interface CircularProgress {
ndjymb: number
wcl: number
ywcmb: number
ndhkmb: number
}
interface Zhyedata {
name: string
num: number
jdt: number
}
type Szyc = [[string, string | number, string | number]]
//我的待办
let data = ref<Data[]>([
{ dictionary: 'dclgd', img: '/img/img1.svg', title: '待处理工单' },
{ dictionary: 'dbxmx', img: '/img/img2.svg', title: '待报销明细' },
{ dictionary: 'dtjhb', img: '/img/img3.svg', title: '待提交汇报' },
{ dictionary: 'dtjbb', img: '/img/img4.svg', title: '待提交报表' }
])
//常用功能
const cygnData = reactive([
{ src: '/img/tb1.svg', title: '资金收支报表' },
{ src: '/img/tb2.svg', title: '资产负债报表' },
{ src: '/img/tb3.svg', title: '企业利润报表' },
{ src: '/img/tb4.svg', title: '现金流量报表' },
{ src: '/img/tb5.svg', title: '项目报告列表' },
{ src: '/img/tb6.svg', title: '责任中心报告' }
])
//预警信息
let yjxxdata = ref<Yjxxdata[]>([
{
dictionary: 'dqysk',
name: '到期应收款'
},
{
dictionary: 'yqysk',
name: '逾期应收款'
},
{
dictionary: 'dqyfk',
name: '到期应付款'
},
{
dictionary: 'yqyfk',
name: '逾期应付款'
}
])
// 年度经营目标
const annualIndicators1 = ref<CircularProgress>()
// 年度回款目标
const annualIndicators2 = ref<CircularProgress>()
//账户余额
const zhyeNum = ref('')
let zhyedata = ref<Zhyedata[]>()
//收支预测
const szycDataset = ref<Szyc>([['product', '预计收入', '预计支出']])
const szycOption = reactive({
legend: {
// data: ['系列1', '系列2'],
orient: 'horizontal',
x: 'center', // 可以是 'center'、'left'、'right' 等
y: 'bottom' // 可以是 'top'、'center'、'bottom' 等,或者是具体的像素值
},
tooltip: {},
dataset: {
source: szycDataset.value
},
xAxis: {
type: 'category',
axisTick: {
show: false // 设置X轴不显示刻度
}
},
yAxis: {},
// Declare several bar series, each will be mapped
// to a column of dataset.source by default.
series: [
{
type: 'bar',
itemStyle: {
// 设置柱形图的颜色
color: '#2c9ef7'
}
},
{
type: 'bar',
itemStyle: {
// 设置柱形图的颜色
color: '#0dd78d'
}
}
]
})
//费用结构
const fyjgOption = reactive({
tooltip: {
trigger: 'item'
},
legend: {
orient: 'horizontal',
left: 'center',
top: 'bottom',
icon: 'circle'
},
series: [
{
name: '',
type: 'pie',
radius: '58%',
center: ['50%', '40%'],
color: ['#2c9ef7', '#aa89fe', '#fdad4e', '#0dd78d'],
data: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
normal: {
formatter: '{b}\n\n¥{c}',
textStyle: {
color: '#999999',
fontSize: 12
}
}
}
}
]
})
//收入结构
const srjgOption = reactive({
tooltip: {
trigger: 'item'
},
legend: {
orient: 'horizontal',
left: 'center',
top: 'bottom',
icon: 'circle'
},
series: [
{
name: '',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '40%'],
color: ['#2c9ef7', '#5cb9ff', '#abdbff', '#d0ebff'],
data: [],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
normal: {
formatter: '{b}\n \n ¥{c}',
textStyle: {
color: '#999999',
fontSize: 12
}
}
}
}
]
})
// 业务处理
const cwclType = ref('1')
const cwclTableData = ref<CwclTableData[]>()
const cwclTypeList = ref([
{ name: '待我审批', type: '1' },
{ name: '超期待审', type: '2' },
{ name: '我提交的', type: '3' },
{ name: '被退回的', type: '4' }
])
const handleSelect = async (key: string, keyPath: string[]) => {
let res = await getTableList('example_systemmonitor_business_processing', { type: key })
cwclTableData.value = res.records
}
const handleClick = () => {
ElMessage({
message: '查看详情',
type: 'success'
})
}
// 通用函数,用于初始化 ECharts 图表
const initEcharts = (domElementId, option) => {
const domElement = document.getElementById(domElementId)
if (domElement) {
const chart = echarts.init(domElement)
domElement.removeAttribute('_echarts_instance_')
chart.setOption(option)
}
}
const echartFun = () => {
initEcharts('szycMain', szycOption)
initEcharts('fyjgMain', fyjgOption)
initEcharts('srjgMain', srjgOption)
}
const init = async () => {
let oneres = await batchGetTableList(
'example_signagetwo_pending,example_systemmonitor_business_processing,example_systemmonitor_warning_messages,example_systemmonitor_business_target'
)
let signagetwoPending = oneres.example_signagetwo_pending.records[0]
let businessProcessing = oneres.example_systemmonitor_business_processing.records
let warningMessages = oneres.example_systemmonitor_warning_messages.records[0]
let businessTarget = oneres.example_systemmonitor_business_target.records[0]
data.value = data.value.map((item) => {
return (item = {
...item,
num: signagetwoPending[item.dictionary]
})
})
cwclTableData.value = businessProcessing
yjxxdata.value = yjxxdata.value.map((item) => {
return (item = {
...item,
num: warningMessages[item.dictionary]
})
})
annualIndicators1.value = businessTarget
let endres = await batchGetTableList(
'example_systemmonitor_payback_target,example_systemmonitor_account_balance,example_systemmonitor_Income_expenditure_forecasts,example_systemmonitor_fee_structure,example_systemmonitor_Income_structure'
)
let paybackTarget = endres.example_systemmonitor_payback_target.records[0]
let accountBalance = endres.example_systemmonitor_account_balance.records
let expenditureForecasts = endres.example_systemmonitor_Income_expenditure_forecasts.records
let feeStructure = endres.example_systemmonitor_fee_structure.records
let incomestructure = endres.example_systemmonitor_Income_structure.records
annualIndicators2.value = paybackTarget
zhyedata.value = accountBalance
zhyeNum.value = accountBalance.reduce((a, b) => {
return a + b.num
}, 0)
expenditureForecasts.forEach((ele) => {
szycDataset.value.push([ele.sj, ele.yjsr, ele.yjzc])
})
szycOption.dataset.source = szycDataset.value
fyjgOption.series[0].data = feeStructure
srjgOption.series[0].data = incomestructure
}
onMounted(async () => {
await init()
echartFun()
})
</script>
<style lang="scss" scoped>
.box {
display: flex;
width: 100%;
justify-content: space-between;
p {
padding: 0;
margin: 0;
}
.box-left {
width: 72.3%;
// height: 300px;
// background: red;
.title {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 60px;
color: #333;
text-align: left;
}
.top {
padding: 0 20px 20px;
background: #fff;
border-radius: 5px;
.text {
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif;
font-weight: 700;
color: #333;
}
.top-content {
display: flex;
justify-content: space-between;
margin: 25px;
.top-content-box {
display: flex;
justify-content: space-between;
align-items: center;
img {
width: 48px;
height: 48px;
}
}
}
}
.center {
padding: 0 20px 10px;
margin-top: 20px;
background: #fff;
border-radius: 5px;
.center-box {
display: flex;
padding-bottom: 20px;
margin-top: 10px;
justify-content: space-evenly;
.content-box {
width: 100px;
text-align: center;
// background: red;
.img {
display: flex;
width: 60px;
height: 60px;
margin: 0 auto;
border-radius: 50%;
align-items: center;
justify-content: center;
}
}
}
}
.bottom {
padding: 0 20px;
padding-bottom: 35px;
margin-top: 20px;
background: #fff;
border-radius: 5px;
.bottom-top {
display: flex;
margin-bottom: 8px;
justify-content: space-between;
.bottom-top-title {
height: 40px;
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-weight: 700;
line-height: 52px;
color: #333;
text-align: left;
}
}
}
}
.box-right {
width: 26.3%;
// height: 300px;
// background: blue;
.title {
// margin-bottom: 20px;
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: 60px;
color: #333;
text-align: left;
}
.box-right-top {
padding: 0 20px 20px;
background: #fff;
border-radius: 5px;
.list {
display: flex;
justify-content: space-between;
// margin-bottom: 15px;
line-height: 40px;
.list-left {
font-family: '微软雅黑', sans-serif;
font-size: 14px;
font-style: normal;
font-weight: 400;
color: #666;
text-align: left;
}
.list-right {
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif;
font-size: 16px;
font-style: normal;
font-weight: 700;
color: #0fd1b6;
text-align: right;
}
}
}
.box-right-center {
padding: 0 20px 20px;
margin-top: 20px;
background: #fff;
border-radius: 5px;
.ndmb {
display: flex;
width: 100%;
justify-content: center;
margin-top: 10px;
}
.text {
margin-top: 20px;
font-family: '微软雅黑', sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 40px;
color: #797979;
text-align: center;
}
}
}
}
.eCharts {
display: flex;
margin-top: 20px;
justify-content: space-between;
.title {
font-family: '微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: 60px;
color: #333;
text-align: left;
}
.eCharts-left {
width: 46.1%;
padding: 0 20px 20px;
line-height: 60px;
background: #fff;
border-radius: 5px;
.eCharts-left-top {
display: flex;
justify-content: space-between;
}
.eCharts-right-top {
display: flex;
justify-content: space-between;
}
}
.eCharts-right {
width: 46.1%;
padding: 0 20px;
background: #fff;
border-radius: 5px;
}
}
.percentage-value {
display: block;
margin-top: 10px;
font-family: Arial-BoldMT, 'Arial Bold', Arial, sans-serif;
font-size: 28px;
font-weight: 700;
color: #13ce66;
}
.percentage-label {
display: block;
margin-top: 10px;
font-family: ArialMT, Arial, sans-serif;
font-size: 14px;
font-weight: 400;
color: #666 !important;
}
.el-menu--horizontal.el-menu {
border: 0 !important;
}
.bj0 {
background: #2c9ef7;
}
.bj1 {
background: #b591f5;
}
.bj2 {
background: #11d88f;
}
.bj3 {
background: #ffad52;
}
.bj4 {
background: #10d1b7;
}
.bj5 {
background: #f67263;
}
.text-color {
color: #f6716e !important;
}
.font {
font-family: '微软雅黑', sans-serif !important;
font-size: 14px !important;
font-weight: 400 !important;
color: #666;
}
.fontFamily {
font-family: 'Montserrat Bold', 'Montserrat Regular', Montserrat, sans-serif !important;
}
.font-title {
font-family: MicrosoftYaHei, '微软雅黑 Regular', '微软雅黑', sans-serif !important;
font-size: 14px !important;
font-weight: 400;
color: #666;
}
::v-deep .el-table--default .el-table__cell {
padding: 0 !important; /* 根据需要调整这个值来增加或减少行间距 */
}
</style>

432
src/views/Home/Index8.vue Normal file
View File

@@ -0,0 +1,432 @@
<template>
<div class="w-100%">
<div class="w-100% flex gap-y-20px home-top">
<div class="el-card top-card flex items-center" v-for="(item, index) in tabList" :key="index">
<div class="left">
<span class="text-value">
<avue-count-up :end="item.countUp"></avue-count-up>
</span>
<div class="text mt-10px">
<span class="mr-5px">{{ item.title }}</span>
</div>
</div>
<div>
<el-image :src="item.imgUrl" fit="contain"></el-image>
</div>
</div>
</div>
<div class="w-100% flex gap-x-20px mt-30px ml-10px mr-10px">
<div class="w-35%">
<el-calendar v-model="value">
<template #header>
<span>{{ calendarDate }}</span>
</template>
</el-calendar>
</div>
<div class="echart-util bold bg-white .dark:bg-#1D1E1F">
<Echart :options="visitor_option" width="100%" height="314px" />
</div>
</div>
<div class="w-100% flex gap-x-20px mt-30px ml-10px mr-10px">
<div class="echart-util bold bg-white .dark:bg-#1D1E1F">
<Echart :options="salesVolume_option" width="100%" height="314px" />
</div>
<div class="echart-util reverse bg-white .dark:bg-#1D1E1F">
<Echart :options="cyclic_annular_option" width="100%" height="314px" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { useAppStore } from '@/store/modules/app'
import { batchGetTableList } from '@/api/design/report'
defineOptions({ name: 'Home8' })
interface CyclicAnnular {
name: string
dictionary: string
value?: number
}
const tabList = ref([
{ countUp: 0, title: '今日访客数(人)', imgUrl: '/img/sjhy1.png', dictionary: 'today_visitors' },
{
countUp: 0,
title: '今日会员访问数(人)',
imgUrl: '/img/sjhy2.png',
dictionary: 'today_member_visitors'
},
{
countUp: 0,
title: '今日会员付费数(人)',
imgUrl: '/img/sjhy3.png',
dictionary: 'today_paid_member'
},
{ countUp: 0, title: '新增会员数(人)', imgUrl: '/img/sjhy4.png', dictionary: 'today_new_member' }
])
const value = ref(new Date())
const calendarDate = formatDate(new Date(), 'YYYY-MM-DD')
// 实时访客量
const visitorXAxis = ref<string[]>([])
const visitorSeries = ref<number[]>([])
const visitor_option = ref({
tooltip: {
trigger: 'axis'
},
title: {
text: '实时访客量',
textStyle: {
fontSize: 20,
color: 'black'
},
left: 25
},
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#EDE8FE' // 0% 处的颜色
},
{
offset: 1,
color: '#F8F7FC' // 100% 处的颜色
}
],
global: false // 缺省为 false
},
grid: {
left: '4%',
right: '4%',
bottom: 20,
top: '16%',
containLabel: true
},
xAxis: {
data: visitorXAxis.value,
type: 'category',
boundaryGap: false,
axisLine: {
symbol: 'none',
lineStyle: {
color: '#EBEBEB'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0,
color: 'black',
fontSize: 12,
padding: [10, 0, 0, 0],
align: 'center'
}
},
yAxis: {
type: 'value',
axisLabel: {
color: '#6071A9',
fontSize: 12
},
splitLine: {
lineStyle: {
color: '#EBEBEB',
type: 'solid'
}
},
offset: 10
},
series: [
{
name: '访客量',
type: 'line',
data: visitorSeries.value,
smooth: true,
lineStyle: {
width: 5,
color: '#5D77FF'
},
areaStyle: {
opacity: 0.7
},
emphasis: { disabled: true },
symbol: 'none'
}
]
})
// 销售额
const salesVolumeXAxis = ref<string[]>([])
const salesVolumeSeries = ref<number[]>([])
const salesVolume_option = ref({
tooltip: {
trigger: 'axis'
},
title: {
text: '销售额(元)',
textStyle: {
fontSize: 20,
color: 'black'
},
left: 25
},
color: ['#6F7EFD'],
grid: {
left: '4%',
right: '4%',
bottom: 20,
top: '16%',
containLabel: true
},
xAxis: {
data: salesVolumeXAxis.value,
type: 'category',
boundaryGap: false,
axisLine: {
symbol: 'none',
lineStyle: {
color: '#EBEBEB'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0,
color: 'black',
fontSize: 12,
padding: [10, 0, 0, 0],
align: 'center'
}
},
yAxis: {
type: 'value',
axisLabel: {
color: '#6071A9',
fontSize: 12
},
splitLine: {
lineStyle: {
width: 0
}
},
offset: 10
},
series: [
{
type: 'line',
data: salesVolumeSeries.value,
smooth: true,
lineStyle: {
width: 0
},
areaStyle: {
opacity: 1
},
emphasis: { disabled: true },
symbol: 'none'
}
]
})
// 环状图
const cyclic_annular_option_data = ref<CyclicAnnular[]>([
{ name: '男装', dictionary: 'nz' },
{ name: '鞋包', dictionary: 'xb' },
{ name: '母婴', dictionary: 'my' },
{ name: '数码', dictionary: 'sm' }
])
const cyclic_annular_option = ref({
title: {
text: '',
left: 'center',
top: 'center',
textStyle: {
fontSize: 24,
fontWeight: 'bold',
color: '#5E78FD'
},
subtext: '',
subtextStyle: {
color: '#999999',
fontWeight: '600'
},
itemGap: 0
},
color: ['#5E78FD', '#6E86FD', '#8DA0FF', '#AEBBFF'],
series: [
{
type: 'pie',
radius: ['35%', '55%'],
label: {
formatter: (parms) => {
return [`{str|${parms.percent}% ${parms.data.name}}`].join('\n')
},
rich: {
str: {
fontWeight: 'bold',
align: 'center'
}
}
},
data: cyclic_annular_option_data.value
}
]
})
const appStore = useAppStore()
watch(
() => appStore.isDark,
(val) => {
if (val) {
visitor_option.value.title.textStyle.color = 'white'
salesVolume_option.value.title.textStyle.color = 'white'
} else {
visitor_option.value.title.textStyle.color = 'black'
salesVolume_option.value.title.textStyle.color = 'black'
}
}
)
const init = async () => {
let oneres = await batchGetTableList('example_member_count_data,example_member_realtime_visits')
let countData = oneres.example_member_count_data.records[0]
let realtimeVisits = oneres.example_member_realtime_visits.records
tabList.value = tabList.value.map((item) => {
return (item = {
...item,
countUp: countData[item.dictionary]
})
})
realtimeVisits.forEach((ele) => {
visitorXAxis.value?.push(ele.sj)
visitorSeries.value?.push(ele.ssfkl)
})
let endres = await batchGetTableList('example_member_sale,example_member_product_percentage')
let sale = endres.example_member_sale.records
let productPercentage = endres.example_member_product_percentage.records[0]
sale.forEach((ele) => {
salesVolumeXAxis.value?.push(ele.sj)
salesVolumeSeries.value?.push(ele.xse)
})
cyclic_annular_option_data.value = cyclic_annular_option_data.value.map((item) => {
return (item = {
...item,
value: productPercentage[item.dictionary]
})
})
cyclic_annular_option.value.series[0].data = cyclic_annular_option_data.value
cyclic_annular_option.value.title.text = '¥' + productPercentage.je
cyclic_annular_option.value.title.subtext = productPercentage.sj
}
onMounted(async () => {
// 判断是否为暗色模式
const { wsCache } = useCache()
if (wsCache.get(CACHE_KEY.IS_DARK)) {
visitor_option.value.title.textStyle.color = 'white'
salesVolume_option.value.title.textStyle.color = 'white'
}
await init()
})
</script>
<style lang="scss" scoped>
.home-top {
flex-wrap: wrap;
.top-card {
width: calc(25% - 50px);
height: 85px;
padding: 15px;
margin: 0 10px;
margin-top: 0 !important;
border: none;
border-radius: inherit;
flex-shrink: 0;
justify-content: space-around;
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
.left {
font-weight: bold;
.text-value {
margin-bottom: 45px;
font-size: 28px;
}
.text {
font-size: 13px;
}
}
}
}
.echart-util {
padding: 20px;
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
}
.echart-util.bold {
width: calc(65% - 80px);
}
.echart-util.reverse {
width: calc(35% - 40px);
}
::v-deep(.el-calendar) {
box-shadow: 0 0 20px rgb(204 204 204 / 34.9%);
.el-calendar__header {
font-size: 18px;
font-weight: bold;
border-bottom: none;
}
.el-calendar__body {
th {
font-size: 15px;
font-weight: bold;
}
.el-calendar-table__row {
.is-selected {
background-color: rgb(0 255 255 / 0%);
}
.current,
.next,
.prev {
border: none;
.el-calendar-day {
display: flex;
height: 43px;
font-size: 14px;
font-weight: 700;
justify-content: center;
align-items: center;
}
}
}
td {
// border: none;
}
}
}
</style>

1016
src/views/Home/Index9.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
<template>
<div>
<el-table
:data="tableData"
stripe
style="width: 100%"
:header-cell-style="{
height: '50px',
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
fontWeight: ' 700 !important',
color: '#666666 !important'
}"
:cell-style="{
height: '50px',
fontFamily: `'微软雅黑 Bold', '微软雅黑 Regular', '微软雅黑', sans-serif !important`,
fontWeight: ' 400 !important',
color: '#666666 !important'
}"
>
<el-table-column prop="ranking" label="排名" width="180" />
<el-table-column prop="name" label="商户名称" />
<el-table-column prop="visit" label="访问量" />
<el-table-column prop="quantity" label="订单量" />
<el-table-column prop="cancel" label="取消量" />
<el-table-column label="综合评分">
<template #default="scope">
<el-rate
v-model="scope.row.pj"
disabled
allow-half
show-score
size="large"
:score-template="scope.row.score"
/>
</template>
</el-table-column>
<el-table-column label="成交金额(元)">
<template #default="scope">
<div v-if="scope.row.zeng" style="display: flex; align-items: center; height: 12px">
<span>{{ scope.row.money }}</span>
<span class="icon"
><Icon icon="ep:top" width="12" height="12" style="color: #f56c6c"
/></span>
<span style="color: #f7716f !important">{{ scope.row.zeng }}%</span>
</div>
<div v-else-if="scope.row.jian" style="display: flex; align-items: center; height: 12px">
<span>{{ scope.row.money }}</span>
<span class="icon"
><Icon icon="ep:bottom" width="12" height="12" style="color: #19be6b"
/></span>
<span style="color: #73c883 !important">{{ scope.row.jian }}%</span>
</div>
<div v-else>
<span>{{ scope.row.money }}</span>
</div>
</template>
</el-table-column>
</el-table>
<div class="page">
<el-pagination background layout="prev, pager, next" :total="tableDataTotal" />
</div>
</div>
</template>
<script lang="ts" setup>
import * as TableApi from '@/api/design/table'
interface TableData {
cancel: string
ranking: string
name: string
visit: string
quantity: string
pj: string
score: string
money: string
zeng: string
jian: string
}
const tableData = ref<TableData[]>()
const tableDataTotal = ref(0)
onMounted(async () => {
let { records } = await TableApi.getTableList(
'1847537155101040642',
{ pageNo: 1, pageSize: 10 },
false
)
tableData.value = records.map((item) => {
item.pj = Number(item.pj || 0)
return item
})
tableDataTotal.value = records.length
})
</script>
<style lang="scss" scoped>
.page {
display: flex;
width: 100%;
padding: 20px;
justify-content: center;
align-items: center;
}
.icon {
margin-top: 4px;
}
/* 将默认的星星颜色改为蓝色 */
::v-deep .el-rate .el-rate__icon.is-active {
color: #f56c6c !important;
}
::v-deep .custom-rate .el-rate__item.active {
color: #f56c6c; /* 选中的颜色 */
}
::v-deep .el-rate .el-rate__item {
color: #ccc;
}
</style>

View File

@@ -0,0 +1,308 @@
import { EChartsOption } from 'echarts'
const { t } = useI18n()
export const lineOptions: EChartsOption = {
title: {
text: t('analysis.monthlySales'),
left: 'center'
},
xAxis: {
data: [
t('analysis.january'),
t('analysis.february'),
t('analysis.march'),
t('analysis.april'),
t('analysis.may'),
t('analysis.june'),
t('analysis.july'),
t('analysis.august'),
t('analysis.september'),
t('analysis.october'),
t('analysis.november'),
t('analysis.december')
],
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 80,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: [t('analysis.estimate'), t('analysis.actual')],
top: 50
},
series: [
{
name: t('analysis.estimate'),
smooth: true,
type: 'line',
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: t('analysis.actual'),
smooth: true,
type: 'line',
itemStyle: {},
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
animationDuration: 2800,
animationEasing: 'quadraticOut'
}
]
}
export const pieOptions: EChartsOption = {
title: {
text: t('analysis.userAccessSource'),
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: [
t('analysis.directAccess'),
t('analysis.mailMarketing'),
t('analysis.allianceAdvertising'),
t('analysis.videoAdvertising'),
t('analysis.searchEngines')
]
},
series: [
{
name: t('analysis.userAccessSource'),
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{ value: 335, name: t('analysis.directAccess') },
{ value: 310, name: t('analysis.mailMarketing') },
{ value: 234, name: t('analysis.allianceAdvertising') },
{ value: 135, name: t('analysis.videoAdvertising') },
{ value: 1548, name: t('analysis.searchEngines') }
]
}
]
}
export const barOptions: EChartsOption = {
title: {
text: t('analysis.weeklyUserActivity'),
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: 50,
right: 20,
bottom: 20
},
xAxis: {
type: 'category',
data: [
t('analysis.monday'),
t('analysis.tuesday'),
t('analysis.wednesday'),
t('analysis.thursday'),
t('analysis.friday'),
t('analysis.saturday'),
t('analysis.sunday')
],
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value'
},
series: [
{
name: t('analysis.activeQuantity'),
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
type: 'bar'
}
]
}
export const radarOption: EChartsOption = {
legend: {
data: [t('workplace.personal'), t('workplace.team')]
},
radar: {
// shape: 'circle',
indicator: [
{ name: t('workplace.quote'), max: 65 },
{ name: t('workplace.contribution'), max: 160 },
{ name: t('workplace.hot'), max: 300 },
{ name: t('workplace.yield'), max: 130 },
{ name: t('workplace.follow'), max: 100 }
]
},
series: [
{
name: `xxx${t('workplace.index')}`,
type: 'radar',
data: [
{
value: [42, 30, 20, 35, 80],
name: t('workplace.personal')
},
{
value: [50, 140, 290, 100, 90],
name: t('workplace.team')
}
]
}
]
}
export const wordOptions = {
series: [
{
type: 'wordCloud',
gridSize: 2,
sizeRange: [12, 50],
rotationRange: [-90, 90],
shape: 'pentagon',
width: 600,
height: 400,
drawOutOfBound: true,
textStyle: {
color: function () {
return (
'rgb(' +
[
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') +
')'
)
}
},
emphasis: {
textStyle: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: [
{
name: 'Sam S Club',
value: 10000,
textStyle: {
color: 'black'
},
emphasis: {
textStyle: {
color: 'red'
}
}
},
{
name: 'Macys',
value: 6181
},
{
name: 'Amy Schumer',
value: 4386
},
{
name: 'Jurassic World',
value: 4055
},
{
name: 'Charter Communications',
value: 2467
},
{
name: 'Chick Fil A',
value: 2244
},
{
name: 'Planet Fitness',
value: 1898
},
{
name: 'Pitch Perfect',
value: 1484
},
{
name: 'Express',
value: 1112
},
{
name: 'Home',
value: 965
},
{
name: 'Johnny Depp',
value: 847
},
{
name: 'Lena Dunham',
value: 582
},
{
name: 'Lewis Hamilton',
value: 555
},
{
name: 'KXAN',
value: 550
},
{
name: 'Mary Ellen Mark',
value: 462
},
{
name: 'Farrah Abraham',
value: 366
},
{
name: 'Rita Ora',
value: 360
},
{
name: 'Serena Williams',
value: 282
},
{
name: 'NCAA baseball tournament',
value: 273
},
{
name: 'Point Break',
value: 265
}
]
}
]
}

55
src/views/Home/types.ts Normal file
View File

@@ -0,0 +1,55 @@
export type WorkplaceTotal = {
project: number
access: number
todo: number
}
export type Project = {
name: string
icon: string
message: string
personal: string
time: Date | number | string
}
export type Notice = {
title: string
type: string
keys: string[]
date: Date | number | string
}
export type Shortcut = {
name: string
icon: string
url: string
}
export type RadarData = {
personal: number
team: number
max: number
name: string
}
export type AnalysisTotalTypes = {
users: number
messages: number
moneys: number
shoppings: number
}
export type UserAccessSource = {
value: number
name: string
}
export type WeeklyUserActivity = {
value: number
name: string
}
export type MonthlySales = {
name: string
estimate: number
actual: number
}