feat(component): Add CountTo component and Echart component

This commit is contained in:
kailong321200875 2022-01-22 19:39:44 +08:00
parent dad7330634
commit e20fa76cad
15 changed files with 461 additions and 48 deletions

View File

@ -30,6 +30,8 @@
"@zxcvbn-ts/core": "^1.2.0",
"animate.css": "^4.1.1",
"axios": "^0.25.0",
"echarts": "^5.2.2",
"echarts-wordcloud": "^2.0.0",
"element-plus": "1.3.0-beta.5",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",

View File

@ -21,6 +21,8 @@ specifiers:
autoprefixer: ^10.4.2
axios: ^0.25.0
commitizen: ^4.2.4
echarts: ^5.2.2
echarts-wordcloud: ^2.0.0
element-plus: 1.3.0-beta.5
eslint: ^8.7.0
eslint-config-prettier: ^8.3.0
@ -70,6 +72,8 @@ dependencies:
'@zxcvbn-ts/core': registry.npmmirror.com/@zxcvbn-ts/core/1.2.0
animate.css: registry.npmmirror.com/animate.css/4.1.1
axios: registry.npmmirror.com/axios/0.25.0
echarts: registry.npmmirror.com/echarts/5.2.2
echarts-wordcloud: registry.npmmirror.com/echarts-wordcloud/2.0.0_echarts@5.2.2
element-plus: registry.npmmirror.com/element-plus/1.3.0-beta.5_vue@3.2.26
lodash-es: registry.nlark.com/lodash-es/4.17.21
mockjs: registry.npmmirror.com/mockjs/1.1.0
@ -350,38 +354,6 @@ packages:
version: 1.0.2
dev: true
registry.nlark.com/acorn-jsx/5.3.2_acorn@7.4.1:
resolution:
{
integrity: sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/acorn-jsx/download/acorn-jsx-5.3.2.tgz
}
id: registry.nlark.com/acorn-jsx/5.3.2
name: acorn-jsx
version: 5.3.2
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: registry.npmmirror.com/acorn/7.4.1
dev: true
registry.nlark.com/acorn-jsx/5.3.2_acorn@8.7.0:
resolution:
{
integrity: sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/acorn-jsx/download/acorn-jsx-5.3.2.tgz
}
id: registry.nlark.com/acorn-jsx/5.3.2
name: acorn-jsx
version: 5.3.2
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: registry.npmmirror.com/acorn/8.7.0
dev: true
registry.nlark.com/acorn-walk/8.2.0:
resolution:
{
@ -1781,7 +1753,7 @@ packages:
{
integrity: sha1-0t5eA0JOcH3BDHQGjd7a5wh0Gyc=,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz
tarball: https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz?cache=0&sync_timestamp=1631600361784&other_urls=https%3A%2F%2Fregistry.nlark.com%2Feslint-utils%2Fdownload%2Feslint-utils-2.1.0.tgz
}
name: eslint-utils
version: 2.1.0
@ -6244,6 +6216,19 @@ packages:
engines: { node: '>=10' }
dev: true
registry.nlark.com/zrender/5.2.1:
resolution:
{
integrity: sha1-X0u9qRW6bUErCxncJDG+qtBUF7s=,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.nlark.com/zrender/download/zrender-5.2.1.tgz
}
name: zrender
version: 5.2.1
dependencies:
tslib: registry.npmmirror.com/tslib/2.3.0
dev: false
registry.npmmirror.com/@antfu/utils/0.3.0:
resolution:
{
@ -8402,6 +8387,38 @@ packages:
through: registry.nlark.com/through/2.3.8
dev: true
registry.npmmirror.com/acorn-jsx/5.3.2_acorn@7.4.1:
resolution:
{
integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/acorn-jsx/download/acorn-jsx-5.3.2.tgz
}
id: registry.npmmirror.com/acorn-jsx/5.3.2
name: acorn-jsx
version: 5.3.2
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: registry.npmmirror.com/acorn/7.4.1
dev: true
registry.npmmirror.com/acorn-jsx/5.3.2_acorn@8.7.0:
resolution:
{
integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/acorn-jsx/download/acorn-jsx-5.3.2.tgz
}
id: registry.npmmirror.com/acorn-jsx/5.3.2
name: acorn-jsx
version: 5.3.2
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
acorn: registry.npmmirror.com/acorn/8.7.0
dev: true
registry.npmmirror.com/acorn/7.4.1:
resolution:
{
@ -9378,6 +9395,36 @@ packages:
domhandler: registry.npmmirror.com/domhandler/4.3.0
dev: true
registry.npmmirror.com/echarts-wordcloud/2.0.0_echarts@5.2.2:
resolution:
{
integrity: sha512-K7l6pTklqdW7ZWzT/1CS0KhBSINr/cd7c5N1fVMzZMwLQHEwT7x+nivK7g5hkVh7WNcAv4Dn6/ZS5zMKRozC1g==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/echarts-wordcloud/download/echarts-wordcloud-2.0.0.tgz
}
id: registry.npmmirror.com/echarts-wordcloud/2.0.0
name: echarts-wordcloud
version: 2.0.0
peerDependencies:
echarts: ^5.0.1
dependencies:
echarts: registry.npmmirror.com/echarts/5.2.2
dev: false
registry.npmmirror.com/echarts/5.2.2:
resolution:
{
integrity: sha512-yxuBfeIH5c+0FsoRP60w4De6omXhA06c7eUYBsC1ykB6Ys2yK5fSteIYWvkJ4xJVLQgCvAdO8C4mN6MLeJpBaw==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/echarts/download/echarts-5.2.2.tgz
}
name: echarts
version: 5.2.2
dependencies:
tslib: registry.npmmirror.com/tslib/2.3.0
zrender: registry.nlark.com/zrender/5.2.1
dev: false
registry.npmmirror.com/electron-to-chromium/1.4.30:
resolution:
{
@ -10035,7 +10082,7 @@ packages:
engines: { node: '>=6.0.0' }
dependencies:
acorn: registry.npmmirror.com/acorn/7.4.1
acorn-jsx: registry.nlark.com/acorn-jsx/5.3.2_acorn@7.4.1
acorn-jsx: registry.npmmirror.com/acorn-jsx/5.3.2_acorn@7.4.1
eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/1.3.0
dev: true
@ -10051,7 +10098,7 @@ packages:
engines: { node: ^10.12.0 || >=12.0.0 }
dependencies:
acorn: registry.npmmirror.com/acorn/7.4.1
acorn-jsx: registry.nlark.com/acorn-jsx/5.3.2_acorn@7.4.1
acorn-jsx: registry.npmmirror.com/acorn-jsx/5.3.2_acorn@7.4.1
eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/1.3.0
dev: true
@ -10067,7 +10114,7 @@ packages:
engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 }
dependencies:
acorn: registry.npmmirror.com/acorn/8.7.0
acorn-jsx: registry.nlark.com/acorn-jsx/5.3.2_acorn@8.7.0
acorn-jsx: registry.npmmirror.com/acorn-jsx/5.3.2_acorn@8.7.0
eslint-visitor-keys: registry.npmmirror.com/eslint-visitor-keys/3.2.0
dev: true
@ -12239,6 +12286,17 @@ packages:
version: 1.14.1
dev: true
registry.npmmirror.com/tslib/2.3.0:
resolution:
{
integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==,
registry: https://registry.npm.taobao.org/,
tarball: https://registry.npmmirror.com/tslib/download/tslib-2.3.0.tgz
}
name: tslib
version: 2.3.0
dev: false
registry.npmmirror.com/tslib/2.3.1:
resolution:
{

View File

View File

@ -0,0 +1,175 @@
<script setup lang="ts">
import { reactive, computed, watch, onMounted, unref, toRef, PropType } from 'vue'
import { isNumber } from '@/utils/is'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
startVal: propTypes.number.def(0),
endVal: propTypes.number.def(2021),
duration: propTypes.number.def(3000),
autoplay: propTypes.bool.def(false),
decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
decimal: propTypes.string.def('.'),
separator: propTypes.string.def(','),
prefix: propTypes.string.def(''),
suffix: propTypes.string.def(''),
useEasing: propTypes.bool.def(true),
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
}
}
})
const emit = defineEmits(['mounted', 'callback'])
const formatNumber = (num: number | string) => {
const { decimals, decimal, separator, suffix, prefix } = 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
}
const state = reactive<{
localStartVal: number
printVal: number | null
displayValue: string
paused: boolean
localDuration: number | null
startTime: number | null
timestamp: number | null
rAF: any
remaining: number | null
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
})
const displayValue = toRef(state, 'displayValue')
onMounted(() => {
if (props.autoplay) {
start()
}
emit('mounted')
})
const getCountDown = computed(() => {
return props.startVal > props.endVal
})
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start()
}
})
const start = () => {
const { startVal, duration } = props
state.localStartVal = startVal
state.startTime = null
state.localDuration = duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
const pauseResume = () => {
if (state.paused) {
resume()
state.paused = false
} else {
pause()
state.paused = true
}
}
const pause = () => {
cancelAnimationFrame(state.rAF)
}
const resume = () => {
state.startTime = null
state.localDuration = +(state.remaining as number)
state.localStartVal = +(state.printVal as number)
requestAnimationFrame(count)
}
const reset = () => {
state.startTime = null
cancelAnimationFrame(state.rAF)
state.displayValue = formatNumber(props.startVal)
}
const count = (timestamp: number) => {
const { useEasing, easingFn, endVal } = props
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = (state.localDuration as number) - progress
if (useEasing) {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
} else {
state.printVal = easingFn(
progress,
state.localStartVal,
endVal - state.localStartVal,
state.localDuration as number
)
}
} else {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
(state.localStartVal - endVal) * (progress / (state.localDuration as number))
} else {
state.printVal =
state.localStartVal +
(endVal - state.localStartVal) * (progress / (state.localDuration as number))
}
}
if (unref(getCountDown)) {
state.printVal = state.printVal < endVal ? endVal : state.printVal
} else {
state.printVal = state.printVal > endVal ? endVal : state.printVal
}
state.displayValue = formatNumber(state.printVal)
if (progress < (state.localDuration as number)) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}
defineExpose({
pauseResume,
reset,
start,
pause
})
</script>
<template>
<span>
{{ displayValue }}
</span>
</template>

