数据源和 oss
This commit is contained in:
@@ -1,187 +0,0 @@
|
||||
<template>
|
||||
<Chart :height="height" :option="chartOption" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Chart from '@/components/charts/index.vue'
|
||||
import useChartOption from '@/hooks/charts'
|
||||
import { graphic } from 'echarts'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useThemeStore } from '@/pinia'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { addOpacityToColor } from '@/theme/color'
|
||||
const themeStore = useThemeStore()
|
||||
const { settings } = storeToRefs(themeStore)
|
||||
defineProps({
|
||||
height: {
|
||||
type: String,
|
||||
default: '128px'
|
||||
}
|
||||
})
|
||||
const axisTextColor = computed(() => {
|
||||
return themeStore.isDark ? 'rgba(255,255,255,0.70)' : 'rgba(0,0,0,0.70)'
|
||||
})
|
||||
const dotColor = computed(() => {
|
||||
return themeStore.isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)'
|
||||
})
|
||||
const primaryColor = (opacity) => addOpacityToColor(settings.value.themeColor, opacity)
|
||||
const graphicFactory = (side) => {
|
||||
return {
|
||||
type: 'text',
|
||||
bottom: '8',
|
||||
...side,
|
||||
style: {
|
||||
text: '',
|
||||
textAlign: 'center',
|
||||
fill: axisTextColor.value,
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
const xAxis = ref([
|
||||
'2024-1',
|
||||
'2024-2',
|
||||
'2024-3',
|
||||
'2024-4',
|
||||
'2024-5',
|
||||
'2024-6',
|
||||
'2024-7',
|
||||
'2024-8'
|
||||
])
|
||||
const chartsData = ref([12, 22, 32, 45, 32, 78, 89, 92])
|
||||
const graphicElements = ref([
|
||||
graphicFactory({ left: '5%' }),
|
||||
graphicFactory({ right: 0 })
|
||||
])
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: '40',
|
||||
right: '0',
|
||||
top: '10',
|
||||
bottom: '30'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 2,
|
||||
data: xAxis.value,
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
color: axisTextColor.value,
|
||||
formatter(value, idx) {
|
||||
if (idx === 0) return ''
|
||||
if (idx === xAxis.value.length - 1) return ''
|
||||
return `${value}`
|
||||
}
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
interval: (idx) => {
|
||||
if (idx === 0) return false
|
||||
if (idx === xAxis.value.length - 1) return false
|
||||
return true
|
||||
},
|
||||
lineStyle: {
|
||||
color: dotColor.value
|
||||
}
|
||||
},
|
||||
axisPointer: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: primaryColor(1),
|
||||
width: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
formatter(value, idx) {
|
||||
if (idx === 0) return value
|
||||
return `${value}k`
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: dotColor.value
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter(params) {
|
||||
const [firstElement] = params
|
||||
return `<div>
|
||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
|
||||
Number(firstElement.value) * 10000
|
||||
).toLocaleString()}</span></div>
|
||||
</div>`
|
||||
},
|
||||
className: 'echarts-tooltip-diy'
|
||||
},
|
||||
graphic: {
|
||||
elements: graphicElements.value
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: chartsData.value,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
// symbol: 'circle',
|
||||
symbolSize: 12,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: new graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: primaryColor(0.5)
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
color: primaryColor(0.57)
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: primaryColor(1)
|
||||
}
|
||||
])
|
||||
},
|
||||
showSymbol: false,
|
||||
areaStyle: {
|
||||
opacity: 0.8,
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: primaryColor(0.13)
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: primaryColor(0.03)
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,132 +0,0 @@
|
||||
<template>
|
||||
<Chart :height="height" :option="chartOption" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Chart from '@/components/charts/index.vue'
|
||||
import useChartOption from '@/hooks/charts'
|
||||
import { graphic } from 'echarts'
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useThemeStore } from '@/pinia'
|
||||
import { addOpacityToColor } from '@/theme/color'
|
||||
const themeStore = useThemeStore()
|
||||
const { settings } = storeToRefs(themeStore)
|
||||
|
||||
const prop = defineProps({
|
||||
height: {
|
||||
type: String,
|
||||
default: '128px'
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
const graphicFactory = (side) => {
|
||||
return {
|
||||
type: 'text',
|
||||
bottom: '8',
|
||||
...side,
|
||||
style: {
|
||||
text: '',
|
||||
textAlign: 'center',
|
||||
fill: themeStore.isDark ? '#FFFFFF' : '#000000',
|
||||
fontSize: 12
|
||||
}
|
||||
}
|
||||
}
|
||||
const graphicElements = ref([
|
||||
graphicFactory({ left: '5%' }),
|
||||
graphicFactory({ right: 0 })
|
||||
])
|
||||
const primaryColor = (opacity) => addOpacityToColor(settings.value.themeColor, opacity)
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: '40',
|
||||
right: '0',
|
||||
top: '10',
|
||||
bottom: '30'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 2,
|
||||
show: false,
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false,
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
graphic: {
|
||||
elements: graphicElements.value
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: prop.data,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 12,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: new graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: primaryColor(0.2)
|
||||
},
|
||||
{
|
||||
offset: 0.5,
|
||||
color: primaryColor(0.39)
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: primaryColor(1)
|
||||
}
|
||||
])
|
||||
},
|
||||
showSymbol: false,
|
||||
areaStyle: {
|
||||
opacity: 0.8,
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: primaryColor(0.13)
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: primaryColor(0.03)
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div v-if="title" class="text-sm font-semibold tracking-tight text-base-text">
|
||||
{{ title }}
|
||||
</div>
|
||||
<slot v-else name="title" />
|
||||
</div>
|
||||
<div class="w-full relative">
|
||||
<div v-if="type !== 4">
|
||||
<div class="mt-4 text-3xl font-mono text-base-text">
|
||||
<el-statistic :value="268500" />
|
||||
</div>
|
||||
<div class="mt-2 text-xs font-mono text-muted-foreground">
|
||||
+80% <el-icon class="align-middle"><TopRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-2 w-[50%] h-20">
|
||||
<charts-people-number v-if="type === 1" :data="data[0]" height="100%" />
|
||||
<charts-people-number v-if="type === 2" :data="data[1]" height="100%" />
|
||||
<charts-people-number v-if="type === 3" :data="data[2]" height="100%" />
|
||||
</div>
|
||||
<charts-content-number v-if="type === 4" height="14rem" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import chartsPeopleNumber from './charts-people-numbers.vue'
|
||||
import chartsContentNumber from './charts-content-numbers.vue'
|
||||
defineProps({
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const data = [
|
||||
[12, 22, 32, 45, 32, 78, 89, 92],
|
||||
[1, 2, 43, 5, 67, 78, 89, 12],
|
||||
[12, 22, 32, 45, 32, 78, 89, 92]
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,11 +1,9 @@
|
||||
import KraCard from './card.vue'
|
||||
import KraChart from './charts.vue'
|
||||
import KraNotice from './notice.vue'
|
||||
import KraQuickLink from './quickLinks.vue'
|
||||
|
||||
export {
|
||||
KraCard,
|
||||
KraChart,
|
||||
KraNotice,
|
||||
KraQuickLink,
|
||||
}
|
||||
|
||||
@@ -30,37 +30,20 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const notices = [
|
||||
{
|
||||
typeTitle: '通知',
|
||||
time: '今天',
|
||||
title: 'Kra 管理后台已完成核心系统、公告和邮件模块接入。',
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getInfoList } from '@/modules/announcement/api/info'
|
||||
const notices = ref([])
|
||||
onMounted(async () => {
|
||||
const response = await getInfoList({ page: 1, pageSize: 8, status: 1 })
|
||||
if (response.code !== 0) return
|
||||
notices.value = (response.data?.list || []).map((item) => ({
|
||||
typeTitle: item.type === 2 ? '公告' : '通知',
|
||||
time: item.createdAt ? new Date(item.createdAt).toLocaleDateString('zh-CN') : '',
|
||||
title: item.title,
|
||||
dotClass: 'bg-cyan-500',
|
||||
tagClass: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-200'
|
||||
},
|
||||
{
|
||||
typeTitle: '安全',
|
||||
time: '2天前',
|
||||
title: '请在部署前修改 JWT 密钥并配置数据库连接信息。',
|
||||
dotClass: 'bg-emerald-500',
|
||||
tagClass: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-200'
|
||||
},
|
||||
{
|
||||
typeTitle: '运维',
|
||||
time: '3天前',
|
||||
title: '邮件告警默认关闭,填写 SMTP 配置后自动启用。',
|
||||
dotClass: 'bg-amber-500',
|
||||
tagClass: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200'
|
||||
},
|
||||
{
|
||||
typeTitle: '存储',
|
||||
time: '5天前',
|
||||
title: '媒体文件默认存储在本地 uploads/file 目录。',
|
||||
dotClass: 'bg-violet-500',
|
||||
tagClass: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-200'
|
||||
}
|
||||
]
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -1,81 +1,38 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="h-full kra-container2 overflow-auto bg-main">
|
||||
<div class="space-y-2 py-2">
|
||||
<kra-card
|
||||
class="relative overflow-hidden rounded-xl border border-slate-200/80 bg-white px-5 py-6 shadow-sm dark:border-slate-700 dark:bg-slate-900"
|
||||
>
|
||||
|
||||
<div class="relative flex flex-col gap-2 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<p class="text-xs tracking-[0.2em] text-muted-foreground">DASHBOARD</p>
|
||||
<h1 class="mt-2 text-xl font-semibold text-base-text lg:text-2xl">
|
||||
欢迎回来,开始今天的Coding节奏
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
{{ today }} · 已为你聚合核心业务数据和系统公告
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<kra-card class="rounded-xl border border-slate-200/80 bg-white px-5 py-6 shadow-sm dark:border-slate-700 dark:bg-slate-900">
|
||||
<p class="text-xs tracking-[0.2em] text-muted-foreground">DASHBOARD</p>
|
||||
<h1 class="mt-2 text-xl font-semibold text-base-text lg:text-2xl">欢迎回来,{{ userStore.userInfo.nickName }}</h1>
|
||||
<p class="mt-2 text-sm text-muted-foreground">{{ today }} · 当前展示后台实时数据</p>
|
||||
</kra-card>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<kra-card>
|
||||
<kra-chart :type="1" title="访问人数" />
|
||||
</kra-card>
|
||||
<kra-card>
|
||||
<kra-chart :type="2" title="新增客户" />
|
||||
</kra-card>
|
||||
<kra-card>
|
||||
<kra-chart :type="3" title="解决数量" />
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<kra-card v-for="item in statistics" :key="item.title">
|
||||
<div class="p-2"><div class="text-sm text-muted-foreground">{{ item.title }}</div><div class="mt-3 text-3xl font-semibold text-base-text">{{ item.value }}</div></div>
|
||||
</kra-card>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 items-stretch gap-2 xl:grid-cols-12">
|
||||
<div class="grid grid-cols-1 gap-2 content-start xl:col-span-8 xl:h-full">
|
||||
<kra-card title="内容数据">
|
||||
<kra-chart :type="4" />
|
||||
</kra-card>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 xl:col-span-4 xl:h-full">
|
||||
<kra-card title="快捷功能" show-action custom-class="min-h-[300px]">
|
||||
<kra-quick-link />
|
||||
</kra-card>
|
||||
<kra-card title="公告" show-action custom-class="min-h-[300px]">
|
||||
<kra-notice />
|
||||
</kra-card>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 items-stretch gap-2 xl:grid-cols-2">
|
||||
<kra-card title="快捷功能" show-action custom-class="min-h-[300px]"><kra-quick-link /></kra-card>
|
||||
<kra-card title="公告" show-action custom-class="min-h-[300px]"><kra-notice /></kra-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
KraChart,
|
||||
KraNotice,
|
||||
KraQuickLink,
|
||||
KraCard
|
||||
} from './components'
|
||||
|
||||
const today = computed(() => {
|
||||
try {
|
||||
const d = new Date()
|
||||
return d.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
})
|
||||
} catch (e) {
|
||||
return new Date().toISOString().slice(0, 10)
|
||||
}
|
||||
})
|
||||
|
||||
defineOptions({
|
||||
name: 'Dashboard'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { getUserList } from '@/api/user'
|
||||
import { getSysOperationRecordList } from '@/api/sysOperationRecord'
|
||||
import { getFileList } from '@/api/fileUploadAndDownload'
|
||||
import { getInfoList } from '@/modules/announcement/api/info'
|
||||
import { KraNotice, KraQuickLink, KraCard } from './components'
|
||||
defineOptions({ name: 'Dashboard' })
|
||||
const userStore = useUserStore()
|
||||
const today = computed(() => new Date().toLocaleDateString('zh-CN'))
|
||||
const statistics = ref([{ title: '系统用户', value: '—' }, { title: '操作记录', value: '—' }, { title: '媒体文件', value: '—' }, { title: '系统公告', value: '—' }])
|
||||
onMounted(async () => {
|
||||
const results = await Promise.allSettled([getUserList({ page: 1, pageSize: 1 }), getSysOperationRecordList({ page: 1, pageSize: 1 }), getFileList({ page: 1, pageSize: 1 }), getInfoList({ page: 1, pageSize: 1 })])
|
||||
results.forEach((result, index) => { if (result.status === 'fulfilled' && result.value?.code === 0) statistics.value[index].value = result.value.data?.total ?? 0 })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user