Initial commit

This commit is contained in:
root
2025-12-21 09:58:23 -05:00
committed by Sebastian Krupinski
commit 9db14ce44b
20 changed files with 2710 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import type { ComposeOption } from 'echarts/core'
import type { BarSeriesOption } from 'echarts/charts'
import type { GridComponentOption, TooltipComponentOption, TitleComponentOption } from 'echarts/components'
type ECOption = ComposeOption<
BarSeriesOption | GridComponentOption | TooltipComponentOption | TitleComponentOption
>
const chartRef = ref<HTMLElement>()
const isReady = ref(false)
const option: ECOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
},
xAxis: [
{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true,
},
},
],
yAxis: [
{
type: 'value',
axisTick: {
show: false,
},
},
],
series: [
{
name: 'pageA',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220],
},
{
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220],
},
{
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220],
},
],
}
onMounted(async () => {
await nextTick()
// Simple delay to let DOM settle, then render
setTimeout(() => {
console.log('ChartBar: rendering chart')
isReady.value = true
}, 500)
})
</script>
<template>
<div ref="chartRef" class="chart-container">
<v-chart
v-if="isReady"
:option="option"
:style="{ width: '100%', height: '100%' }"
autoresize
/>
</div>
</template>

View File

@@ -0,0 +1,123 @@
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import type { ComposeOption } from 'echarts/core'
import type { LineSeriesOption } from 'echarts/charts'
import type { GridComponentOption, TooltipComponentOption, VisualMapComponentOption } from 'echarts/components'
type ECOption = ComposeOption<
LineSeriesOption | GridComponentOption | TooltipComponentOption | VisualMapComponentOption
>
const chartRef = ref<HTMLElement>()
const isReady = ref(false)
const data = [
['2022-06-05', 116],
['2022-06-06', 129],
['2022-06-07', 135],
['2022-06-08', 86],
['2022-06-09', 73],
['2022-06-10', 85],
['2022-06-11', 73],
['2022-06-12', 68],
['2022-06-13', 92],
['2022-06-14', 130],
['2022-06-15', 245],
['2022-06-16', 139],
['2022-06-17', 115],
['2022-06-18', 111],
['2022-06-19', 309],
['2022-06-20', 206],
['2022-06-21', 137],
['2022-06-22', 128],
['2022-06-23', 85],
['2022-06-24', 94],
['2022-06-25', 71],
['2022-06-26', 106],
['2022-06-27', 84],
['2022-06-28', 93],
['2022-06-29', 85],
['2022-06-30', 73],
['2022-07-01', 83],
['2022-07-02', 125],
['2022-07-03', 107],
['2022-07-04', 82],
['2022-07-05', 44],
['2022-07-06', 72],
['2022-07-07', 106],
['2022-07-08', 107],
['2022-07-09', 66],
['2022-07-10', 91],
['2022-07-11', 92],
['2022-07-12', 113],
['2022-07-13', 107],
['2022-07-14', 131],
['2022-07-15', 111],
['2022-07-16', 64],
['2022-07-17', 69],
['2022-07-18', 88],
['2022-07-19', 77],
['2022-07-20', 83],
['2022-07-21', 111],
['2022-07-22', 57],
['2022-07-23', 55],
['2022-07-24', 60],
]
const option: ECOption = {
backgroundColor: 'transparent',
dataset: { source: data },
visualMap: {
show: false,
type: 'continuous',
min: 0,
max: 400,
},
tooltip: {
trigger: 'axis',
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
},
xAxis: {
type: 'time',
},
yAxis: {
type: 'value',
},
series: [
{
name: 'value',
type: 'line',
showSymbol: false,
lineStyle: {
width: 4,
},
},
],
}
onMounted(async () => {
await nextTick()
// Simple delay to let DOM settle, then render
setTimeout(() => {
console.log('ChartLine: rendering chart')
isReady.value = true
}, 500)
})
</script>
<template>
<div ref="chartRef" class="chart-container">
<v-chart
v-if="isReady"
:option="option"
:style="{ width: '100%', height: '100%' }"
autoresize
/>
</div>
</template>

View File

@@ -0,0 +1,63 @@
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import type { ComposeOption } from 'echarts/core'
import type { PieSeriesOption } from 'echarts/charts'
import type { LegendComponentOption, TitleComponentOption } from 'echarts/components'
type ECOption = ComposeOption<
PieSeriesOption | LegendComponentOption | TitleComponentOption
>
const chartRef = ref<HTMLElement>()
const isReady = ref(false)
const option: ECOption = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
},
legend: {
left: 'center',
bottom: '10',
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts'],
},
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: 'Industries' },
{ value: 240, name: 'Technology' },
{ value: 149, name: 'Forex' },
{ value: 100, name: 'Gold' },
{ value: 59, name: 'Forecasts' },
],
animationEasing: 'cubicInOut',
},
],
}
onMounted(async () => {
await nextTick()
// Simple delay to let DOM settle, then render
setTimeout(() => {
console.log('ChartPie: rendering chart')
isReady.value = true
}, 500)
})
</script>
<template>
<div ref="chartRef" class="chart-container">
<v-chart
v-if="isReady"
:option="option"
:style="{ width: '100%', height: '100%' }"
autoresize
/>
</div>
</template>

View File

@@ -0,0 +1,93 @@
<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue'
import type { ComposeOption } from 'echarts/core'
import type { RadarSeriesOption } from 'echarts/charts'
import type { LegendComponentOption, TitleComponentOption } from 'echarts/components'
type ECOption = ComposeOption<
RadarSeriesOption | LegendComponentOption | TitleComponentOption
>
const chartRef = ref<HTMLElement>()
const isReady = ref(false)
const option: ECOption = {
backgroundColor: 'transparent',
radar: {
radius: '66%',
center: ['50%', '42%'],
splitNumber: 8,
splitArea: {
areaStyle: {
color: 'rgba(127,95,132,.3)',
opacity: 1,
shadowBlur: 45,
shadowColor: 'rgba(0,0,0,.5)',
shadowOffsetX: 0,
shadowOffsetY: 15,
},
},
indicator: [
{ name: 'Sales' },
{ name: 'Administration' },
{ name: 'Technology' },
{ name: 'Customer Support' },
{ name: 'Development' },
{ name: 'Marketing' },
],
},
legend: {
left: 'center',
bottom: '10',
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending'],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 13,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [5000, 7000, 12000, 11000, 15000, 14000],
name: 'Allocated Budget',
},
{
value: [4000, 9000, 15000, 15000, 13000, 11000],
name: 'Expected Spending',
},
{
value: [5500, 5000, 12000, 15000, 8000, 6000],
name: 'Actual Spending',
},
],
},
],
}
onMounted(async () => {
await nextTick()
// Simple delay to let DOM settle, then render
setTimeout(() => {
console.log('ChartRadar: rendering chart')
isReady.value = true
}, 500)
})
</script>
<template>
<div ref="chartRef" class="chart-container">
<v-chart
v-if="isReady"
:option="option"
:style="{ width: '100%', height: '100%' }"
autoresize
/>
</div>
</template>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
withDefaults(
defineProps<{
icon: string
iconClass?: string
color: string
title: string
value: number | null
unit?: string
formatter?: (v: number) => string
}>(),
{
iconClass: '',
value: null,
unit: '',
formatter: (v: number) => v.toString(),
},
)
</script>
<template>
<v-card class="stats-card" v-bind="$attrs">
<v-icon class="stats-icon" :color="color" :class="iconClass" :icon="icon" />
<div class="card-title ml-auto text-right">
<span
class="card-title--name font-weight-bold text--darken-2"
:class="`${color}--text`"
v-text="title"
/>
<h3
class="font-weight-regular text--primary d-inline-block ml-2"
style="font-size: 18px"
>
{{ value != null ? formatter(value) : '' }}
<small v-if="unit">{{ unit }}</small>
</h3>
<v-divider />
</div>
<div
class="v-alert__border v-alert__border--top v-alert__border--has-color"
:class="color"
/>
<div
v-if="$slots.footer"
class="grey--text text-right stats-footer text-caption"
>
<slot name="footer" />
</div>
</v-card>
</template>
<style lang="scss" scoped>
.stats-card {
padding: 5px;
padding-top: 10px;
.card-title {
width: fit-content;
.card-title--name {
display: inline-block;
backdrop-filter: blur(3px);
}
}
.caption {
font-size: 12px;
letter-spacing: 0;
}
.stats-icon {
position: absolute;
opacity: 0.3;
:deep(svg) {
height: 35px;
}
}
.stats-footer {
:deep(span) {
display: inline-block;
font-size: 12px !important;
letter-spacing: 0 !important;
}
}
}
</style>

