前端代码

This commit is contained in:
2026-08-13 22:35:13 +08:00
parent 9044bafdff
commit f1a0b17bb0
412 changed files with 36769 additions and 6617 deletions
@@ -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>
+81
View File
@@ -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>