初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,40 @@
<template>
<div class="md:flex">
<template v-for="(item, index) in growCardList" :key="item.title">
<Card
size="small"
:loading="loading"
:title="item.title"
class="md:w-1/4 w-full !md:mt-0"
:class="{ '!md:mr-4': index + 1 < 4, '!mt-4': index > 0 }"
>
<template #extra>
<Tag :color="item.color">{{ item.action }}</Tag>
</template>
<div class="py-4 px-4 flex justify-between items-center">
<CountTo prefix="$" :startVal="1" :endVal="item.value" class="text-2xl" />
<Icon :icon="item.icon" :size="40" />
</div>
<div class="p-2 px-4 flex justify-between">
<span>{{ t('总') }}{{ item.title }}</span>
<CountTo prefix="$" :startVal="1" :endVal="item.total" />
</div>
</Card>
</template>
</div>
</template>
<script lang="ts" setup>
import { CountTo } from '/@/components/CountTo/index';
import { Icon } from '/@/components/Icon';
import { Tag, Card } from 'ant-design-vue';
import { growCardList } from '../data';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
defineProps({
loading: {
type: Boolean,
},
});
</script>

View File

@ -0,0 +1,65 @@
<template>
<Card :title="t('成交占比')" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
series: [
{
name: t('访问来源'),
type: 'pie',
radius: '80%',
center: ['50%', '50%'],
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
data: [
{ value: 500, name: t('电子产品') },
{ value: 310, name: t('服装') },
{ value: 274, name: t('化妆品') },
{ value: 400, name: t('家居') },
].sort(function (a, b) {
return a.value - b.value;
}),
roseType: 'radius',
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 400;
},
},
],
});
},
{ immediate: true },
);
</script>

View File

@ -0,0 +1,39 @@
<template>
<Card
:tab-list="tabListTitle"
v-bind="$attrs"
:active-tab-key="activeKey"
@tab-change="onTabChange"
>
<p v-if="activeKey === 'tab1'">
<VisitAnalysis />
</p>
<p v-if="activeKey === 'tab2'">
<VisitAnalysisBar />
</p>
</Card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Card } from 'ant-design-vue';
import VisitAnalysis from './VisitAnalysis.vue';
import VisitAnalysisBar from './VisitAnalysisBar.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const activeKey = ref('tab1');
const tabListTitle = [
{
key: 'tab1',
tab: t('流量趋势'),
},
{
key: 'tab2',
tab: t('访问量'),
},
];
function onTabChange(key) {
activeKey.value = key;
}
</script>

View File

@ -0,0 +1,89 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts">
import { basicProps } from './props';
</script>
<script lang="ts" setup>
import { onMounted, ref, Ref } from 'vue';
import { useECharts } from '/@/hooks/web/useECharts';
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [...new Array(18)].map((_item, index) => `${index + 6}:00`),
splitLine: {
show: true,
lineStyle: {
width: 1,
type: 'solid',
color: 'rgba(226,226,226,0.5)',
},
},
axisTick: {
show: false,
},
},
yAxis: [
{
type: 'value',
max: 80000,
splitNumber: 4,
axisTick: {
show: false,
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
},
},
},
],
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
series: [
{
smooth: true,
data: [
111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222,
11111, 4000, 2000, 500, 333, 222, 111,
],
type: 'line',
areaStyle: {},
itemStyle: {
color: '#5ab1ef',
},
},
{
smooth: true,
data: [
33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390,
198, 60, 30, 22, 11,
],
type: 'line',
areaStyle: {},
itemStyle: {
color: '#019680',
},
},
],
});
});
</script>

View File

@ -0,0 +1,48 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts">
import { basicProps } from './props';
</script>
<script lang="ts" setup>
import { onMounted, ref, Ref } from 'vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
onMounted(() => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: '#019680',
},
},
},
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
xAxis: {
type: 'category',
data: [...new Array(12)].map((_item, index) => t(`{index}月`, { index: index + 1 })),
},
yAxis: {
type: 'value',
max: 8000,
splitNumber: 4,
},
series: [
{
data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
type: 'bar',
barMaxWidth: 80,
},
],
});
});
</script>

View File

@ -0,0 +1,95 @@
<template>
<Card :title="t('转化率')" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
legend: {
bottom: 0,
data: [t('访问'), t('购买')],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: t('电脑'),
},
{
name: t('充电器'),
},
{
name: t('耳机'),
},
{
name: t('手机'),
},
{
name: 'Ipad',
},
{
name: t('耳机'),
},
],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: t('访问'),
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: t('购买'),
itemStyle: {
color: '#5ab1ef',
},
},
],
},
],
});
},
{ immediate: true },
);
</script>

View File

@ -0,0 +1,83 @@
<template>
<Card :title="t('访问来源')" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
legend: {
bottom: '1%',
left: 'center',
},
series: [
{
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
name: t('访问来源'),
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '12',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: t('搜索引擎') },
{ value: 735, name: t('直接访问') },
{ value: 580, name: t('邮件营销') },
{ value: 484, name: t('联盟广告') },
],
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 100;
},
},
],
});
},
{ immediate: true },
);
</script>

View File

@ -0,0 +1,16 @@
import { PropType } from 'vue';
export interface BasicProps {
width: string;
height: string;
}
export const basicProps = {
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '280px',
},
};

View File

@ -0,0 +1,45 @@
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export interface GrowCardItem {
icon: string;
title: string;
value: number;
total: number;
color: string;
action: string;
}
export const growCardList: GrowCardItem[] = [
{
title: t('访问数'),
icon: 'visit-count|svg',
value: 2000,
total: 120000,
color: 'green',
action: t('月'),
},
{
title: t('成交额'),
icon: 'total-sales|svg',
value: 20000,
total: 500000,
color: 'blue',
action: t('月'),
},
{
title: t('下载数'),
icon: 'download-count|svg',
value: 8000,
total: 120000,
color: 'orange',
action: t('周'),
},
{
title: t('成交数'),
icon: 'transaction|svg',
value: 5000,
total: 50000,
color: 'purple',
action: t('年'),
},
];

View File

@ -0,0 +1,63 @@
<template>
<div v-if="desktop.hasCustomDesktop">
<a-spin :spinning="spinning" :delay="delayTime">
<DesktopHome v-if="desktop.hasCustomDesktop" :list="desktop.list" />
</a-spin>
</div>
<div class="p-4" v-else>
<GrowCard :loading="loading" class="enter-y" />
<SiteAnalysis class="!my-4 enter-y" :loading="loading" />
<div class="md:flex enter-y">
<VisitRadar class="md:w-1/3 w-full" :loading="loading" />
<VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
<SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
</div>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import GrowCard from './components/GrowCard.vue';
import SiteAnalysis from './components/SiteAnalysis.vue';
import VisitSource from './components/VisitSource.vue';
import VisitRadar from './components/VisitRadar.vue';
import SalesProductPie from './components/SalesProductPie.vue';
import DesktopHome from '/@/views/generator/desktop/components/DesktopHome.vue';
import { useUserStore } from '/@/store/modules/user';
import { useTabs } from '/@/hooks/web/useTabs';
import { useTitle } from '@vueuse/core';
const loading = ref(true);
const desktop = reactive({ hasCustomDesktop: false, list: [] });
const spinning = ref<boolean>(true);
const delayTime = 500;
setTimeout(() => {
loading.value = false;
spinning.value = false;
}, 1000);
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
const { setTitle } = useTabs();
watch(
() => userInfo.desktopSchema,
(v) => {
getList(v);
},
{
deep: true,
},
);
getList(userInfo.desktopSchema);
function getList(desktopSchema) {
if (desktopSchema?.jsonContent) {
desktop.list = JSON.parse(desktopSchema.jsonContent);
//设置Tab标题
setTitle(desktopSchema.name);
//设置浏览器标题
useTitle(` ${desktopSchema.name} `);
desktop.hasCustomDesktop = true;
}
}
</script>

View File