15
src/integrations.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { ModuleIntegrations } from "@KTXC/types/moduleTypes";
const integrations: ModuleIntegrations = {
app_menu: [
{
id: 'home',
label: 'Dashboard',
path: '/',
icon: 'mdi-view-dashboard-outline',
priority: 1,
},
],
};
export default integrations;

56
src/main.ts Normal file
View File

@@ -0,0 +1,56 @@
import routes from '@/routes'
import integrations from '@/integrations'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import VChart from 'vue-echarts'
import type { App as Vue } from 'vue'
import {
LineChart,
BarChart,
PieChart,
RadarChart,
EffectScatterChart,
ScatterChart,
} from 'echarts/charts'
import {
LegendComponent,
TooltipComponent,
GridComponent,
TitleComponent,
VisualMapComponent,
DataZoomComponent,
ToolboxComponent,
MarkPointComponent,
DatasetComponent,
} from 'echarts/components'
// CSS filename is injected by the vite plugin at build time
export const css = ['__CSS_FILENAME_PLACEHOLDER__']
export { routes, integrations }
export default {
install(app: Vue) {
use([
LineChart,
BarChart,
PieChart,
RadarChart,
EffectScatterChart,
ScatterChart,
CanvasRenderer,
LegendComponent,
TooltipComponent,
GridComponent,
TitleComponent,
VisualMapComponent,
DataZoomComponent,
ToolboxComponent,
MarkPointComponent,
DatasetComponent,
])
app.component('VChart', VChart)
}
}

9
src/routes.ts Normal file
View File

@@ -0,0 +1,9 @@
const routes = [
{
name: 'home',
path: '/',
component: () => import('@/views/Main.vue'),
},
];
export default routes;

1
src/style.css Normal file
View File

@@ -0,0 +1 @@
/* dashboard module styles */

131
src/views/Main.vue Normal file
View File

@@ -0,0 +1,131 @@
<script setup lang="ts">
import { ref } from 'vue'
import ChartRadar from '@/components/ChartRadar.vue'
import ChartLine from '@/components/ChartLine.vue'
import ChartBar from '@/components/ChartBar.vue'
import ChartPie from '@/components/ChartPie.vue'
import StatsCard from '@/components/StatsCard.vue'
// Import MDI icons
import { mdiWeb, mdiRss, mdiSend, mdiBell, mdiGithub, mdiCurrencyCny } from '@mdi/js'
const stats = ref([
{
icon: mdiWeb,
title: 'Bandwidth',
value: 230,
unit: 'GB',
color: 'primary',
caption: 'Up: 100, Down: 130',
},
{
icon: mdiRss,
title: 'Submissions',
value: 108,
color: 'primary',
caption: 'Too young, too naive',
},
{
icon: mdiSend,
title: 'Requests',
value: 1238,
color: 'warning',
caption: 'Limit: 1320',
},
{
icon: mdiBell,
title: 'Messages',
value: 9042,
color: 'primary',
caption: 'Warnings: 300, erros: 47',
},
{
icon: mdiGithub,
title: 'Github Stars',
value: NaN,
color: 'grey',
caption: 'API has no response',
},
{
icon: mdiCurrencyCny,
title: 'Total Fee',
value: 2300,
unit: '¥',
color: 'error',
caption: 'Upper Limit: 2000 ¥',
},
])
</script>
<template>
<v-container fluid>
<v-row>
<v-col
v-for="stat in stats"
:key="stat.title"
cols="12"
sm="6"
md="4"
lg="2"
>
<StatsCard
:title="stat.title"
:unit="stat.unit"
:color="stat.color"
:icon="stat.icon"
:value="stat.value"
>
<template #footer>
{{ stat.caption }}
</template>
</StatsCard>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6" lg="12">
<v-card class="pa-2 chart-card">
<ChartLine />
</v-card>
</v-col>
<v-col cols="12" md="6" lg="4">
<v-card class="pa-2 chart-card">
<ChartRadar />
</v-card>
</v-col>
<v-col cols="12" md="6" lg="4">
<v-card class="pa-2 chart-card">
<ChartPie />
</v-card>
</v-col>
<v-col cols="12" md="6" lg="4">
<v-card class="pa-2 chart-card">
<ChartBar />
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<style scoped lang="scss">
.chart-card {
height: 350px;
display: flex;
flex-direction: column;
:deep(.chart-container) {
flex: 1;
width: 100%;
min-height: 300px;
position: relative;
}
:deep(.v-chart) {
width: 100% !important;
height: 100% !important;
position: absolute;
top: 0;
left: 0;
}
}
</style>

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />