前端代码
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div
|
||||
class="kra-card bg-container text-base-text"
|
||||
:class="[customClass || '', withoutPadding ? 'p-0' : 'p-4']"
|
||||
>
|
||||
<div v-if="title" class="flex justify-between items-center">
|
||||
<div class="text-sm font-semibold tracking-tight text-base-text">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div
|
||||
v-if="showAction"
|
||||
class="text-xs text-muted-foreground hover:text-active cursor-pointer"
|
||||
>
|
||||
更多
|
||||
</div>
|
||||
</div>
|
||||
<div :class="title ? 'mt-3' : ''">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
customClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
withoutPadding: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<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>
|
||||
@@ -0,0 +1,132 @@
|
||||
<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>
|
||||
@@ -0,0 +1,49 @@
|
||||
<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>
|
||||
@@ -0,0 +1,11 @@
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<el-scrollbar max-height="320px">
|
||||
<div class="space-y-2 pr-1">
|
||||
<div
|
||||
v-for="(item, index) in notices"
|
||||
:key="index"
|
||||
class="group rounded-lg border border-black/10 bg-white/70 p-3 transition-all duration-200 hover:-translate-y-0.5 hover:border-black/20 hover:shadow-sm dark:border-white/10 dark:bg-white/[0.02] dark:hover:border-white/20"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="mt-1.5 h-2.5 w-2.5 shrink-0 rounded-full" :class="item.dotClass" />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="rounded-md px-2 py-0.5 text-[11px] font-semibold leading-4" :class="item.tagClass">
|
||||
{{ item.typeTitle }}
|
||||
</span>
|
||||
<span class="shrink-0 text-[11px] text-muted-foreground">{{ item.time }}</span>
|
||||
</div>
|
||||
<el-tooltip effect="light" :content="item.title" placement="top">
|
||||
<p class="mt-1.5 line-clamp-2 text-sm text-base-text">
|
||||
{{ item.title }}
|
||||
</p>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const notices = [
|
||||
{
|
||||
typeTitle: '通知',
|
||||
time: '今天',
|
||||
title: 'Kra 管理后台已完成核心系统、公告和邮件模块接入。',
|
||||
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>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="h-full space-y-5">
|
||||
<div>
|
||||
<div class="mb-2 text-xs tracking-wide text-muted-foreground">常用入口</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<button
|
||||
v-for="(item, index) in shortcuts"
|
||||
:key="index"
|
||||
class="group flex w-full items-center gap-3 rounded-lg border border-black/10 bg-white/70 p-2.5 text-left transition-all duration-200 hover:border-[var(--el-color-primary)] hover:shadow-sm dark:border-white/10 dark:bg-white/[0.02]"
|
||||
type="button"
|
||||
@click="toPath(item)"
|
||||
>
|
||||
<span
|
||||
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-base-text transition-colors group-hover:bg-[var(--el-color-primary)] group-hover:text-white"
|
||||
>
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
</span>
|
||||
<span class="min-w-0 text-sm text-base-text">{{ item.title }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Menu,
|
||||
Link,
|
||||
User,
|
||||
Service,
|
||||
Bell,
|
||||
Message
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const toPath = (item) => {
|
||||
router.push({ name: item.path })
|
||||
}
|
||||
|
||||
const shortcuts = [
|
||||
{ icon: Menu, title: '菜单管理', path: 'menu' },
|
||||
{ icon: Link, title: 'API管理', path: 'api' },
|
||||
{ icon: Service, title: '角色管理', path: 'authority' },
|
||||
{ icon: User, title: '用户管理', path: 'user' },
|
||||
{ icon: Bell, title: '公告管理', path: 'anInfo' },
|
||||
{ icon: Message, title: '邮件发送', path: 'email' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<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>
|
||||
|
||||
<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="解决数量" />
|
||||
</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>
|
||||
</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'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Reference in New Issue
Block a user