@ -0,0 +1,31 @@
<template>
<Card title="最新动态" v-bind="$attrs">
<template #extra>
<a-button type="link" size="small">更多</a-button>
</template>
<List item-layout="horizontal" :data-source="dynamicInfoItems">
<template #renderItem="{ item }">
<ListItem>
<ListItemMeta>
<template #description>
{{ item.date }}
</template>
<!-- eslint-disable-next-line -->
<template #title> {{ item.name }} <span v-html="item.desc"> </span> </template>
<template #avatar>
<Icon :icon="item.avatar" :size="30" />
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</Card>
</template>
<script lang="ts" setup>
import { Card, List } from 'ant-design-vue';
import { dynamicInfoItems } from './data';
import { Icon } from '/@/components/Icon';
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
</script>

View File

@ -0,0 +1,32 @@
<template>
<Card title="项目" v-bind="$attrs">
<template #extra>
<a-button type="link" size="small">更多</a-button>
</template>
<CardGrid v-for="item in items" :key="item" class="!md:w-1/3 !w-full">
<span class="flex">
<Icon :icon="item.icon" :color="item.color" size="30" />
<span class="text-lg ml-4">{{ item.title }}</span>
</span>
<div class="flex mt-2 h-10 text-secondary">{{ item.desc }}</div>
<div class="flex justify-between text-secondary">
<span>{{ item.group }}</span>
<span>{{ item.date }}</span>
</div>
</CardGrid>
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Card } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { groupItems } from './data';
export default defineComponent({
components: { Card, CardGrid: Card.Grid, Icon },
setup() {
return { items: groupItems };
},
});
</script>

View File

@ -0,0 +1,17 @@
<template>
<Card title="快捷导航" v-bind="$attrs">
<CardGrid v-for="item in navItems" :key="item">
<span class="flex flex-col items-center">
<Icon :icon="item.icon" :color="item.color" size="20" />
<span class="text-md mt-2">{{ item.title }}</span>
</span>
</CardGrid>
</Card>
</template>
<script lang="ts" setup>
import { Card } from 'ant-design-vue';
import { navItems } from './data';
import { Icon } from '/@/components/Icon';
const CardGrid = Card.Grid;
</script>

View File

@ -0,0 +1,94 @@
<template>
<Card title="销售统计" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '400px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
legend: {
bottom: 0,
data: ['Visits', 'Sales'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '2017',
},
{
name: '2017',
},
{
name: '2018',
},
{
name: '2019',
},
{
name: '2020',
},
{
name: '2021',
},
],
},
series: [
{
type: 'radar',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: 'Visits',
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: 'Sales',
itemStyle: {
color: '#67e0e3',
},
},
],
},
],
});
},
{ immediate: true },
);
</script>

View File

@ -0,0 +1,33 @@
<template>
<div class="lg:flex">
<Avatar :src="userinfo.avatar || headerImg" :size="72" class="!mx-auto !block" />
<div class="md:ml-6 flex flex-col justify-center md:mt-0 mt-2">
<h1 class="md:text-lg text-md">早安, {{ userinfo.realName }}, 开始您一天的工作吧</h1>
<span class="text-secondary"> 今日晴20 - 32 </span>
</div>
<div class="flex flex-1 justify-end md:mt-0 mt-4">
<div class="flex flex-col justify-center text-right">
<span class="text-secondary"> 待办 </span>
<span class="text-2xl">2/10</span>
</div>
<div class="flex flex-col justify-center text-right md:mx-16 mx-12">
<span class="text-secondary"> 项目 </span>
<span class="text-2xl">8</span>
</div>
<div class="flex flex-col justify-center text-right md:mr-10 mr-4">
<span class="text-secondary"> 团队 </span>
<span class="text-2xl">300</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { Avatar } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import headerImg from '/@/assets/images/header.jpg';
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
</script>

View File

@ -0,0 +1,156 @@
interface GroupItem {
title: string;
icon: string;
color: string;
desc: string;
date: string;
group: string;
}
interface NavItem {
title: string;
icon: string;
color: string;
}
interface DynamicInfoItem {
avatar: string;
name: string;
date: string;
desc: string;
}
export const navItems: NavItem[] = [
{
title: '首页',
icon: 'ion:home-outline',
color: '#1fdaca',
},
{
title: '仪表盘',
icon: 'ion:grid-outline',
color: '#bf0c2c',
},
{
title: '组件',
icon: 'ion:layers-outline',
color: '#e18525',
},
{
title: '系统管理',
icon: 'ion:settings-outline',
color: '#3fb27f',
},
{
title: '权限管理',
icon: 'ion:key-outline',
color: '#4daf1bc9',
},
{
title: '图表',
icon: 'ion:bar-chart-outline',
color: '#00d8ff',
},
];
export const dynamicInfoItems: DynamicInfoItem[] = [
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '刚刚',
desc: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
},
{
avatar: 'dynamic-avatar-2|svg',
name: '艾文',
date: '1个小时前',
desc: `关注了 <a>威廉</a> `,
},
{
avatar: 'dynamic-avatar-3|svg',
name: '克里斯',
date: '1天前',
desc: `发布了 <a>个人动态</a> `,
},
{
avatar: 'dynamic-avatar-4|svg',
name: 'Vben',
date: '2天前',
desc: `发表文章 <a>如何编写一个Vite插件</a> `,
},
{
avatar: 'dynamic-avatar-5|svg',
name: '皮特',
date: '3天前',
desc: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
},
{
avatar: 'dynamic-avatar-6|svg',
name: '杰克',
date: '1周前',
desc: `关闭了问题 <a>如何运行项目</a> `,
},
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '1周前',
desc: `发布了 <a>个人动态</a> `,
},
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '2021-04-01 20:00',
desc: `推送了代码到 <a>Github</a>`,
},
];
export const groupItems: GroupItem[] = [
{
title: 'Github',
icon: 'carbon:logo-github',
color: '',
desc: '不要等待机会,而要创造机会。',
group: '开源组',
date: '2021-04-01',
},
{
title: 'Vue',
icon: 'ion:logo-vue',
color: '#3fb27f',
desc: '现在的你决定将来的你。',
group: '算法组',
date: '2021-04-01',
},
{
title: 'Html5',
icon: 'ion:logo-html5',
color: '#e18525',
desc: '没有什么才能比努力更重要。',
group: '上班摸鱼',
date: '2021-04-01',
},
{
title: 'Angular',
icon: 'ion:logo-angular',
color: '#bf0c2c',
desc: '热情和欲望可以突破一切难关。',
group: 'UI',
date: '2021-04-01',
},
{
title: 'React',
icon: 'bx:bxl-react',
color: '#00d8ff',
desc: '健康的身体是实目标的基石。',
group: '技术牛',
date: '2021-04-01',
},
{
title: 'Js',
icon: 'ion:logo-javascript',
color: '#4daf1bc9',
desc: '路是走出来的,而不是空想出来的。',
group: '架构组',
date: '2021-04-01',
},
];

View File

@ -0,0 +1,36 @@
<template>
<PageWrapper>
<template #headerContent> <WorkbenchHeader /> </template>
<div class="lg:flex">
<div class="lg:w-7/10 w-full !mr-4 enter-y">
<ProjectCard :loading="loading" class="enter-y" />
<DynamicInfo :loading="loading" class="!my-4 enter-y" />
</div>
<div class="lg:w-3/10 w-full enter-y">
<QuickNav :loading="loading" class="enter-y" />
<Card class="!my-4 enter-y" :loading="loading">
<img class="xl:h-50 h-30 mx-auto" src="../../../assets/svg/illustration.svg" />
</Card>
<SaleRadar :loading="loading" class="enter-y" />
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Card } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import ProjectCard from './components/ProjectCard.vue';
import QuickNav from './components/QuickNav.vue';
import DynamicInfo from './components/DynamicInfo.vue';
import SaleRadar from './components/SaleRadar.vue';
const loading = ref(true);
setTimeout(() => {
loading.value = false;
}, 1500);
</script>