View File

@ -0,0 +1,3 @@
import Echart from './src/Echart.vue'
export { Echart }

View File

@ -0,0 +1,110 @@
<script setup lang="ts">
import type { EChartsOption } from 'echarts'
import echarts from '@/plugins/echarts'
import { debounce } from 'lodash-es'
import 'echarts-wordcloud'
import { propTypes } from '@/utils/propTypes'
import { computed, PropType, ref, unref, watch, onMounted, onBeforeUnmount, onActivated } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { isString } from '@/utils/is'
const appStore = useAppStore()
const props = defineProps({
options: {
type: Object as PropType<EChartsOption>,
required: true
},
width: propTypes.oneOfType([Number, String]).def(''),
height: propTypes.oneOfType([Number, String]).def('500px')
})
const isDark = computed(() => appStore.getIsDark)
const theme = computed(() => {
const echartTheme: boolean | string = unref(isDark) ? true : 'auto'
return echartTheme
})
const options = computed(() => {
return Object.assign(props.options, {
darkMode: unref(theme)
})
})
const elRef = ref<ElRef>()
const echartRef = ref<echarts.ECharts>()
const contentEl = ref<Element>()
const styles = computed(() => {
const width = isString(props.width) ? props.width : `${props.width}px`
const height = isString(props.height) ? props.height : `${props.height}px`
return {
width,
height
}
})
const initChart = () => {
if (unref(elRef) && props.options) {
echartRef.value = echarts.init(unref(elRef) as HTMLElement, unref(options))
}
}
watch(
() => options.value,
(options) => {
const chart = unref(echartRef)
if (chart) {
chart?.setOption(options)
}
},
{
deep: true
}
)
const resizeHandler = debounce(() => {
const chart = unref(echartRef)
if (chart) {
chart.resize()
}
}, 100)
const contentResizeHandler = (e: TransitionEvent) => {
if (e.propertyName === 'width') {
resizeHandler()
}
}
onMounted(() => {
initChart()
window.addEventListener('resize', resizeHandler)
contentEl.value = document.getElementsByClassName('v-content')[0]
unref(contentEl) &&
(unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler)
unref(contentEl) &&
(unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
})
onActivated(() => {
const chart = unref(echartRef)
if (chart) {
chart.resize()
}
})
</script>
<template>
<div ref="elRef" :class="$attrs.class" :style="styles" />
</template>

View File

@ -147,6 +147,7 @@ watch(
{
icon: 'ant-design:close-outlined',
label: t('common.closeTab'),
disabled: !!visitedViews?.length && selectedTag?.meta.affix,
command: () => {
closeSelectedTag(item)
}
@ -243,7 +244,8 @@ watch(
},
{
icon: 'ant-design:close-outlined',
label: t('common.closeTab')
label: t('common.closeTab'),
disabled: !!visitedViews?.length && selectedTag?.meta.affix
},
{
divided: true,

View File

@ -49,7 +49,7 @@ export const useRenderLayout = () => {
</div>
<div
class={[
'v-app-right',
'v-content',
'absolute top-0 h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
@ -64,7 +64,6 @@ export const useRenderLayout = () => {
<ElScrollbar
v-loading={pageLoading.value}
class={[
'v-content',
{
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
fixedHeader.value
@ -110,7 +109,7 @@ export const useRenderLayout = () => {
<Menu class="!h-full"></Menu>
<div
class={[
'v-app-right',
'v-content',
'h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
@ -124,7 +123,6 @@ export const useRenderLayout = () => {
<ElScrollbar
v-loading={pageLoading.value}
class={[
'v-content',
{
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
fixedHeader.value && tagsView.value
@ -163,11 +161,10 @@ export const useRenderLayout = () => {
<Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu>
<ToolHeader></ToolHeader>
</div>
<div class="v-app-right h-full w-full">
<div class="v-content h-full w-full">
<ElScrollbar
v-loading={pageLoading.value}
class={[
'v-content',
{
'mt-[var(--tags-view-height)]': fixedHeader.value
}
@ -205,7 +202,7 @@ export const useRenderLayout = () => {
{/* <Menu class="!h-full"></Menu> */}
<div
class={[
'v-app-right',
'v-content',
'h-[100%]',
{
'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]':
@ -219,7 +216,6 @@ export const useRenderLayout = () => {
<ElScrollbar
v-loading={pageLoading.value}
class={[
'v-content',
{
'!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':
fixedHeader.value && tagsView.value

View File

@ -69,7 +69,9 @@ export default {
menu11: 'Menu1-1',
menu111: 'Menu1-1-1',
menu12: 'Menu1-2',
menu2: 'Menu2'
menu2: 'Menu2',
dashboard: 'Dashboard',
analysis: 'Analysis'
},
formDemo: {
input: 'Input',

View File

@ -69,7 +69,9 @@ export default {
menu11: '菜单1-1',
menu111: '菜单1-1-1',
menu12: '菜单1-2',
menu2: '菜单2'
menu2: '菜单2',
dashboard: '首页',
analysis: '分析页'
},
formDemo: {
input: '输入框',

View File

@ -38,7 +38,7 @@ router.beforeEach(async (to, from, next) => {
const redirect = decodeURIComponent(redirectPath as string)
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
permissionStore.setIsAddRouters(true)
next(nextData)
next(to.path === '/' ? { path: permissionStore.addRouters[0]?.path as string } : nextData)
}
} else {
if (whiteList.indexOf(to.path) !== -1) {

View File

@ -0,0 +1,33 @@
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
LegendComponent
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
LegendComponent,
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
BarChart,
LineChart,
PieChart,
MapChart,
CanvasRenderer,
PictorialBarChart
])
export default echarts

View File

@ -37,6 +37,29 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
]
export const asyncRouterMap: AppRouteRecordRaw[] = [
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/analysis',
name: 'Dashboard',
meta: {
title: t('router.dashboard'),
icon: 'ant-design:dashboard-filled',
alwaysShow: true
},
children: [
{
path: 'analysis',
component: () => import('@/views/Dashboard/Analysis.vue'),
name: 'Analysis',
meta: {
title: t('router.analysis'),
noCache: true,
affix: true
}
}
]
},
{
path: '/level',
component: Layout,

View File

@ -0,0 +1,7 @@
<script setup lang="ts">
// import { ElRow, ElCol } from 'element-plus'
</script>
<template>
<div>dddd</div>
</template>

View File