feat(projects): 引入echarts替换antvG2plot

This commit is contained in:
Soybean 2022-05-07 00:58:24 +08:00
parent 14c145eef1
commit e7ad08685e
9 changed files with 384 additions and 878 deletions

2
.gitignore vendored
View File

@ -28,3 +28,5 @@ stats.html
*.njsproj
*.sln
*.sw?
/src/typings/components.d.ts

View File

@ -50,7 +50,7 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
## 开发计划
- [ ] 引入ECharts替换AntV G2Plot
- [x] 引入ECharts替换AntV G2Plot
- [ ] 多页签同一页面支持多个Tab(应用场景不同query参数的详情页面在不同tab展示)
- [ ] 最近功能的有关文档更新
- [ ] 性能优化(优化递归函数)

View File

@ -26,7 +26,6 @@
}
},
"dependencies": {
"@antv/g2plot": "^2.4.16",
"@better-scroll/core": "^2.4.2",
"@soybeanjs/vue-admin-layout": "^1.0.3",
"@soybeanjs/vue-admin-tab": "^1.0.1",
@ -36,6 +35,7 @@
"colord": "^2.9.2",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.1",
"echarts": "^5.3.2",
"form-data": "^4.0.0",
"lodash-es": "^4.17.21",
"naive-ui": "^2.28.2",

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
import useEcharts from './useEcharts';
import useCountDown from './useCountDown';
import useSmsCode from './useSmsCode';
import useImageVerify from './useImageVerify';
export { useCountDown, useSmsCode, useImageVerify };
export { useEcharts, useCountDown, useSmsCode, useImageVerify };
export * from './useEcharts';

View File

@ -0,0 +1,136 @@
import { ref, watch, nextTick, onUnmounted, computed } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import * as echarts from 'echarts/core';
import { BarChart, LineChart, PieChart } from 'echarts/charts';
import type { BarSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts/charts';
import {
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent
} from 'echarts/components';
import type {
TitleComponentOption,
LegendComponentOption,
TooltipComponentOption,
GridComponentOption,
ToolboxComponentOption,
DatasetComponentOption
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { useElementSize } from '@vueuse/core';
export type ECOption = echarts.ComposeOption<
| BarSeriesOption
| LineSeriesOption
| PieSeriesOption
| TitleComponentOption
| LegendComponentOption
| TooltipComponentOption
| GridComponentOption
| ToolboxComponentOption
| DatasetComponentOption
>;
echarts.use([
TitleComponent,
LegendComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
BarChart,
LineChart,
PieChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
/**
* Echarts hooks函数
* @param options -
* @param renderFun - ()
*/
export default function useEcharts(
options: Ref<ECOption> | ComputedRef<ECOption>,
darkMode?: ComputedRef<boolean>,
renderFun?: (chartInstance: echarts.ECharts) => void
) {
let chart: echarts.ECharts | null = null;
const domRef = ref<HTMLElement>();
const initialSize = { width: 0, height: 0 };
const { width, height } = useElementSize(domRef, initialSize);
function canRender() {
return initialSize.width > 0 && initialSize.height > 0;
}
function isRendered() {
return Boolean(domRef.value && chart);
}
function update(updateOptions: ECOption) {
if (isRendered()) {
chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' });
}
}
async function render() {
if (domRef.value) {
const theme = darkMode?.value ? 'dark' : 'light';
await nextTick();
chart = echarts.init(domRef.value, theme);
if (renderFun) {
renderFun(chart);
}
update(options.value);
}
}
function resize() {
chart?.resize();
}
function destroy() {
chart?.dispose();
}
function updateTheme() {
destroy();
render();
}
watch([width, height], ([newWidth, newHeight]) => {
initialSize.width = newWidth;
initialSize.height = newHeight;
if (canRender()) {
if (!isRendered()) {
render();
} else {
resize();
}
}
});
watch(options, newValue => {
update(newValue);
});
watch(darkMode || computed(() => false), () => {
updateTheme();
});
onUnmounted(() => {
destroy();
});
return {
domRef
};
}

View File

@ -1,105 +0,0 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
BetterScroll: typeof import('./../components/custom/BetterScroll.vue')['default']
CountTo: typeof import('./../components/custom/CountTo.vue')['default']
DarkModeContainer: typeof import('./../components/common/DarkModeContainer.vue')['default']
DarkModeSwitch: typeof import('./../components/common/DarkModeSwitch.vue')['default']
GithubLink: typeof import('./../components/custom/GithubLink.vue')['default']
HoverContainer: typeof import('./../components/common/HoverContainer.vue')['default']
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconCustomActivity: typeof import('~icons/custom/activity')['default']
IconCustomAvatar: typeof import('~icons/custom/avatar')['default']
IconCustomBanner: typeof import('~icons/custom/banner')['default']
IconCustomCast: typeof import('~icons/custom/cast')['default']
IconCustomEmptyData: typeof import('~icons/custom/empty-data')['default']
IconCustomLogo: typeof import('~icons/custom/logo')['default']
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']
IconCustomNetworkError: typeof import('~icons/custom/network-error')['default']
IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default']
IconCustomNotFound: typeof import('~icons/custom/not-found')['default']
IconCustomServiceError: typeof import('~icons/custom/service-error')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcBaselineDoNotDisturb: typeof import('~icons/ic/baseline-do-not-disturb')['default']
IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default']
IconIcRoundHdrAuto: typeof import('~icons/ic/round-hdr-auto')['default']
IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default']
IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiGithub: typeof import('~icons/mdi/github')['default']
IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default']
IconMdiPin: typeof import('~icons/mdi/pin')['default']
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiWechat: typeof import('~icons/mdi/wechat')['default']
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']
IconSelect: typeof import('./../components/custom/IconSelect.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
ImageVerify: typeof import('./../components/custom/ImageVerify.vue')['default']
LoadingEmptyWrapper: typeof import('./../components/business/LoadingEmptyWrapper.vue')['default']
LoginAgreement: typeof import('./../components/business/LoginAgreement.vue')['default']
NaiveProvider: typeof import('./../components/common/NaiveProvider.vue')['default']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGradientText: typeof import('naive-ui')['NGradientText']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTimeline: typeof import('naive-ui')['NTimeline']
NTimelineItem: typeof import('naive-ui')['NTimelineItem']
NTooltip: typeof import('naive-ui')['NTooltip']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SystemLogo: typeof import('./../components/common/SystemLogo.vue')['default']
WebSiteLink: typeof import('./../components/custom/WebSiteLink.vue')['default']
}
}
export {}

View File

@ -1,152 +0,0 @@
[
{
"date": "2021/10/1",
"type": "下载量",
"value": 4623
},
{
"date": "2021/10/1",
"type": "注册数",
"value": 2208
},
{
"date": "2021/10/2",
"type": "下载量",
"value": 6145
},
{
"date": "2021/10/2",
"type": "注册数",
"value": 2016
},
{
"date": "2021/10/3",
"type": "下载量",
"value": 508
},
{
"date": "2021/10/3",
"type": "注册数",
"value": 2916
},
{
"date": "2021/10/4",
"type": "下载量",
"value": 6268
},
{
"date": "2021/10/4",
"type": "注册数",
"value": 4512
},
{
"date": "2021/10/5",
"type": "下载量",
"value": 6411
},
{
"date": "2021/10/5",
"type": "注册数",
"value": 8281
},
{
"date": "2021/10/6",
"type": "下载量",
"value": 1890
},
{
"date": "2021/10/6",
"type": "注册数",
"value": 2008
},
{
"date": "2021/10/7",
"type": "下载量",
"value": 4251
},
{
"date": "2021/10/7",
"type": "注册数",
"value": 1963
},
{
"date": "2021/10/8",
"type": "下载量",
"value": 2978
},
{
"date": "2021/10/8",
"type": "注册数",
"value": 2367
},
{
"date": "2021/10/9",
"type": "下载量",
"value": 3880
},
{
"date": "2021/10/9",
"type": "注册数",
"value": 2956
},
{
"date": "2021/10/10",
"type": "下载量",
"value": 3606
},
{
"date": "2021/10/10",
"type": "注册数",
"value": 678
},
{
"date": "2021/10/11",
"type": "下载量",
"value": 4311
},
{
"date": "2021/10/11",
"type": "注册数",
"value": 3188
},
{
"date": "2021/10/12",
"type": "下载量",
"value": 4116
},
{
"date": "2021/10/12",
"type": "注册数",
"value": 3491
},
{
"date": "2021/10/13",
"type": "下载量",
"value": 6419
},
{
"date": "2021/10/13",
"type": "注册数",
"value": 2852
},
{
"date": "2021/10/14",
"type": "下载量",
"value": 1643
},
{
"date": "2021/10/14",
"type": "注册数",
"value": 4788
},
{
"date": "2021/10/15",
"type": "下载量",
"value": 445
},
{
"date": "2021/10/15",
"type": "注册数",
"value": 4319
}
]

View File

@ -31,99 +31,159 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { Line, Pie } from '@antv/g2plot';
import data from './data.json';
import { ref, computed } from 'vue';
import { useThemeStore } from '@/store';
import { useEcharts, type ECOption } from '@/hooks';
const lineRef = ref<HTMLElement>();
const line = ref<Line>();
const pieRef = ref<HTMLElement>();
const pie = ref<Pie>();
const theme = useThemeStore();
function renderLineChart() {
if (!lineRef.value) return;
line.value = new Line(lineRef.value, {
data,
autoFit: true,
xField: 'date',
yField: 'value',
seriesField: 'type',
lineStyle: {
lineWidth: 4
},
area: {
style: {
fill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff'
}
},
smooth: true,
animation: {
appear: {
animation: 'wave-in',
duration: 2000
}
}
});
line.value.render();
}
function renderPieChart() {
if (!pieRef.value) return;
const pieData = [
{ type: '学习', value: 20 },
{ type: '娱乐', value: 10 },
{ type: '工作', value: 30 },
{ type: '休息', value: 40 }
];
pie.value = new Pie(pieRef.value, {
appendPadding: 10,
data: pieData,
angleField: 'value',
colorField: 'type',
radius: 0.8,
innerRadius: 0.65,
meta: {
value: {
formatter: v => `${v}%`
}
},
label: {
type: 'inner',
autoRotate: false,
formatter: ({ percent }) => `${(percent * 100).toFixed(0)}%`
},
statistic: undefined,
pieStyle: {
radius: [20]
},
color: ['#025DF4', '#DB6BCF', '#2498D1', '#FF745A', '#007E99', '#FFA8A8', '#2391FF'],
legend: {
position: 'bottom'
},
interactions: [
{ type: 'element-selected' },
{ type: 'element-active' },
{
type: 'pie-statistic-active',
cfg: {
start: [
{ trigger: 'element:mouseenter', action: 'pie-statistic:change' },
{ trigger: 'legend-item:mouseenter', action: 'pie-statistic:change' }
],
end: [
{ trigger: 'element:mouseleave', action: 'pie-statistic:reset' },
{ trigger: 'legend-item:mouseleave', action: 'pie-statistic:reset' }
]
const darkMode = computed(() => theme.darkMode);
const { domRef: lineRef } = useEcharts(
ref<ECOption>({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['下载量', '注册数']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
color: '#8e9dff',
name: '下载量',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#8e9dff'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311]
},
{
color: '#26deca',
name: '注册数',
type: 'line',
smooth: true,
stack: 'Total',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0.25,
color: '#26deca'
},
{
offset: 1,
color: '#fff'
}
]
}
},
emphasis: {
focus: 'series'
},
data: [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678]
}
]
});
}),
darkMode
);
pie.value.render();
}
onMounted(() => {
renderLineChart();
renderPieChart();
});
const { domRef: pieRef } = useEcharts(
ref<ECOption>({
tooltip: {
trigger: 'item'
},
legend: {
bottom: '1%',
left: 'center',
itemStyle: {
borderWidth: 0
}
},
series: [
{
color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
name: '时间安排',
type: 'pie',
radius: ['45%', '75%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 1
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '12'
}
},
labelLine: {
show: false
},
data: [
{ value: 20, name: '学习' },
{ value: 10, name: '娱乐' },
{ value: 30, name: '工作' },
{ value: 40, name: '休息' }
]
}
]
}),
darkMode
);
</script>
<style></style>