feat: add dashboard page
parent
199d5506ac
commit
c58c0797ba
|
@ -5,7 +5,7 @@ const fakeUserList = [
|
|||
accessToken: 'fakeAdminToken',
|
||||
avatar: '',
|
||||
desc: 'manager',
|
||||
homePath: '/welcome',
|
||||
homePath: '/',
|
||||
password: '123456',
|
||||
realName: 'Vben Admin',
|
||||
roles: [
|
||||
|
@ -21,7 +21,7 @@ const fakeUserList = [
|
|||
accessToken: 'fakeTestToken',
|
||||
avatar: '',
|
||||
desc: 'tester',
|
||||
homePath: '/welcome',
|
||||
homePath: '/',
|
||||
password: '123456',
|
||||
realName: 'test user',
|
||||
roles: [
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.11",
|
||||
"pinia": "2.1.7",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales/helper';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
order: -1,
|
||||
title: $t('page.dashboard.title'),
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
redirect: '/analytics',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: () => import('#/views/dashboard/analytics/index.vue'),
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: $t('page.dashboard.analytics'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||
meta: {
|
||||
title: $t('page.dashboard.workspace'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
|
@ -9,7 +9,7 @@ const routes: RouteRecordRaw[] = [
|
|||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'mdi:lightbulb-error-outline',
|
||||
title: $t('page.fallback.page'),
|
||||
title: $t('page.fallback.title'),
|
||||
},
|
||||
name: 'FallbackLayout',
|
||||
path: '/fallback',
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { BasicLayout } from '#/layouts';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
hideChildrenInMenu: true,
|
||||
order: -1,
|
||||
title: '首页',
|
||||
},
|
||||
name: 'Home',
|
||||
path: '/',
|
||||
redirect: '/welcome',
|
||||
children: [
|
||||
{
|
||||
name: 'Welcome',
|
||||
path: '/welcome',
|
||||
component: () => import('#/views/dashboard/index.vue'),
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: 'Welcome',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
|
@ -11,7 +11,7 @@ const routes: RouteRecordRaw[] = [
|
|||
icon: 'ic:round-menu',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: $t('page.nested.page'),
|
||||
title: $t('page.nested.title'),
|
||||
},
|
||||
name: 'Nested',
|
||||
path: '/nested',
|
||||
|
|
|
@ -9,7 +9,7 @@ const routes: RouteRecordRaw[] = [
|
|||
component: BasicLayout,
|
||||
meta: {
|
||||
icon: 'ic:round-settings-input-composite',
|
||||
title: $t('page.outside.page'),
|
||||
title: $t('page.outside.title'),
|
||||
},
|
||||
name: 'Outside',
|
||||
path: '/outside',
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { InitStoreOptions } from '@vben-core/stores';
|
|||
|
||||
import type { App } from 'vue';
|
||||
|
||||
import { initStore } from '@vben-core/stores';
|
||||
import { initStore, useAccessStore, useTabsStore } from '@vben-core/stores';
|
||||
|
||||
/**
|
||||
* @zh_CN 初始化pinia
|
||||
|
@ -13,4 +13,4 @@ async function setupStore(app: App, options: InitStoreOptions) {
|
|||
app.use(pinia);
|
||||
}
|
||||
|
||||
export { setupStore };
|
||||
export { setupStore, useAccessStore, useTabsStore };
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'AnalyticsTrends' });
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
|
||||
36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
|
||||
111,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
{
|
||||
areaStyle: {},
|
||||
data: [
|
||||
33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
|
||||
11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#019680',
|
||||
},
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#019680',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
|
@ -0,0 +1,82 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'AnalyticsVisitsData' });
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: 0,
|
||||
data: ['访问', '趋势'],
|
||||
},
|
||||
radar: {
|
||||
indicator: [
|
||||
{
|
||||
name: '网页',
|
||||
},
|
||||
{
|
||||
name: '移动端',
|
||||
},
|
||||
{
|
||||
name: 'Ipad',
|
||||
},
|
||||
{
|
||||
name: '客户端',
|
||||
},
|
||||
{
|
||||
name: '第三方',
|
||||
},
|
||||
{
|
||||
name: '其它',
|
||||
},
|
||||
],
|
||||
radius: '60%',
|
||||
splitNumber: 8,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
areaStyle: {
|
||||
opacity: 1,
|
||||
shadowBlur: 0,
|
||||
shadowColor: 'rgba(0,0,0,.2)',
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 10,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#b6a2de',
|
||||
},
|
||||
name: '访问',
|
||||
value: [90, 50, 86, 40, 50, 20],
|
||||
},
|
||||
{
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
name: '趋势',
|
||||
value: [70, 75, 70, 76, 20, 85],
|
||||
},
|
||||
],
|
||||
itemStyle: {
|
||||
// borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
symbolSize: 0,
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
tooltip: {},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'AnalyticsVisitsSales' });
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 400;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
center: ['50%', '50%'],
|
||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||
data: [
|
||||
{ name: '外包', value: 500 },
|
||||
{ name: '定制', value: 310 },
|
||||
{ name: '技术支持', value: 274 },
|
||||
{ name: '远程', value: 400 },
|
||||
].sort((a, b) => {
|
||||
return a.value - b.value;
|
||||
}),
|
||||
name: '商业占比',
|
||||
radius: '80%',
|
||||
roseType: 'radius',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
|
@ -0,0 +1,65 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'AnalyticsVisitsSource' });
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
legend: {
|
||||
bottom: '2%',
|
||||
left: 'center',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
return Math.random() * 100;
|
||||
},
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
avoidLabelOverlap: false,
|
||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||
data: [
|
||||
{ name: '搜索引擎', value: 1048 },
|
||||
{ name: '直接访问', value: 735 },
|
||||
{ name: '邮件营销', value: 580 },
|
||||
{ name: '联盟广告', value: 484 },
|
||||
],
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: '12',
|
||||
fontWeight: 'bold',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
// borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
position: 'center',
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
name: '访问来源',
|
||||
radius: ['40%', '65%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'AnalyticsVisits' });
|
||||
|
||||
const chartRef = ref<EchartsUIType>();
|
||||
const { renderEcharts } = useEcharts(chartRef);
|
||||
|
||||
onMounted(() => {
|
||||
renderEcharts({
|
||||
grid: {
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
left: '1%',
|
||||
right: '1%',
|
||||
top: '2 %',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
barMaxWidth: 80,
|
||||
// color: '#4f69fd',
|
||||
data: [
|
||||
3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,
|
||||
3200, 4800,
|
||||
],
|
||||
type: 'bar',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
// color: '#4f69fd',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: {
|
||||
data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: {
|
||||
max: 8000,
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EchartsUI ref="chartRef" />
|
||||
</template>
|
|
@ -0,0 +1,92 @@
|
|||
<script lang="ts" setup>
|
||||
import type { TabsItem } from '@vben/types';
|
||||
import type { AnalysisOverviewItem } from '@vben/universal-ui';
|
||||
|
||||
import {
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgCardIcon,
|
||||
SvgDownloadIcon,
|
||||
} from '@vben/icons';
|
||||
import {
|
||||
AnalysisChartCard,
|
||||
AnalysisChartsTabs,
|
||||
AnalysisOverview,
|
||||
} from '@vben/universal-ui';
|
||||
|
||||
import AnalyticsTrends from './analytics-trends.vue';
|
||||
import AnalyticsVisits from './analytics-visits.vue';
|
||||
import AnalyticsVisitsData from './analytics-visits-data.vue';
|
||||
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
|
||||
import AnalyticsVisitsSource from './analytics-visits-source.vue';
|
||||
|
||||
defineOptions({ name: 'Analytics' });
|
||||
|
||||
const overviewItems: AnalysisOverviewItem[] = [
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
title: '用户量',
|
||||
totalTitle: '总用户量',
|
||||
totalValue: 120_000,
|
||||
value: 2000,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '访问量',
|
||||
totalTitle: '总访问量',
|
||||
totalValue: 500_000,
|
||||
value: 20_000,
|
||||
},
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
title: '下载量',
|
||||
totalTitle: '总下载量',
|
||||
totalValue: 120_000,
|
||||
value: 8000,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
title: '使用量',
|
||||
totalTitle: '总使用量',
|
||||
totalValue: 50_000,
|
||||
value: 5000,
|
||||
},
|
||||
];
|
||||
|
||||
const chartTabs: TabsItem[] = [
|
||||
{
|
||||
label: '流量趋势',
|
||||
value: 'trends',
|
||||
},
|
||||
{
|
||||
label: '月访问量',
|
||||
value: 'visits',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<AnalysisOverview :items="overviewItems" />
|
||||
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
|
||||
<template #trends>
|
||||
<AnalyticsTrends />
|
||||
</template>
|
||||
<template #visits>
|
||||
<AnalyticsVisits />
|
||||
</template>
|
||||
</AnalysisChartsTabs>
|
||||
|
||||
<div class="mt-5 w-full md:flex">
|
||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
|
||||
<AnalyticsVisitsData />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
</AnalysisChartCard>
|
||||
<AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
|
||||
<AnalyticsVisitsSales />
|
||||
</AnalysisChartCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,250 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
// import { ref } from 'vue';
|
||||
|
||||
// import { echartsInstance as echarts } from '@vben/chart-ui';
|
||||
|
||||
defineOptions({ name: 'Welcome' });
|
||||
|
||||
// const cardList = ref([
|
||||
// {
|
||||
// color: 'green',
|
||||
// extra: '月',
|
||||
// leftContent: '2000',
|
||||
// leftFooter: '总访问数',
|
||||
// rightContent: 'flat-color-icons:conference-call',
|
||||
// rightFooter: '5000',
|
||||
// title: '访问数',
|
||||
// },
|
||||
// {
|
||||
// color: 'red',
|
||||
// extra: '日',
|
||||
// leftContent: '$1350',
|
||||
// leftFooter: '总销售额',
|
||||
// rightContent: 'flat-color-icons:sales-performance',
|
||||
// rightFooter: '$550000',
|
||||
// title: '销售额',
|
||||
// },
|
||||
// ]);
|
||||
// const chartTabs = ref([
|
||||
// {
|
||||
// name: '1',
|
||||
// option: {
|
||||
// color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
|
||||
|
||||
// grid: {
|
||||
// bottom: '3%',
|
||||
// containLabel: true,
|
||||
// left: '3%',
|
||||
// right: '4%',
|
||||
// },
|
||||
// legend: {
|
||||
// data: ['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5'],
|
||||
// },
|
||||
// series: [
|
||||
// {
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// color: 'rgb(128, 255, 165)',
|
||||
// offset: 0,
|
||||
// },
|
||||
// {
|
||||
// color: 'rgb(1, 191, 236)',
|
||||
// offset: 1,
|
||||
// },
|
||||
// ]),
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// data: [140, 232, 101, 264, 90, 340, 250],
|
||||
// emphasis: {
|
||||
// focus: 'series',
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 0,
|
||||
// },
|
||||
// name: 'Line 1',
|
||||
// showSymbol: false,
|
||||
// smooth: true,
|
||||
// stack: 'Total',
|
||||
// type: 'line',
|
||||
// },
|
||||
// {
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// color: 'rgb(0, 221, 255)',
|
||||
// offset: 0,
|
||||
// },
|
||||
// {
|
||||
// color: 'rgb(77, 119, 255)',
|
||||
// offset: 1,
|
||||
// },
|
||||
// ]),
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// data: [120, 282, 111, 234, 220, 340, 310],
|
||||
// emphasis: {
|
||||
// focus: 'series',
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 0,
|
||||
// },
|
||||
// name: 'Line 2',
|
||||
// showSymbol: false,
|
||||
// smooth: true,
|
||||
// stack: 'Total',
|
||||
// type: 'line',
|
||||
// },
|
||||
// {
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// color: 'rgb(55, 162, 255)',
|
||||
// offset: 0,
|
||||
// },
|
||||
// {
|
||||
// color: 'rgb(116, 21, 219)',
|
||||
// offset: 1,
|
||||
// },
|
||||
// ]),
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// data: [320, 132, 201, 334, 190, 130, 220],
|
||||
// emphasis: {
|
||||
// focus: 'series',
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 0,
|
||||
// },
|
||||
// name: 'Line 3',
|
||||
// showSymbol: false,
|
||||
// smooth: true,
|
||||
// stack: 'Total',
|
||||
// type: 'line',
|
||||
// },
|
||||
// {
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// color: 'rgb(255, 0, 135)',
|
||||
// offset: 0,
|
||||
// },
|
||||
// {
|
||||
// color: 'rgb(135, 0, 157)',
|
||||
// offset: 1,
|
||||
// },
|
||||
// ]),
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// data: [220, 402, 231, 134, 190, 230, 120],
|
||||
// emphasis: {
|
||||
// focus: 'series',
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 0,
|
||||
// },
|
||||
// name: 'Line 4',
|
||||
// showSymbol: false,
|
||||
// smooth: true,
|
||||
// stack: 'Total',
|
||||
// type: 'line',
|
||||
// },
|
||||
// {
|
||||
// areaStyle: {
|
||||
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
// {
|
||||
// color: 'rgb(255, 191, 0)',
|
||||
// offset: 0,
|
||||
// },
|
||||
// {
|
||||
// color: 'rgb(224, 62, 76)',
|
||||
// offset: 1,
|
||||
// },
|
||||
// ]),
|
||||
// opacity: 0.8,
|
||||
// },
|
||||
// data: [220, 302, 181, 234, 210, 290, 150],
|
||||
// emphasis: {
|
||||
// focus: 'series',
|
||||
// },
|
||||
// label: {
|
||||
// position: 'top',
|
||||
// show: true,
|
||||
// },
|
||||
// lineStyle: {
|
||||
// width: 0,
|
||||
// },
|
||||
// name: 'Line 5',
|
||||
// showSymbol: false,
|
||||
// smooth: true,
|
||||
// stack: 'Total',
|
||||
// type: 'line',
|
||||
// },
|
||||
// ],
|
||||
// toolbox: {
|
||||
// feature: {
|
||||
// saveAsImage: {},
|
||||
// },
|
||||
// },
|
||||
// tooltip: {
|
||||
// axisPointer: {
|
||||
// type: 'cross',
|
||||
// // label: {
|
||||
// // backgroundColor: '#6a7985',
|
||||
// // },
|
||||
// },
|
||||
// trigger: 'axis',
|
||||
// },
|
||||
// xAxis: [
|
||||
// {
|
||||
// boundaryGap: false,
|
||||
// data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
// type: 'category',
|
||||
// },
|
||||
// ],
|
||||
// yAxis: [
|
||||
// {
|
||||
// type: 'value',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// title: '流量趋势',
|
||||
// },
|
||||
// {
|
||||
// name: '2',
|
||||
// option: {
|
||||
// series: [
|
||||
// {
|
||||
// data: [
|
||||
// 120,
|
||||
// {
|
||||
// itemStyle: {
|
||||
// color: '#a90000',
|
||||
// },
|
||||
// value: 200,
|
||||
// },
|
||||
// 150,
|
||||
// 80,
|
||||
// 70,
|
||||
// 110,
|
||||
// 130,
|
||||
// ],
|
||||
// type: 'bar',
|
||||
// },
|
||||
// ],
|
||||
// xAxis: {
|
||||
// data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
// type: 'category',
|
||||
// },
|
||||
// yAxis: {
|
||||
// type: 'value',
|
||||
// },
|
||||
// },
|
||||
// title: '访问量',
|
||||
// },
|
||||
// ]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>dashboard</div>
|
||||
</template>
|
|
@ -0,0 +1,125 @@
|
|||
<script lang="ts" setup>
|
||||
import type {
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickNavItem,
|
||||
} from '@vben/universal-ui';
|
||||
|
||||
import {
|
||||
WorkbenchHeader,
|
||||
WorkbenchProject,
|
||||
WorkbenchQuickNav,
|
||||
} from '@vben/universal-ui';
|
||||
import { preferences } from '@vben-core/preferences';
|
||||
|
||||
import { useAccessStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Workspace' });
|
||||
|
||||
const { userInfo } = useAccessStore();
|
||||
|
||||
const projectItems: WorkbenchProjectItem[] = [
|
||||
{
|
||||
color: '',
|
||||
content: '不要等待机会,而要创造机会。',
|
||||
date: '2021-04-01',
|
||||
group: '开源组',
|
||||
icon: 'carbon:logo-github',
|
||||
title: 'Github',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
content: '现在的你决定将来的你。',
|
||||
date: '2021-04-01',
|
||||
group: '算法组',
|
||||
icon: 'ion:logo-vue',
|
||||
title: 'Vue',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
content: '没有什么才能比努力更重要。',
|
||||
date: '2021-04-01',
|
||||
group: '上班摸鱼',
|
||||
icon: 'ion:logo-html5',
|
||||
title: 'Html5',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
content: '热情和欲望可以突破一切难关。',
|
||||
date: '2021-04-01',
|
||||
group: 'UI',
|
||||
icon: 'ion:logo-angular',
|
||||
title: 'Angular',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
content: '健康的身体是实现目标的基石。',
|
||||
date: '2021-04-01',
|
||||
group: '技术牛',
|
||||
icon: 'bx:bxl-react',
|
||||
title: 'React',
|
||||
},
|
||||
{
|
||||
color: '#EBD94E',
|
||||
content: '路是走出来的,而不是空想出来的。',
|
||||
date: '2021-04-01',
|
||||
group: '架构组',
|
||||
icon: 'ion:logo-javascript',
|
||||
title: 'Js',
|
||||
},
|
||||
];
|
||||
|
||||
const quickNavItems: WorkbenchQuickNavItem[] = [
|
||||
{
|
||||
color: '#1fdaca',
|
||||
icon: 'ion:home-outline',
|
||||
title: '首页',
|
||||
},
|
||||
{
|
||||
color: '#bf0c2c',
|
||||
icon: 'ion:grid-outline',
|
||||
title: '仪表盘',
|
||||
},
|
||||
{
|
||||
color: '#e18525',
|
||||
icon: 'ion:layers-outline',
|
||||
title: '组件',
|
||||
},
|
||||
{
|
||||
color: '#3fb27f',
|
||||
icon: 'ion:settings-outline',
|
||||
title: '系统管理',
|
||||
},
|
||||
{
|
||||
color: '#4daf1bc9',
|
||||
icon: 'ion:key-outline',
|
||||
title: '权限管理',
|
||||
},
|
||||
{
|
||||
color: '#00d8ff',
|
||||
icon: 'ion:bar-chart-outline',
|
||||
title: '图表',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<WorkbenchHeader
|
||||
:avatar="userInfo?.avatar || preferences.app.defaultAvatar"
|
||||
>
|
||||
<template #title>
|
||||
早安, {{ userInfo?.realName }}, 开始您一天的工作吧!
|
||||
</template>
|
||||
<template #description> 今日晴,20℃ - 32℃! </template>
|
||||
</WorkbenchHeader>
|
||||
|
||||
<div class="mt-5 flex">
|
||||
<div class="mr-4 w-full md:w-2/3">
|
||||
<WorkbenchProject :items="projectItems" title="项目" />
|
||||
</div>
|
||||
<div class="w-full md:w-1/3">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -5,6 +5,7 @@
|
|||
"words": [
|
||||
"clsx",
|
||||
"esno",
|
||||
"unref",
|
||||
"taze",
|
||||
"acmr",
|
||||
"antd",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@changesets/git": "^3.0.0",
|
||||
"@manypkg/get-packages": "^2.2.1",
|
||||
"@manypkg/get-packages": "^2.2.2",
|
||||
"consola": "^3.2.3",
|
||||
"dayjs": "^1.11.11",
|
||||
"find-up": "^7.0.0",
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"tailwindcss": "^3.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.222",
|
||||
"@iconify/json": "^2.2.223",
|
||||
"@iconify/tailwind": "^1.1.1",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
|
||||
|
|
|
@ -20,6 +20,6 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@vben/types": "workspace:*",
|
||||
"vite": "^5.3.1"
|
||||
"vite": "^5.3.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"useDefineForClassFields": true,
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["vite/client", "@vben/types/window"],
|
||||
"types": ["vite/client"],
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@
|
|||
"rollup": "^4.18.0",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.6",
|
||||
"unplugin-turbo-console": "^1.8.8-beta.1",
|
||||
"vite": "^5.3.1",
|
||||
"unplugin-turbo-console": "^1.8.9",
|
||||
"vite": "^5.3.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-dts": "^3.9.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
|
|
|
@ -43,7 +43,7 @@ interface CommonPluginOptions {
|
|||
/** 环境变量 */
|
||||
env?: Record<string, any>;
|
||||
/** 是否开启注入metadata */
|
||||
injectMetadata: boolean;
|
||||
injectMetadata?: boolean;
|
||||
/** 是否构建模式 */
|
||||
isBuild?: boolean;
|
||||
/** 构建模式 */
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"turbo": "^2.0.5",
|
||||
"typescript": "^5.5.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vite": "^5.3.1",
|
||||
"vite": "^5.3.2",
|
||||
"vitest": "^2.0.0-beta.10",
|
||||
"vue-tsc": "^2.0.22"
|
||||
},
|
||||
|
@ -86,7 +86,7 @@
|
|||
"@ant-design/colors": "^7.0.2",
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
},
|
||||
"neverBuiltDependencies": [
|
||||
"canvas",
|
||||
|
|
|
@ -34,6 +34,6 @@
|
|||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ const defaultPreferences: Preferences = {
|
|||
styleType: 'normal',
|
||||
},
|
||||
footer: {
|
||||
enable: true,
|
||||
enable: false,
|
||||
fixed: true,
|
||||
},
|
||||
header: {
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('preferences', () => {
|
|||
});
|
||||
|
||||
it('initPreferences should initialize preferences with overrides and namespace', async () => {
|
||||
const overrides = { theme: { colorPrimary: 'hsl(211 91% 39%)' } };
|
||||
const overrides = { theme: { colorPrimary: 'hsl(245 82% 67%)' } };
|
||||
const namespace = 'testNamespace';
|
||||
|
||||
await preferenceManager.initPreferences({ namespace, overrides });
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
"@vben-core/typings": "workspace:*",
|
||||
"pinia": "2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { TabItem } from '@vben-core/typings';
|
||||
import type { RouteRecordNormalized, Router } from 'vue-router';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import { startProgress, stopProgress } from '@vben-core/toolkit';
|
||||
import { TabItem } from '@vben-core/typings';
|
||||
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Icon } from '@iconify/vue';
|
|||
|
||||
function createIcon(name: string) {
|
||||
return defineComponent({
|
||||
name: `SvgIcon-${name}`,
|
||||
setup(props, { attrs }) {
|
||||
return () => h(Icon, { icon: name, ...props, ...attrs });
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/shared": "^3.4.30",
|
||||
"@vue/shared": "^3.4.31",
|
||||
"clsx": "2.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"nprogress": "^0.2.0",
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getElementVisibleHeight } from './dom'; // 假设函数位于 utils.ts 中
|
||||
|
||||
describe('getElementVisibleHeight', () => {
|
||||
// Mocking the getBoundingClientRect method
|
||||
const mockGetBoundingClientRect = vi.fn();
|
||||
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
|
||||
|
||||
beforeAll(() => {
|
||||
// Mock getBoundingClientRect method
|
||||
Element.prototype.getBoundingClientRect = mockGetBoundingClientRect;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Restore original getBoundingClientRect method
|
||||
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
|
||||
});
|
||||
|
||||
it('should return 0 if the element is null or undefined', () => {
|
||||
expect(getElementVisibleHeight(null)).toBe(0);
|
||||
expect(getElementVisibleHeight()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return the visible height of the element', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: 500,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: 100,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
// Mocking window.innerHeight and document.documentElement.clientHeight
|
||||
const originalInnerHeight = window.innerHeight;
|
||||
const originalClientHeight = document.documentElement.clientHeight;
|
||||
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(400);
|
||||
|
||||
// Restore original values
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: originalInnerHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: originalClientHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
|
||||
it('should return the visible height when element is partially out of viewport', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: 300,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: -100,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
// Mocking window.innerHeight and document.documentElement.clientHeight
|
||||
const originalInnerHeight = window.innerHeight;
|
||||
const originalClientHeight = document.documentElement.clientHeight;
|
||||
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(300);
|
||||
|
||||
// Restore original values
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: originalInnerHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: originalClientHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
|
||||
it('should return 0 if the element is completely out of viewport', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: -100,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: -500,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(0);
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 获取元素可见高度
|
||||
* @param element
|
||||
* @returns
|
||||
*/
|
||||
function getElementVisibleHeight(
|
||||
element?: HTMLElement | null | undefined,
|
||||
): number {
|
||||
if (!element) {
|
||||
return 0;
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
const viewHeight = Math.max(
|
||||
document.documentElement.clientHeight,
|
||||
window.innerHeight,
|
||||
);
|
||||
|
||||
const top = Math.max(rect.top, 0);
|
||||
const bottom = Math.min(rect.bottom, viewHeight);
|
||||
|
||||
return Math.max(0, bottom - top);
|
||||
}
|
||||
|
||||
export { getElementVisibleHeight };
|
|
@ -1,5 +1,6 @@
|
|||
export * from './cn';
|
||||
export * from './diff';
|
||||
export * from './dom';
|
||||
export * from './hash';
|
||||
export * from './inference';
|
||||
export * from './letter';
|
||||
|
@ -7,5 +8,6 @@ export * from './merge';
|
|||
export * from './namespace';
|
||||
export * from './nprogress';
|
||||
export * from './tree';
|
||||
export * from './unique';
|
||||
export * from './update-css-variables';
|
||||
export * from './window';
|
||||
|
|
|
@ -97,11 +97,20 @@ function isWindowsOs(): boolean {
|
|||
return windowsRegex.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查传入的值是否为数字
|
||||
* @param value
|
||||
*/
|
||||
function isNumber(value: any): value is number {
|
||||
return typeof value === 'number' && Number.isFinite(value);
|
||||
}
|
||||
|
||||
export {
|
||||
isEmpty,
|
||||
isFunction,
|
||||
isHttpUrl,
|
||||
isMacOs,
|
||||
isNumber,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { uniqueByField } from './unique';
|
||||
|
||||
describe('uniqueByField', () => {
|
||||
it('should return an array with unique items based on id field', () => {
|
||||
const items = [
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
{ id: 1, name: 'Duplicate Item' },
|
||||
];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toHaveLength(3); // After deduplication, there should be three objects left
|
||||
expect(uniqueItems).toEqual([
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when input array is empty', () => {
|
||||
const items: any[] = []; // Empty array
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle arrays with only one item correctly', () => {
|
||||
const items = [{ id: 1, name: 'Item 1' }];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toHaveLength(1);
|
||||
expect(uniqueItems).toEqual([{ id: 1, name: 'Item 1' }]);
|
||||
});
|
||||
|
||||
it('should preserve the order of the first occurrence of each item', () => {
|
||||
const items = [
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
{ id: 1, name: 'Duplicate Item' },
|
||||
];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results (order of first occurrences preserved)
|
||||
expect(uniqueItems).toEqual([
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* 根据指定字段对对象数组进行去重
|
||||
* @param arr 要去重的对象数组
|
||||
* @param key 去重依据的字段名
|
||||
* @returns 去重后的对象数组
|
||||
*/
|
||||
function uniqueByField<T>(arr: T[], key: keyof T): T[] {
|
||||
const seen = new Map<any, T>();
|
||||
return arr.filter((item) => {
|
||||
const value = item[key];
|
||||
return seen.has(value) ? false : (seen.set(value, item), true);
|
||||
});
|
||||
}
|
||||
|
||||
export { uniqueByField };
|
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,8 +39,9 @@
|
|||
"dependencies": {
|
||||
"@vben-core/iconify": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import type { ContentCompactType } from '@vben-core/typings';
|
|||
import type { CSSProperties } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { getElementVisibleHeight } from '@vben-core/toolkit';
|
||||
|
||||
import { useCssVar, useDebounceFn, useWindowSize } from '@vueuse/core';
|
||||
|
||||
interface Props {
|
||||
|
@ -54,12 +56,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
paddingTop: 16,
|
||||
});
|
||||
|
||||
const domElement = ref<HTMLDivElement | null>();
|
||||
const contentElement = ref<HTMLDivElement | null>();
|
||||
|
||||
const { height, width } = useWindowSize();
|
||||
const contentClientHeight = useCssVar('--vben-content-client-height');
|
||||
const debouncedCalcHeight = useDebounceFn(() => {
|
||||
contentClientHeight.value = `${domElement.value?.clientHeight ?? window.innerHeight}px`;
|
||||
contentClientHeight.value = `${getElementVisibleHeight(contentElement.value)}px`;
|
||||
}, 200);
|
||||
|
||||
const style = computed((): CSSProperties => {
|
||||
|
@ -97,7 +99,7 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main ref="domElement" :style="style">
|
||||
<main ref="contentElement" :style="style">
|
||||
<slot></slot>
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
@ -43,6 +43,6 @@
|
|||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"@vueuse/core": "^10.11.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"radix-vue": "^1.8.5",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-sonner": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ interface Props extends BacktopProps {}
|
|||
defineOptions({ name: 'BackTop' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
bottom: 40,
|
||||
bottom: 24,
|
||||
isGroup: false,
|
||||
right: 40,
|
||||
target: '',
|
||||
|
@ -32,7 +32,7 @@ const { handleClick, visible } = useBackTop(props);
|
|||
<VbenButton
|
||||
v-if="visible"
|
||||
:style="backTopStyle"
|
||||
class="bg-accent data fixed bottom-10 right-5 h-10 w-10 rounded-full"
|
||||
class="bg-accent hover:bg-heavy data fixed bottom-10 right-5 z-10 h-10 w-10 rounded-full"
|
||||
size="icon"
|
||||
variant="icon"
|
||||
@click="handleClick"
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, unref, watch, watchEffect } from 'vue';
|
||||
|
||||
import { isNumber } from '@vben-core/toolkit';
|
||||
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core';
|
||||
|
||||
interface Props {
|
||||
autoplay?: boolean;
|
||||
color?: string;
|
||||
decimal?: string;
|
||||
decimals?: number;
|
||||
duration?: number;
|
||||
endVal?: number;
|
||||
prefix?: string;
|
||||
separator?: string;
|
||||
startVal?: number;
|
||||
suffix?: string;
|
||||
transition?: keyof typeof TransitionPresets;
|
||||
useEasing?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'CountToAnimator' });
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
autoplay: true,
|
||||
color: '',
|
||||
decimal: '.',
|
||||
decimals: 0,
|
||||
duration: 1500,
|
||||
endVal: 2021,
|
||||
prefix: '',
|
||||
separator: ',',
|
||||
startVal: 0,
|
||||
suffix: '',
|
||||
transition: 'linear',
|
||||
useEasing: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onStarted', 'onFinished']);
|
||||
|
||||
const source = ref(props.startVal);
|
||||
const disabled = ref(false);
|
||||
let outputValue = useTransition(source);
|
||||
|
||||
const value = computed(() => formatNumber(unref(outputValue)));
|
||||
|
||||
watchEffect(() => {
|
||||
source.value = props.startVal;
|
||||
});
|
||||
|
||||
watch([() => props.startVal, () => props.endVal], () => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
props.autoplay && start();
|
||||
});
|
||||
|
||||
function start() {
|
||||
run();
|
||||
source.value = props.endVal;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
source.value = props.startVal;
|
||||
run();
|
||||
}
|
||||
|
||||
function run() {
|
||||
outputValue = useTransition(source, {
|
||||
disabled,
|
||||
duration: props.duration,
|
||||
onFinished: () => emit('onFinished'),
|
||||
onStarted: () => emit('onStarted'),
|
||||
...(props.useEasing
|
||||
? { transition: TransitionPresets[props.transition] }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
function formatNumber(num: number | string) {
|
||||
if (!num && num !== 0) {
|
||||
return '';
|
||||
}
|
||||
const { decimal, decimals, prefix, separator, suffix } = props;
|
||||
num = Number(num).toFixed(decimals);
|
||||
num += '';
|
||||
|
||||
const x = num.split('.');
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (separator && !isNumber(separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, `$1${separator}$2`);
|
||||
}
|
||||
}
|
||||
return prefix + x1 + x2 + suffix;
|
||||
}
|
||||
|
||||
defineExpose({ reset });
|
||||
</script>
|
||||
<template>
|
||||
<span :style="{ color }">
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
export { default as VbenCountToAnimator } from './count-to-animator.vue';
|
|
@ -6,6 +6,7 @@ export * from './breadcrumb';
|
|||
export * from './button';
|
||||
export * from './checkbox';
|
||||
export * from './context-menu';
|
||||
export * from './count-to-animator';
|
||||
export * from './dropdown-menu';
|
||||
export * from './floating-button-group';
|
||||
export * from './full-screen';
|
||||
|
|
|
@ -42,6 +42,6 @@
|
|||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vben-core/preferences": "workspace:*",
|
||||
"echarts": "^5.5.0",
|
||||
"vue": "^3.4.30"
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"echarts": "^5.5.1",
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { echartsInstance, ECOption } from './index';
|
||||
import { onMounted, ref, unref, warn } from 'vue';
|
||||
import { usePreferences } from '@vben-core/preferences';
|
||||
const { isDark } = usePreferences();
|
||||
interface Props {
|
||||
height?: string;
|
||||
width?: string;
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {
|
||||
height: '500px',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
const instance = ref();
|
||||
const instanceRef = ref(HTMLElement);
|
||||
onMounted(() => {
|
||||
instance.value = echartsInstance.init(
|
||||
instanceRef.value,
|
||||
isDark.value ? 'dark' : '',
|
||||
);
|
||||
});
|
||||
const setChart = (option: ECOption, clear: boolean = true) => {
|
||||
const c = unref(instance);
|
||||
if (!c) {
|
||||
warn('instance is null');
|
||||
return;
|
||||
}
|
||||
if (clear) c.clear();
|
||||
c.setOption(option);
|
||||
};
|
||||
defineExpose({ setChart });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="instanceRef" :style="{ height, width }"></div>
|
||||
</template>
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
interface Props {
|
||||
height?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
height: '300px',
|
||||
width: '100%',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-bind="$attrs" :style="{ height, width }"></div>
|
||||
</template>
|
|
@ -0,0 +1,59 @@
|
|||
import type {
|
||||
// 系列类型的定义后缀都为 SeriesOption
|
||||
BarSeriesOption,
|
||||
LineSeriesOption,
|
||||
} from 'echarts/charts';
|
||||
import type {
|
||||
DatasetComponentOption,
|
||||
GridComponentOption,
|
||||
// 组件类型的定义后缀都为 ComponentOption
|
||||
TitleComponentOption,
|
||||
TooltipComponentOption,
|
||||
} from 'echarts/components';
|
||||
import type { ComposeOption } from 'echarts/core';
|
||||
|
||||
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
|
||||
import {
|
||||
// 数据集组件
|
||||
DatasetComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
TitleComponent,
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
// 内置数据转换器组件 (filter, sort)
|
||||
TransformComponent,
|
||||
} from 'echarts/components';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
|
||||
export type ECOption = ComposeOption<
|
||||
| BarSeriesOption
|
||||
| DatasetComponentOption
|
||||
| GridComponentOption
|
||||
| LineSeriesOption
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
>;
|
||||
|
||||
// 注册必须的组件
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
PieChart,
|
||||
RadarChart,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
LabelLayout,
|
||||
UniversalTransition,
|
||||
CanvasRenderer,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
]);
|
||||
|
||||
export { echarts };
|
|
@ -0,0 +1,3 @@
|
|||
export * from './echarts';
|
||||
export { default as EchartsUI } from './echarts-ui.vue';
|
||||
export * from './use-echarts';
|
|
@ -0,0 +1,108 @@
|
|||
import type { EChartsOption } from 'echarts';
|
||||
|
||||
import type EchartsUI from './echarts-ui.vue';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, nextTick, watch } from 'vue';
|
||||
|
||||
import { usePreferences } from '@vben-core/preferences';
|
||||
|
||||
import {
|
||||
tryOnUnmounted,
|
||||
useDebounceFn,
|
||||
useTimeoutFn,
|
||||
useWindowSize,
|
||||
} from '@vueuse/core';
|
||||
|
||||
import { echarts } from './echarts';
|
||||
|
||||
type EchartsUIType = typeof EchartsUI | undefined;
|
||||
|
||||
type EchartsThemeType = 'dark' | 'light' | null;
|
||||
|
||||
function useEcharts(chartRef: Ref<EchartsUIType>) {
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let cacheOptions: EChartsOption = {};
|
||||
|
||||
const { isDark } = usePreferences();
|
||||
const { height, width } = useWindowSize();
|
||||
const resizeHandler: () => void = useDebounceFn(resize, 200);
|
||||
|
||||
const getOptions = computed((): EChartsOption => {
|
||||
if (!isDark.value) {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
...cacheOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const initCharts = (t?: EchartsThemeType) => {
|
||||
const el = chartRef?.value?.$el;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
chartInstance = echarts.init(el, t || isDark.value ? 'dark' : null);
|
||||
|
||||
return chartInstance;
|
||||
};
|
||||
|
||||
const renderEcharts = (options: EChartsOption, clear = true) => {
|
||||
cacheOptions = options;
|
||||
return new Promise((resolve) => {
|
||||
if (chartRef.value?.offsetHeight === 0) {
|
||||
useTimeoutFn(() => {
|
||||
renderEcharts(getOptions.value);
|
||||
resolve(null);
|
||||
}, 30);
|
||||
return;
|
||||
}
|
||||
nextTick(() => {
|
||||
useTimeoutFn(() => {
|
||||
if (!chartInstance) {
|
||||
const instance = initCharts();
|
||||
if (!instance) return;
|
||||
}
|
||||
clear && chartInstance?.clear();
|
||||
chartInstance?.setOption(getOptions.value);
|
||||
resolve(null);
|
||||
}, 30);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function resize() {
|
||||
chartInstance?.resize({
|
||||
animation: {
|
||||
duration: 300,
|
||||
easing: 'quadraticIn',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
watch([width, height], () => {
|
||||
resizeHandler?.();
|
||||
});
|
||||
|
||||
watch(isDark, () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
initCharts();
|
||||
renderEcharts(cacheOptions);
|
||||
}
|
||||
});
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
// 销毁实例,释放资源
|
||||
chartInstance?.dispose();
|
||||
});
|
||||
return {
|
||||
renderEcharts,
|
||||
};
|
||||
}
|
||||
|
||||
export { useEcharts };
|
||||
|
||||
export type { EchartsUIType };
|
|
@ -1,59 +1 @@
|
|||
import * as echarts from 'echarts/core';
|
||||
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
|
||||
// 数据集组件
|
||||
DatasetComponent,
|
||||
// 内置数据转换器组件 (filter, sort)
|
||||
TransformComponent,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
} from 'echarts/components';
|
||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import type {
|
||||
// 系列类型的定义后缀都为 SeriesOption
|
||||
BarSeriesOption,
|
||||
LineSeriesOption,
|
||||
} from 'echarts/charts';
|
||||
import type {
|
||||
// 组件类型的定义后缀都为 ComponentOption
|
||||
TitleComponentOption,
|
||||
TooltipComponentOption,
|
||||
GridComponentOption,
|
||||
DatasetComponentOption,
|
||||
} from 'echarts/components';
|
||||
import type { ComposeOption } from 'echarts/core';
|
||||
|
||||
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
|
||||
export type ECOption = ComposeOption<
|
||||
| BarSeriesOption
|
||||
| LineSeriesOption
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| DatasetComponentOption
|
||||
>;
|
||||
|
||||
// 注册必须的组件
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
PieChart,
|
||||
RadarChart,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
BarChart,
|
||||
LineChart,
|
||||
LabelLayout,
|
||||
UniversalTransition,
|
||||
CanvasRenderer,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
]);
|
||||
export const echartsInstance = echarts;
|
||||
export { default as chart } from './chart.vue';
|
||||
export * from './echarts';
|
||||
|
|
|
@ -46,9 +46,10 @@
|
|||
"@vben-core/stores": "workspace:*",
|
||||
"@vben-core/tabs-ui": "workspace:*",
|
||||
"@vben-core/toolkit": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/widgets": "workspace:*",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -10,8 +10,8 @@ import { useContentSpinner } from './use-content-spinner';
|
|||
|
||||
defineOptions({ name: 'LayoutContent' });
|
||||
|
||||
const { keepAlive } = usePreferences();
|
||||
const tabsStore = useTabsStore();
|
||||
const { keepAlive } = usePreferences();
|
||||
const { spinning } = useContentSpinner();
|
||||
|
||||
const { getCacheTabs, getExcludeTabs, renderRouteView } =
|
||||
|
|
|
@ -64,6 +64,7 @@ const breadcrumbs = computed((): IBreadcrumb[] => {
|
|||
if (props.hideWhenOnlyOne && resultBreadcrumb.length === 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return resultBreadcrumb;
|
||||
});
|
||||
|
||||
|
|
|
@ -46,9 +46,10 @@
|
|||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben/chart-ui": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vueuse/integrations": "^10.11.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -13,9 +13,9 @@ defineOptions({
|
|||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
description:
|
||||
'是一个基于Vue3.0、Vite 、TypeScript 等前沿技术的后台解决方案,目标是为服务中大型项目开发,提供现成的开箱解决方案及丰富的示例。',
|
||||
'是一个现代化开箱即用的中后台解决方案,采用最新的技术栈,包括 Vue 3.0、Vite、TailwindCSS 和 TypeScript 等前沿技术,代码规范严谨,提供丰富的配置选项,旨在为中大型项目的开发提供现成的开箱即用解决方案及丰富的示例,同时,它也是学习和深入前端技术的一个极佳示例。',
|
||||
name: 'Vben Admin Pro',
|
||||
title: '关于我们',
|
||||
title: '关于项目',
|
||||
});
|
||||
|
||||
const {
|
||||
|
@ -29,7 +29,9 @@ const {
|
|||
license,
|
||||
repositoryUrl,
|
||||
version,
|
||||
} = window.__VBEN_ADMIN_METADATA__ || {};
|
||||
// vite inject-metadata 插件注入的全局变量
|
||||
// eslint-disable-next-line no-undef
|
||||
} = __VBEN_ADMIN_METADATA__ || {};
|
||||
|
||||
const vbenDescriptionItems: DescriptionItem[] = [
|
||||
{
|
||||
|
@ -105,7 +107,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
|||
|
||||
<template>
|
||||
<div class="m-5">
|
||||
<div class="bg-card rounded-md p-5">
|
||||
<div class="bg-card border-border rounded-md border p-5 shadow">
|
||||
<div>
|
||||
<h3 class="text-foreground text-2xl font-semibold leading-7">
|
||||
{{ title }}
|
||||
|
@ -133,7 +135,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card mt-6 rounded-md p-5">
|
||||
<div class="bg-card border-border mt-6 rounded-md border p-5">
|
||||
<div>
|
||||
<h5 class="text-foreground text-lg">生产环境依赖</h5>
|
||||
</div>
|
||||
|
@ -152,8 +154,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
|||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card mt-6 rounded-md p-5">
|
||||
<div class="bg-card border-border mt-6 rounded-md border p-5">
|
||||
<div>
|
||||
<h5 class="text-foreground text-lg">开发环境依赖</h5>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisChartCard',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<slot></slot>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
import type { TabsItem } from '@vben/types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
tabs: TabsItem[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisChartsTabs',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
const defaultValue = computed(() => {
|
||||
return props.tabs?.[0].value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-card border-border w-full rounded-xl border px-4 pb-5 pt-3 shadow"
|
||||
>
|
||||
<Tabs :default-value="defaultValue">
|
||||
<TabsList>
|
||||
<template v-for="tab in tabs" :key="tab.label">
|
||||
<TabsTrigger :value="tab.value"> {{ tab.label }} </TabsTrigger>
|
||||
</template>
|
||||
</TabsList>
|
||||
<template v-for="tab in tabs" :key="tab.label">
|
||||
<TabsContent :value="tab.value" class="pt-4">
|
||||
<slot :name="tab.value"></slot>
|
||||
</TabsContent>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenCountToAnimator,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: AnalysisOverviewItem[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="md:flex">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<Card
|
||||
:class="{ 'md:mr-4': index + 1 < 4 }"
|
||||
:title="item.title"
|
||||
class="mt-5 w-full md:mt-0 md:w-1/4"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">{{ item.title }}</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="flex items-center justify-between">
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.value"
|
||||
:start-val="1"
|
||||
class="text-xl"
|
||||
prefix=""
|
||||
/>
|
||||
<VbenIcon :icon="item.icon" class="size-8 flex-shrink-0" />
|
||||
</CardContent>
|
||||
<CardFooter class="justify-between">
|
||||
<span>{{ item.totalTitle }}</span>
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.totalValue"
|
||||
:start-val="1"
|
||||
prefix=""
|
||||
/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,3 @@
|
|||
export { default as AnalysisChartCard } from './analysis-chart-card.vue';
|
||||
export { default as AnalysisChartsTabs } from './analysis-charts-tabs.vue';
|
||||
export { default as AnalysisOverview } from './analysis-overview.vue';
|
|
@ -1,45 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { VbenIcon, Badge } from '@vben-core/shadcn-ui';
|
||||
defineOptions({ name: 'DashboardCard' });
|
||||
import type { CardItem } from './typings';
|
||||
interface Props {
|
||||
item: CardItem;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<div class="flex justify-between p-2">
|
||||
<div class="">
|
||||
<slot name="title">{{ item.title }}</slot>
|
||||
</div>
|
||||
<div class="text-xs" :class="`bg-${item.color}-500`">
|
||||
<slot name="extra"
|
||||
><Badge>{{ item.extra }}</Badge></slot
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 mr-2">
|
||||
<div class="m-2 flex justify-between">
|
||||
<div class="text-4xl">
|
||||
<slot name="leftContent">{{ item.leftContent }}</slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="rightContent"
|
||||
><VbenIcon :icon="item.rightContent" class="size-10"
|
||||
/></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 flex justify-between">
|
||||
<div>
|
||||
<slot name="leftFooter">{{ item.leftFooter }}</slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="rightFooter">{{ item.rightFooter }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,24 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { chart } from '@vben/chart-ui';
|
||||
defineOptions({ name: 'ChartCard' });
|
||||
import type { ChartItem } from './typings';
|
||||
import { onMounted, ref } from 'vue';
|
||||
interface Props {
|
||||
item: ChartItem;
|
||||
}
|
||||
const chartRef = ref();
|
||||
onMounted(() => {
|
||||
chartRef.value.setChart(props.item.option);
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<div class="">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<chart ref="chartRef" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,41 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { Tabs, TabsList, TabsTrigger } from '@vben-core/shadcn-ui';
|
||||
import { chart } from '@vben/chart-ui';
|
||||
defineOptions({ name: 'ChartTab' });
|
||||
import type { ChartItem } from './typings';
|
||||
import { onMounted, ref } from 'vue';
|
||||
interface Props {
|
||||
items: ChartItem[];
|
||||
}
|
||||
const chartRef = ref();
|
||||
onMounted(() => {
|
||||
change(0);
|
||||
});
|
||||
const change = (i) => {
|
||||
const item = props.items[i];
|
||||
if (!item) return;
|
||||
item.option && chartRef.value.setChart(item.option);
|
||||
};
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<Tabs
|
||||
:defaultValue="items[0].name"
|
||||
className="w-[400px]"
|
||||
@update:modelValue="change"
|
||||
>
|
||||
<TabsList className="flex w-full ">
|
||||
<TabsTrigger
|
||||
:value="index"
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
>{{ item.title }}</TabsTrigger
|
||||
>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<chart ref="chartRef" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,156 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import type { CardItem, ChartItem } from './typings';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import Card from './card.vue';
|
||||
import ChartCard from './chartCard.vue';
|
||||
import ChartTab from './chartTab.vue';
|
||||
|
||||
interface Props {
|
||||
cardList: CardItem[];
|
||||
chartTabs: ChartItem[];
|
||||
}
|
||||
|
||||
defineOptions({ name: 'Dashboard' });
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
cardList: () => [],
|
||||
chartTabs: () => [],
|
||||
});
|
||||
|
||||
const itemA = ref({
|
||||
option: {
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ name: 'rose 1', value: 40 },
|
||||
{ name: 'rose 2', value: 38 },
|
||||
{ name: 'rose 3', value: 32 },
|
||||
{ name: 'rose 4', value: 30 },
|
||||
{ name: 'rose 5', value: 28 },
|
||||
{ name: 'rose 6', value: 26 },
|
||||
{ name: 'rose 7', value: 22 },
|
||||
{ name: 'rose 8', value: 18 },
|
||||
],
|
||||
itemStyle: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
name: 'Nightingale Chart',
|
||||
radius: [50, 200],
|
||||
roseType: 'area',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataView: { readOnly: false, show: true },
|
||||
mark: { show: true },
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true },
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
title: '玫瑰图',
|
||||
});
|
||||
const itemB = ref({
|
||||
option: {
|
||||
legend: {
|
||||
data: ['Allocated Budget', 'Actual Spending'],
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ max: 6500, name: 'Sales' },
|
||||
{ max: 16_000, name: 'Administration' },
|
||||
{ max: 30_000, name: 'Information Technology' },
|
||||
{ max: 38_000, name: 'Customer Support' },
|
||||
{ max: 52_000, name: 'Development' },
|
||||
{ max: 25_000, name: 'Marketing' },
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
name: 'Allocated Budget',
|
||||
value: [4200, 3000, 20_000, 35_000, 50_000, 18_000],
|
||||
},
|
||||
{
|
||||
name: 'Actual Spending',
|
||||
value: [5000, 14_000, 28_000, 26_000, 42_000, 21_000],
|
||||
},
|
||||
],
|
||||
name: 'Budget vs spending',
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
},
|
||||
title: '雷达图',
|
||||
});
|
||||
const itemC = ref({
|
||||
option: {
|
||||
legend: {
|
||||
left: 'center',
|
||||
top: '5%',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
avoidLabelOverlap: false,
|
||||
data: [
|
||||
{ name: 'Search Engine', value: 1048 },
|
||||
{ name: 'Direct', value: 735 },
|
||||
{ name: 'Email', value: 580 },
|
||||
{ name: 'Union Ads', value: 484 },
|
||||
{ name: 'Video Ads', value: 300 },
|
||||
],
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
position: 'center',
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
name: 'Access From',
|
||||
radius: ['40%', '70%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
},
|
||||
title: '饼图',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid grid-cols-4 gap-4 p-2">
|
||||
<Card v-for="item in cardList" :key="item.title" :item="item" />
|
||||
</div>
|
||||
<div class="p-2"><ChartTab :items="chartTabs" /></div>
|
||||
<div class="grid grid-cols-3 gap-2 p-2">
|
||||
<ChartCard :item="itemA" />
|
||||
<ChartCard :item="itemB" />
|
||||
<ChartCard :item="itemC" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,3 +1,3 @@
|
|||
export { default as DashboardLayout } from './layout.vue';
|
||||
export { default as Dashboard } from './dashboard.vue';
|
||||
export { default as chartCard } from './chartCard.vue';
|
||||
export * from './analysis';
|
||||
export type * from './typing';
|
||||
export * from './workbench';
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'DashboardLayout' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>dashboardLayout</div>
|
||||
</template>
|
|
@ -0,0 +1,29 @@
|
|||
import type { Component } from 'vue';
|
||||
|
||||
interface AnalysisOverviewItem {
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
totalTitle: string;
|
||||
totalValue: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface WorkbenchProjectItem {
|
||||
color?: string;
|
||||
content: string;
|
||||
date: string;
|
||||
group: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
}
|
||||
interface WorkbenchQuickNavItem {
|
||||
color?: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type {
|
||||
AnalysisOverviewItem,
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickNavItem,
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
interface CardItem {
|
||||
title: string;
|
||||
extra: string;
|
||||
leftContent: string;
|
||||
rightContent: string;
|
||||
color?: string;
|
||||
leftFooter: string;
|
||||
rightFooter: string;
|
||||
}
|
||||
|
||||
interface ChartItem {
|
||||
name: string;
|
||||
title: string;
|
||||
options: any;
|
||||
}
|
||||
|
||||
export type { CardItem, ChartItem };
|
|
@ -0,0 +1,3 @@
|
|||
export { default as WorkbenchHeader } from './workbench-header.vue';
|
||||
export { default as WorkbenchProject } from './workbench-project.vue';
|
||||
export { default as WorkbenchQuickNav } from './workbench-quick-nav.vue';
|
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts" setup>
|
||||
import { VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchHeader',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
avatar: '',
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-card border-border rounded-xl p-4 py-6 shadow lg:flex">
|
||||
<VbenAvatar :src="avatar" class="size-20" />
|
||||
<div
|
||||
v-if="$slots.title || $slots.description"
|
||||
class="flex flex-col justify-center md:ml-6 md:mt-0"
|
||||
>
|
||||
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl">
|
||||
<slot name="title"></slot>
|
||||
</h1>
|
||||
<span v-if="$slots.description" class="text-foreground/80 mt-1">
|
||||
<slot name="description"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-1 justify-end md:mt-0">
|
||||
<div class="flex flex-col justify-center text-right">
|
||||
<span class="text-foreground/80"> 待办 </span>
|
||||
<span class="text-2xl">2/10</span>
|
||||
</div>
|
||||
|
||||
<div class="mx-12 flex flex-col justify-center text-right md:mx-16">
|
||||
<span class="text-foreground/80"> 项目 </span>
|
||||
<span class="text-2xl">8</span>
|
||||
</div>
|
||||
<div class="mr-4 flex flex-col justify-center text-right md:mr-10">
|
||||
<span class="text-foreground/80"> 团队 </span>
|
||||
<span class="text-2xl">300</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import type { WorkbenchProjectItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: WorkbenchProjectItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchProject',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader class="py-4">
|
||||
<CardTitle class="text-lg">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap p-0">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<div
|
||||
:class="{
|
||||
'border-r-0': index % 3 === 2,
|
||||
'border-b-0': index < 3,
|
||||
'pb-4': index > 2,
|
||||
}"
|
||||
class="border-border w-1/3 border-b border-r border-t p-4 transition-all hover:shadow-xl"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<VbenIcon :color="item.color" :icon="item.icon" class="size-8" />
|
||||
<span class="ml-4 text-lg font-medium">{{ item.title }}</span>
|
||||
</div>
|
||||
<div class="text-foreground/80 mt-4 flex h-10">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<div class="text-foreground/80 flex justify-between">
|
||||
<span>{{ item.group }}</span>
|
||||
<span>{{ item.date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import type { WorkbenchQuickNavItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: WorkbenchQuickNavItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchQuickNav',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader class="py-4">
|
||||
<CardTitle class="text-lg">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap p-0">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<div
|
||||
:class="{
|
||||
'border-r-0': index % 3 === 2,
|
||||
'pb-4': index > 2,
|
||||
'border-b-0': index < 3,
|
||||
}"
|
||||
class="flex-col-center border-border w-1/3 border-b border-r border-t py-5 transition-all hover:shadow-xl"
|
||||
>
|
||||
<VbenIcon :color="item.color" :icon="item.icon" class="size-5" />
|
||||
<span class="text-md mt-2 truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"compilerOptions": {
|
||||
"types": ["@vben/types/window"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"@vueuse/core": "^10.11.0",
|
||||
"@vueuse/integrations": "^10.11.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -56,9 +56,9 @@ onUnmounted(() => {
|
|||
|
||||
<style>
|
||||
.coze-assistant {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
bottom: 60px;
|
||||
z-index: 1000;
|
||||
|
||||
img {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useRouter } from 'vue-router';
|
|||
import { $t } from '@vben/locales';
|
||||
import { IcRoundClose, IcRoundSearchOff } from '@vben-core/iconify';
|
||||
import { VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
import { mapTree, traverseTreeValues } from '@vben-core/toolkit';
|
||||
import { mapTree, traverseTreeValues, uniqueByField } from '@vben-core/toolkit';
|
||||
|
||||
import { onKeyStroke, useLocalStorage, useThrottleFn } from '@vueuse/core';
|
||||
|
||||
|
@ -247,16 +247,17 @@ onMounted(() => {
|
|||
{{ $t('search.recent') }}
|
||||
</li>
|
||||
<li
|
||||
v-for="(item, index) in searchResults"
|
||||
v-for="(item, index) in uniqueByField(searchResults, 'path')"
|
||||
:key="item.path"
|
||||
:class="
|
||||
activeIndex === index
|
||||
? 'active bg-primary text-primary-foreground text-muted-foreground'
|
||||
? 'active bg-primary text-primary-foreground'
|
||||
: ''
|
||||
"
|
||||
:data-index="index"
|
||||
:data-search-item="index"
|
||||
class="bg-accent flex-center group mb-3 w-full cursor-pointer rounded-lg px-4 py-4"
|
||||
@click="handleEnter"
|
||||
@mouseenter="handleMouseenter"
|
||||
>
|
||||
<VbenIcon
|
||||
|
|
|
@ -22,7 +22,7 @@ const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
|
|||
|
||||
const typeItems: SelectListItem[] = [
|
||||
{ label: $t('preferences.normal'), value: 'normal' },
|
||||
{ label: $t('preferences.breadcrumb-background'), value: 'background' },
|
||||
{ label: $t('preferences.breadcrumb.background'), value: 'background' },
|
||||
];
|
||||
|
||||
const disableItem = computed(() => {
|
||||
|
@ -32,22 +32,22 @@ const disableItem = computed(() => {
|
|||
|
||||
<template>
|
||||
<SwitchItem v-model="breadcrumbEnable" :disabled="disabled">
|
||||
{{ $t('preferences.breadcrumb-enable') }}
|
||||
{{ $t('preferences.breadcrumb.enable') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="breadcrumbHideOnlyOne" :disabled="disableItem">
|
||||
{{ $t('preferences.breadcrumb-hide-only-one') }}
|
||||
{{ $t('preferences.breadcrumb.hide-only-one') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="breadcrumbShowHome" :disabled="disableItem">
|
||||
{{ $t('preferences.breadcrumb-home') }}
|
||||
{{ $t('preferences.breadcrumb.home') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="breadcrumbShowIcon" :disabled="disableItem">
|
||||
{{ $t('preferences.breadcrumb-icon') }}
|
||||
{{ $t('preferences.breadcrumb.icon') }}
|
||||
</SwitchItem>
|
||||
<ToggleItem
|
||||
v-model="breadcrumbStyleType"
|
||||
:disabled="disableItem"
|
||||
:items="typeItems"
|
||||
>
|
||||
{{ $t('preferences.breadcrumb-style') }}
|
||||
{{ $t('preferences.breadcrumb.style') }}
|
||||
</ToggleItem>
|
||||
</template>
|
||||
|
|
|
@ -180,7 +180,7 @@ function handleReset() {
|
|||
<VbenSheet
|
||||
v-model:open="openPreferences"
|
||||
:description="$t('preferences.subtitle')"
|
||||
:title="$t('preferences.name')"
|
||||
:title="$t('preferences.title')"
|
||||
>
|
||||
<template #trigger>
|
||||
<Trigger />
|
||||
|
@ -210,7 +210,7 @@ function handleReset() {
|
|||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('preferences.animation.name')">
|
||||
<Block :title="$t('preferences.animation.title')">
|
||||
<Animation
|
||||
v-model:transition-enable="transitionEnable"
|
||||
v-model:transition-loading="transitionLoading"
|
||||
|
@ -220,7 +220,7 @@ function handleReset() {
|
|||
</Block>
|
||||
</template>
|
||||
<template #appearance>
|
||||
<Block :title="$t('preferences.theme.name')">
|
||||
<Block :title="$t('preferences.theme.title')">
|
||||
<Theme
|
||||
v-model="themeMode"
|
||||
v-model:app-semi-dark-menu="appSemiDarkMenu"
|
||||
|
@ -266,7 +266,7 @@ function handleReset() {
|
|||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('preferences.header.name')">
|
||||
<Block :title="$t('preferences.header.title')">
|
||||
<Header
|
||||
v-model:headerEnable="headerEnable"
|
||||
v-model:headerMode="headerMode"
|
||||
|
@ -284,7 +284,7 @@ function handleReset() {
|
|||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('preferences.breadcrumb')">
|
||||
<Block :title="$t('preferences.breadcrumb.title')">
|
||||
<Breadcrumb
|
||||
v-model:breadcrumb-enable="breadcrumbEnable"
|
||||
v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
|
||||
|
@ -303,7 +303,7 @@ function handleReset() {
|
|||
v-model:tabbar-show-icon="tabbarShowIcon"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.footer.name')">
|
||||
<Block :title="$t('preferences.footer.title')">
|
||||
<Footer
|
||||
v-model:footer-enable="footerEnable"
|
||||
v-model:footer-fixed="footerFixed"
|
||||
|
|
|
@ -11,7 +11,7 @@ defineOptions({
|
|||
|
||||
<template>
|
||||
<VbenButton
|
||||
:title="$t('preferences.name')"
|
||||
:title="$t('preferences.title')"
|
||||
class="bg-primary flex-col-center h-12 w-12 cursor-pointer rounded-l-lg rounded-r-none border-none"
|
||||
>
|
||||
<IconSetting
|
||||
|
|
|
@ -175,7 +175,7 @@ if (enableShortcutKey.value) {
|
|||
@click="handleOpenPreference"
|
||||
>
|
||||
<IcRoundSettingsSuggest class="mr-2 size-5" />
|
||||
{{ $t('preferences.name') }}
|
||||
{{ $t('preferences.title') }}
|
||||
<DropdownMenuShortcut v-if="enablePreferencesShortcutKey">
|
||||
{{ altView }} ,
|
||||
</DropdownMenuShortcut>
|
||||
|
|
|
@ -6,6 +6,6 @@ const LOGIN_PATH = '/auth/login';
|
|||
/**
|
||||
* @zh_CN 默认首页地址
|
||||
*/
|
||||
const DEFAULT_HOME_PATH = '/welcome';
|
||||
const DEFAULT_HOME_PATH = '/analytics';
|
||||
|
||||
export { DEFAULT_HOME_PATH, LOGIN_PATH };
|
||||
|
|
|
@ -38,6 +38,6 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 419.23 419.23"><defs><style>.svg-bell-cls-1{fill:#fbc907;}.svg-bell-cls-2{fill:#f3a70f;}.svg-bell-cls-3{fill:#426572;}.svg-bell-cls-4,.svg-bell-cls-9{fill:#fff;}.svg-bell-cls-5{fill:#e8e8e8;}.svg-bell-cls-6{fill:#dadada;}.svg-bell-cls-7{opacity:0.1;}.svg-bell-cls-8{fill:#55e0ff;}.svg-bell-cls-9{opacity:0.4;}</style></defs><title>Asset 510</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><circle class="svg-bell-cls-1" cx="210.66" cy="209.62" r="203.61"/><path class="svg-bell-cls-2" d="M27.21,209.62A203.61,203.61,0,0,1,220.72,6.26q-5-.25-10.08-.25C98.19,4.86,6.11,95.09,5,207.54S94.05,412.07,206.5,413.21q2.07,0,4.13,0,5.06,0,10.08-.25A203.61,203.61,0,0,1,27.21,209.62Z"/><path class="svg-bell-cls-3" d="M209.61,419.23C94,419.23,0,325.19,0,209.61S94,0,209.61,0,419.23,94,419.23,209.61,325.19,419.23,209.61,419.23Zm0-407.23C100.65,12,12,100.65,12,209.61s88.65,197.61,197.61,197.61,197.61-88.65,197.61-197.61S318.58,12,209.61,12Z"/><path class="svg-bell-cls-4" d="M111.69,60.1a195,195,0,0,1,41.08-21.2c3.59-1.34,2-7.14-1.6-5.79a201.47,201.47,0,0,0-42.51,21.8c-3.18,2.15-.18,7.35,3,5.18Z"/><path class="svg-bell-cls-4" d="M35.09,160.61c3.09-10.2,8-20,13.05-29.32A212.37,212.37,0,0,1,95.87,72.18c2.93-2.52-1.33-6.75-4.24-4.24A217.08,217.08,0,0,0,43,128.26C37.63,138,32.54,148.34,29.31,159c-1.12,3.7,4.67,5.29,5.79,1.6Z"/><circle class="svg-bell-cls-5" cx="211.45" cy="212.12" r="156.89"/><path class="svg-bell-cls-6" d="M67.05,232.07a156.89,156.89,0,0,1,283.33-92.82A156.91,156.91,0,1,0,85,304.92,156.19,156.19,0,0,1,67.05,232.07Z"/><path class="svg-bell-cls-5" d="M211.32,152.25h0a9.16,9.16,0,0,1,9.16,9.16V210.5a9.16,9.16,0,0,1-9.16,9.16h0a9.16,9.16,0,0,1-9.16-9.16V161.41A9.16,9.16,0,0,1,211.32,152.25Z"/><circle class="svg-bell-cls-5" cx="211.14" cy="221.32" r="15.94"/><path class="svg-bell-cls-3" d="M210.48,92.62c6.29,0,6.29-9.77,0-9.77S204.19,92.62,210.48,92.62Z"/><path class="svg-bell-cls-3" d="M210.48,343.89c6.29,0,6.29-9.77,0-9.77S204.19,343.89,210.48,343.89Z"/><path class="svg-bell-cls-3" d="M339.84,218.25c6.29,0,6.29-9.77,0-9.77S333.55,218.25,339.84,218.25Z"/><path class="svg-bell-cls-3" d="M81.13,218.25c6.29,0,6.29-9.77,0-9.77S74.84,218.25,81.13,218.25Z"/><path class="svg-bell-cls-3" d="M205.56,153.32h0a9.16,9.16,0,0,1,9.16,9.16v49.09a9.16,9.16,0,0,1-9.16,9.16h0a9.16,9.16,0,0,1-9.16-9.16V162.49A9.16,9.16,0,0,1,205.56,153.32Z"/><circle class="cls-3" cx="205.38" cy="221.15" r="15.94"/><path class="cls-3" d="M135.78,272.58l135.16-89.89L290.11,170c5.22-3.46.33-11.94-4.92-8.44L150,251.4l-19.17,12.74C125.64,267.6,130.52,276.08,135.78,272.58Z"/><g class="svg-bell-cls-7"><ellipse class="svg-bell-cls-8" cx="210.2" cy="211.21" rx="156.89" ry="154.23"/></g><path class="svg-bell-cls-9" d="M243.13,60.17,84.37,301.88a162.18,162.18,0,0,1-18.58-47.29L193.5,60.21a153.88,153.88,0,0,1,49.67,0Z"/><path class="svg-bell-cls-9" d="M289.69,72.6,115.93,325.78a155.09,155.09,0,0,1-14.77-15L270,64.76A155.38,155.38,0,0,1,289.69,72.6Z"/><path class="svg-bell-cls-9" d="M362.16,171.75h0L232.51,360.68h0a160.93,160.93,0,0,1-42.54.43L346.63,132.84A151.63,151.63,0,0,1,362.16,171.75Z"/><path class="cls-3" d="M210.12,369.75c-89.82,0-162.89-71.88-162.89-160.23S120.31,49.29,210.12,49.29,373,121.17,373,209.52,299.94,369.75,210.12,369.75Zm0-308.46c-83.2,0-150.89,66.5-150.89,148.23s67.69,148.23,150.89,148.23S361,291.25,361,209.52,293.32,61.29,210.12,61.29Z"/></g></g></svg>
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 392.49 390.69"><defs><style>.svg-cake-cls-1{fill:#fff;}.svg-cake-cls-2{fill:#f3aa9f;}.svg-cake-cls-3{fill:#e1978f;}.svg-cake-cls-4,.svg-cake-cls-6{fill:#426572;}.svg-cake-cls-5{fill:#e1d2d5;}.svg-cake-cls-6{font-size:100.43px;font-family:Dosis-ExtraBold, Dosis;font-weight:700;}</style></defs><title>Asset 480</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="svg-cake-cls-1" d="M383.9,162H199.69V2.19q4-.19,8.16-.19A176.87,176.87,0,0,1,383.9,162Z"/><path class="svg-cake-cls-2" d="M355.38,210a176.83,176.83,0,0,1-95.72,157.18l-.15.07A176.88,176.88,0,1,1,101.72,50.67l.15-.07a175.93,175.93,0,0,1,72.82-17.4V191H354.37A177.9,177.9,0,0,1,355.38,210Z"/><path class="svg-cake-cls-3" d="M357.53,212.16a176,176,0,0,1-17.44,76.66,1,1,0,0,1-.07.15A176.89,176.89,0,0,1,73.47,352.79l1.23.38q6,1.86,12.26,3.29A177,177,0,0,0,303.49,191h52.78A178.15,178.15,0,0,1,357.53,212.16Z"/><path class="svg-cake-cls-4" d="M182.85,390.69a182.87,182.87,0,0,1-84-345.31l.41-.2a180.59,180.59,0,0,1,75.13-20l6.27-.28V185H364.36l.51,5.44c.54,5.77.82,11.62.82,17.4a180.72,180.72,0,0,1-20.18,83.56c-.06.12-.12.26-.2.41a184.39,184.39,0,0,1-83,80.77l-.18.08,0,0A181.06,181.06,0,0,1,182.85,390.69ZM104.33,56.08A170.88,170.88,0,0,0,256.9,361.85l.17-.08,0,0a172.34,172.34,0,0,0,77.5-75.38l.15-.29a168.84,168.84,0,0,0,18.93-78.23c0-3.6-.11-7.23-.34-10.84H168.69V37.58a168.41,168.41,0,0,0-64.07,18.35Z"/><path class="svg-cake-cls-5" d="M382.9,158H309.11c-2.89-46.4-18.43-98.49-36.89-144.29l1.33.51a177.49,177.49,0,0,1,92.51,83.56A175.63,175.63,0,0,1,382.9,158Z"/><path class="svg-cake-cls-4" d="M392.49,172H195.69V.47L201.4.2C204.11.07,207,0,209.85,0a182.87,182.87,0,0,1,182,165.44Zm-184.8-12H379.18A170.89,170.89,0,0,0,209.85,12h-2.16Z"/><text class="svg-cake-cls-6" transform="translate(232.67 133.93)">%</text><path class="svg-cake-cls-1" d="M101.22,81.14a166.34,166.34,0,0,1,34.83-18c3.58-1.34,2-7.14-1.6-5.79A172.89,172.89,0,0,0,98.19,76c-3.18,2.15-.18,7.35,3,5.18Z"/><path class="svg-cake-cls-1" d="M36.28,166.34c2.62-8.63,6.74-16.94,11.05-24.83A180.58,180.58,0,0,1,87.86,91.34c2.93-2.52-1.33-6.75-4.24-4.24-23.3,20.06-44.07,47.84-53.12,77.65-1.12,3.7,4.67,5.29,5.79,1.6Z"/></g></g></svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 445 271.8"><defs><style>.svg-card-cls-1{fill:#32caf8;}.svg-card-cls-2{fill:#00aaf8;opacity:0.5;}.svg-card-cls-3{fill:#fff;}.svg-card-cls-4{fill:#426572;}</style></defs><g><g><rect class="svg-card-cls-1" x="6" y="8.17" width="433" height="259.8" rx="12" ry="12"/><path class="svg-card-cls-2" d="M439,21.16V255a13,13,0,0,1-13,13H28.72l381-259.8H426A13,13,0,0,1,439,21.16Z"/><path class="svg-card-cls-3" d="M328,33.24h88.92c3.86,0,3.87-6,0-6H328c-3.86,0-3.87,6,0,6Z"/><path class="svg-card-cls-3" d="M283.49,33.24H312.6c3.86,0,3.87-6,0-6H283.49c-3.86,0-3.87,6,0,6Z"/><path class="svg-card-cls-4" d="M427,271.8H18a18,18,0,0,1-18-18V18A18,18,0,0,1,18,0H427a18,18,0,0,1,18,18V253.8A18,18,0,0,1,427,271.8ZM18,12a6,6,0,0,0-6,6V253.8a6,6,0,0,0,6,6H427a6,6,0,0,0,6-6V18a6,6,0,0,0-6-6Z"/><rect class="svg-card-cls-4" x="37.89" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="55.93" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="73.97" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="92.01" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="118.71" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="136.76" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="154.8" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="172.84" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="199.54" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="217.58" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="235.63" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="253.67" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="280.37" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="298.41" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="316.45" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="334.49" y="125.08" width="12" height="20.57"/><rect class="svg-card-cls-4" x="43.89" y="177.53" width="161.29" height="12"/><rect class="svg-card-cls-4" x="43.89" y="204.59" width="68.2" height="12"/><circle class="svg-card-cls-3" cx="379.46" cy="207.35" r="23.82"/><rect class="svg-card-cls-3" x="43.89" y="36.31" width="72.53" height="47.63" rx="12" ry="12"/><path class="svg-card-cls-4" d="M104.42,88.86H55.89a18,18,0,0,1-18-18V47.23a18,18,0,0,1,18-18h48.53a18,18,0,0,1,18,18V70.86A18,18,0,0,1,104.42,88.86ZM55.89,41.23a6,6,0,0,0-6,6V70.86a6,6,0,0,0,6,6h48.53a6,6,0,0,0,6-6V47.23a6,6,0,0,0-6-6Z"/><path class="svg-card-cls-4" d="M379.46,241.49a29.81,29.81,0,1,1,29.82-29.82A29.85,29.85,0,0,1,379.46,241.49Zm0-47.63a17.81,17.81,0,1,0,17.82,17.81A17.84,17.84,0,0,0,379.46,193.86Z"/></g></g></svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356.99 419.8"><defs><style>.svg-download-cls-1{fill:#ffa546;}.svg-download-cls-2{fill:#ff6059;opacity:0.4;}.svg-download-cls-3{fill:#426572;}.cls-4{fill:#ffd947;}</style></defs><g><g><path class="svg-download-cls-1" d="M351,380.73v17.59a15.52,15.52,0,0,1-15.47,15.48H21.46A15.52,15.52,0,0,1,6,398.32V380.73a15.51,15.51,0,0,1,15.47-15.47H335.52A15.51,15.51,0,0,1,351,380.73Z"/><path class="svg-download-cls-2" d="M351,406.85c0,3.95-7,7.19-15.47,7.19H21.46C13,414,6,410.8,6,406.85V380.73a15.51,15.51,0,0,1,15.47-15.47H37.66l3.44,25.27c0,4,7,7.2,15.47,7.2l283.72,12.44,7.38-2.28Z"/><path class="svg-download-cls-3" d="M335.52,419.8H21.46A21.5,21.5,0,0,1,0,398.32V380.73a21.49,21.49,0,0,1,21.46-21.47H335.52A21.49,21.49,0,0,1,357,380.73v17.59a21.52,21.52,0,0,1-21.46,21.48ZM21.46,371.26A9.48,9.48,0,0,0,12,380.73v17.59a9.48,9.48,0,0,0,9.46,9.48H335.52a9.52,9.52,0,0,0,9.46-9.48V380.73a9.48,9.48,0,0,0-9.46-9.47Z"/><path class="svg-download-cls-1" d="M247.93,138H233.23V41.7A35.7,35.7,0,0,0,197.53,6H159.45a35.7,35.7,0,0,0-35.7,35.7V138H109.06C80,138,61.84,169.48,76.37,194.64l34.72,60.13,30,52c16.6,28.76,58.12,28.76,74.72,0l30-52,34.72-60.13C295.14,169.48,277,138,247.93,138Z"/><path class="svg-download-cls-2" d="M280.62,188l-34.73,60.13-30,52c-11.24,19.46-66.68,32.78-52.52,18.88,60.22-59.12,104.3-182.16,104.3-182.16A37.74,37.74,0,0,1,280.62,188Z"/><path class="cls-4" d="M192.3,6c-.22.23-.42.47-.63.72-38.92,45-18.36,116.49-42.85,170.71-10.14,22.45-29.18,41.51-52.15,49.48L78,194.64C63.52,169.48,81.67,138,110.72,138h14.7V41.7A35.7,35.7,0,0,1,161.12,6Z"/><path class="svg-download-cls-3" d="M178.49,334.39h0a48.64,48.64,0,0,1-42.56-24.57L71.17,197.64A43.75,43.75,0,0,1,109.06,132h8.69V41.7A41.74,41.74,0,0,1,159.45,0h38.09a41.75,41.75,0,0,1,41.7,41.7V132h8.69a43.75,43.75,0,0,1,37.89,65.62L221,309.82A48.64,48.64,0,0,1,178.49,334.39ZM109.06,144a31.75,31.75,0,0,0-27.49,47.62l64.76,112.17a37.14,37.14,0,0,0,64.33,0l64.76-112.17A31.75,31.75,0,0,0,247.92,144H227.23V41.7A29.73,29.73,0,0,0,197.53,12H159.45a29.73,29.73,0,0,0-29.7,29.7V144Z"/></g></g></svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -9,5 +9,15 @@ if (!loaded) {
|
|||
}
|
||||
|
||||
const SvgAvatarIcon = createIcon('svg:avatar');
|
||||
const SvgDownloadIcon = createIcon('svg:download');
|
||||
const SvgCardIcon = createIcon('svg:card');
|
||||
const SvgBellIcon = createIcon('svg:bell');
|
||||
const SvgCakeIcon = createIcon('svg:cake');
|
||||
|
||||
export { SvgAvatarIcon };
|
||||
export {
|
||||
SvgAvatarIcon,
|
||||
SvgBellIcon,
|
||||
SvgCakeIcon,
|
||||
SvgCardIcon,
|
||||
SvgDownloadIcon,
|
||||
};
|
||||
|
|
|
@ -37,14 +37,15 @@ async function loadSvgIcons() {
|
|||
|
||||
await Promise.all(
|
||||
Object.entries(svgEagers).map((svg) => {
|
||||
const [key, body] = svg as [string, string];
|
||||
const [key, body] = svg as [string, { default: string } | string];
|
||||
|
||||
// ./icons/xxxx.svg => xxxxxx
|
||||
const start = key.lastIndexOf('/') + 1;
|
||||
const end = key.lastIndexOf('.');
|
||||
const iconName = key.slice(start, end);
|
||||
return addIcon(`svg-icon:${iconName}`, {
|
||||
body,
|
||||
|
||||
return addIcon(`svg:${iconName}`, {
|
||||
body: typeof body === 'object' ? body.default : body,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"dependencies": {
|
||||
"@intlify/core-base": "^9.13.1",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-i18n": "^9.13.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,15 +98,19 @@ page:
|
|||
code-login: Code Login
|
||||
qrcode-login: Qrcode Login
|
||||
forget-password: Forget Password
|
||||
dashboard:
|
||||
title: Dashboard
|
||||
analytics: Analytics
|
||||
workspace: Workspace
|
||||
vben:
|
||||
about: About
|
||||
document: Document
|
||||
outside:
|
||||
page: External Page
|
||||
title: External Page
|
||||
embedded: embedded Page
|
||||
external-link: External Link
|
||||
nested:
|
||||
page: Nested Menu
|
||||
title: Nested Menu
|
||||
menu1: Menu 1
|
||||
menu2: Menu 2
|
||||
menu21: Menu 2-1
|
||||
|
@ -115,10 +119,10 @@ page:
|
|||
menu32: Menu 3-2
|
||||
menu321: Menu 3-2-1
|
||||
fallback:
|
||||
page: Exception Page
|
||||
title: Exception Page
|
||||
|
||||
preferences:
|
||||
name: Preferences
|
||||
title: Preferences
|
||||
subtitle: Customize Preferences & Preview in Real Time
|
||||
reset-tip: The data has changed, click to reset
|
||||
ai-assistant: Ai Assistant
|
||||
|
@ -147,7 +151,6 @@ preferences:
|
|||
full-content-tip: Display only the main content, no menus
|
||||
weak-mode: Color Weak Mode
|
||||
gray-mode: Gray Mode
|
||||
|
||||
language: Language
|
||||
dynamic-title: Dynamic Title
|
||||
normal: Normal
|
||||
|
@ -161,14 +164,6 @@ preferences:
|
|||
navigation-accordion: Sidebar Navigation Menu Accordion mode
|
||||
navigation-split-tip: When enabled, the sidebar shows the top bar's submenu
|
||||
interface-control: Interface Layout Control
|
||||
breadcrumb: Breadcrumb
|
||||
breadcrumb-home: Display the home button
|
||||
breadcrumb-enable: Enable Breadcrumb
|
||||
breadcrumb-icon: Display breadcrumb icon
|
||||
breadcrumb-background: background
|
||||
breadcrumb-style: Breadcrumb Type
|
||||
breadcrumb-hide-only-one: Hidden when only one left
|
||||
|
||||
copy: Copy Preferences
|
||||
copy-success: Copy successful. Please replace in `src/preferences.ts` of the app
|
||||
reset-success: Preferences reset successfully
|
||||
|
@ -179,13 +174,21 @@ preferences:
|
|||
tabs-icon: Display Tabbar Icon
|
||||
mode: Mode
|
||||
logo-visible: Display Logo
|
||||
breadcrumb:
|
||||
title: Breadcrumb
|
||||
home: Display the home button
|
||||
enable: Enable Breadcrumb
|
||||
icon: Display breadcrumb icon
|
||||
background: background
|
||||
style: Breadcrumb Type
|
||||
hide-only-one: Hidden when only one left
|
||||
animation:
|
||||
name: Animation
|
||||
title: Animation
|
||||
loading: Page transition loading
|
||||
transition: Page transition animation
|
||||
progress: Page transition progress
|
||||
theme:
|
||||
name: Theme
|
||||
title: Theme
|
||||
builtin: Built-in
|
||||
radius: Radius
|
||||
default: Default
|
||||
|
@ -204,14 +207,14 @@ preferences:
|
|||
gray: Gray
|
||||
custom: Custom
|
||||
header:
|
||||
name: Header
|
||||
title: Header
|
||||
visible: Display Header
|
||||
mode-static: Static
|
||||
mode-fixed: Fixed
|
||||
mode-auto: Auto hide/display
|
||||
mode-auto-scroll: Scroll hide/display
|
||||
footer:
|
||||
name: Footer
|
||||
title: Footer
|
||||
visible: Fixed at the bottom
|
||||
fixed: Display Footer
|
||||
shortcut-keys:
|
||||
|
|
|
@ -97,15 +97,19 @@ page:
|
|||
code-login: 验证码登陆
|
||||
qrcode-login: 二维码登陆
|
||||
forget-password: 忘记密码
|
||||
dashboard:
|
||||
title: 概览
|
||||
analytics: 分析页
|
||||
workspace: 工作台
|
||||
vben:
|
||||
about: 关于
|
||||
document: 文档
|
||||
outside:
|
||||
page: 外部页面
|
||||
title: 外部页面
|
||||
embedded: 内嵌
|
||||
external-link: 外链
|
||||
nested:
|
||||
page: 嵌套菜单
|
||||
title: 嵌套菜单
|
||||
menu1: 菜单 1
|
||||
menu2: 菜单 2
|
||||
menu21: 菜单 2-1
|
||||
|
@ -114,10 +118,10 @@ page:
|
|||
menu32: 菜单 3-2
|
||||
menu321: 菜单 3-2-1
|
||||
fallback:
|
||||
page: 异常页面
|
||||
title: 异常页面
|
||||
|
||||
preferences:
|
||||
name: 偏好设置
|
||||
title: 偏好设置
|
||||
subtitle: 自定义偏好设置 & 实时预览
|
||||
reset-tip: 数据有变化,点击可进行重置
|
||||
appearance: 外观
|
||||
|
@ -150,7 +154,6 @@ preferences:
|
|||
follow-system: 跟随系统
|
||||
weak-mode: 色弱模式
|
||||
gray-mode: 灰色模式
|
||||
|
||||
navigation-menu: 导航菜单
|
||||
navigation-style: 导航菜单风格
|
||||
navigation-accordion: 侧边导航菜单手风琴模式
|
||||
|
@ -160,13 +163,6 @@ preferences:
|
|||
normal: 默认
|
||||
plain: 朴素
|
||||
rounded: 圆润
|
||||
breadcrumb: 面包屑导航
|
||||
breadcrumb-enable: 开启面包屑导航
|
||||
breadcrumb-icon: 显示面包屑图标
|
||||
breadcrumb-home: 显示首页按钮
|
||||
breadcrumb-style: 面包屑风格
|
||||
breadcrumb-hide-only-one: 只有一个时隐藏
|
||||
breadcrumb-background: 背景
|
||||
copy: 复制偏好设置
|
||||
copy-success: 拷贝成功,请在 app 下的 `src/preferences.ts`内进行覆盖
|
||||
reset-success: 重置偏好设置成功
|
||||
|
@ -177,13 +173,21 @@ preferences:
|
|||
tabs-icon: 显示标签栏图标
|
||||
mode: 模式
|
||||
logo-visible: 显示 Logo
|
||||
breadcrumb:
|
||||
title: 面包屑导航
|
||||
enable: 开启面包屑导航
|
||||
icon: 显示面包屑图标
|
||||
home: 显示首页按钮
|
||||
style: 面包屑风格
|
||||
hide-only-one: 只有一个时隐藏
|
||||
background: 背景
|
||||
animation:
|
||||
name: 动画
|
||||
title: 动画
|
||||
loading: 页面切换 Loading
|
||||
transition: 页面切换动画
|
||||
progress: 页面切换进度条
|
||||
theme:
|
||||
name: 主题
|
||||
title: 主题
|
||||
builtin: 内置主题
|
||||
radius: 圆角
|
||||
default: 默认
|
||||
|
@ -202,14 +206,14 @@ preferences:
|
|||
gray: 中灰色
|
||||
custom: 自定义
|
||||
header:
|
||||
name: 顶栏
|
||||
title: 顶栏
|
||||
mode-static: 静止
|
||||
mode-fixed: 固定
|
||||
mode-auto: 自动隐藏和显示
|
||||
mode-auto-scroll: 滚动隐藏和显示
|
||||
visible: 显示顶栏
|
||||
footer:
|
||||
name: 底栏
|
||||
title: 底栏
|
||||
visible: 显示底栏
|
||||
fixed: 固定在底部
|
||||
shortcut-keys:
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,9 @@ interface SelectListItem {
|
|||
value: string;
|
||||
}
|
||||
|
||||
export type { SelectListItem };
|
||||
interface TabsItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type { SelectListItem, TabsItem };
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__VBEN_ADMIN_METADATA__: {
|
||||
authorEmail: string;
|
||||
authorName: string;
|
||||
authorUrl: string;
|
||||
buildTime: string;
|
||||
dependencies: Record<string, string>;
|
||||
description: string;
|
||||
devDependencies: Record<string, string>;
|
||||
homepage: string;
|
||||
license: string;
|
||||
repositoryUrl: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
// interface Window {
|
||||
const __VBEN_ADMIN_METADATA__: {
|
||||
authorEmail: string;
|
||||
authorName: string;
|
||||
authorUrl: string;
|
||||
buildTime: string;
|
||||
dependencies: Record<string, string>;
|
||||
description: string;
|
||||
devDependencies: Record<string, string>;
|
||||
homepage: string;
|
||||
license: string;
|
||||
repositoryUrl: string;
|
||||
version: string;
|
||||
};
|
||||
// }
|
||||
}
|
||||
|
|
585
pnpm-lock.yaml
585
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -9,6 +9,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.2.3",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue