Browse Source

feat: Add count-to demo

feat: Add useWatermark hook and add useWatermark demo
陈凯龙 3 years ago
parent
commit
d3fbd3a06c

+ 1 - 3
src/hooks/web/usePageLoading.ts

@@ -8,9 +8,7 @@ export const usePageLoading = () => {
   }
   }
 
 
   const loadDone = () => {
   const loadDone = () => {
-    setTimeout(() => {
-      appStore.setPageLoading(false)
-    }, 1000)
+    appStore.setPageLoading(false)
   }
   }
 
 
   return {
   return {

+ 55 - 0
src/hooks/web/useWatermark.ts

@@ -0,0 +1,55 @@
+const domSymbol = Symbol('watermark-dom')
+
+export function useWatermark(appendEl: HTMLElement | null = document.body) {
+  let func: Fn = () => {}
+  const id = domSymbol.toString()
+  const clear = () => {
+    const domId = document.getElementById(id)
+    if (domId) {
+      const el = appendEl
+      el && el.removeChild(domId)
+    }
+    window.removeEventListener('resize', func)
+  }
+  const createWatermark = (str: string) => {
+    clear()
+
+    const can = document.createElement('canvas')
+    can.width = 300
+    can.height = 240
+
+    const cans = can.getContext('2d')
+    if (cans) {
+      cans.rotate((-20 * Math.PI) / 120)
+      cans.font = '15px Vedana'
+      cans.fillStyle = 'rgba(0, 0, 0, 0.15)'
+      cans.textAlign = 'left'
+      cans.textBaseline = 'middle'
+      cans.fillText(str, can.width / 20, can.height)
+    }
+
+    const div = document.createElement('div')
+    div.id = id
+    div.style.pointerEvents = 'none'
+    div.style.top = '0px'
+    div.style.left = '0px'
+    div.style.position = 'absolute'
+    div.style.zIndex = '100000000'
+    div.style.width = document.documentElement.clientWidth + 'px'
+    div.style.height = document.documentElement.clientHeight + 'px'
+    div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
+    const el = appendEl
+    el && el.appendChild(div)
+    return id
+  }
+
+  function setWatermark(str: string) {
+    createWatermark(str)
+    func = () => {
+      createWatermark(str)
+    }
+    window.addEventListener('resize', func)
+  }
+
+  return { setWatermark, clear }
+}

+ 29 - 1
src/locales/en.ts

@@ -87,7 +87,10 @@ export default {
     workplace: 'Workplace',
     workplace: 'Workplace',
     guide: 'Guide',
     guide: 'Guide',
     component: 'Component',
     component: 'Component',
-    icon: 'Icon'
+    icon: 'Icon',
+    echart: 'Echart',
+    countTo: 'Count to',
+    watermark: 'Watermark'
   },
   },
   analysis: {
   analysis: {
     newUser: 'New user',
     newUser: 'New user',
@@ -203,5 +206,30 @@ export default {
     recommendeDes:
     recommendeDes:
       'Iconify component basically contains all icons. You can query any icon you want. And packaging will only package the icons used.',
       'Iconify component basically contains all icons. You can query any icon you want. And packaging will only package the icons used.',
     accessAddress: 'Access address'
     accessAddress: 'Access address'
+  },
+  echartDemo: {
+    echart: 'Echart',
+    echartDes:
+      'Based on the secondary packaging components of eckarts, the width is adaptive. The corresponding chart can be displayed by passing in the options and height attributes.'
+  },
+  countToDemo: {
+    countTo: 'CountTo',
+    countToDes:
+      'The transformation is based on vue-count-to and supports all vue-count-to parameters.',
+    suffix: 'Suffix',
+    prefix: 'Prefix',
+    separator: 'Separator',
+    duration: 'Duration',
+    endVal: 'End val',
+    startVal: 'Start val',
+    start: 'Start',
+    pause: 'Pause',
+    resume: 'Resume'
+  },
+  watermarkDemo: {
+    watermark: 'Watermark',
+    createdWatermark: 'Created watermark',
+    clearWatermark: 'Clear watermark',
+    resetWatermark: 'Reset watermark'
   }
   }
 }
 }

+ 28 - 1
src/locales/zh-CN.ts

@@ -87,7 +87,10 @@ export default {
     workplace: '工作台',
     workplace: '工作台',
     guide: '引导',
     guide: '引导',
     component: '组件',
     component: '组件',
-    icon: '图标'
+    icon: '图标',
+    echart: '图表',
+    countTo: '数字动画',
+    watermark: '水印'
   },
   },
   analysis: {
   analysis: {
     newUser: '新增用户',
     newUser: '新增用户',
@@ -203,5 +206,29 @@ export default {
     recommendeDes:
     recommendeDes:
       'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。',
       'Iconify组件基本包含所有的图标,你可以查询到你想要的任何图标。并且打包只会打包所用到的图标。',
     accessAddress: '访问地址'
     accessAddress: '访问地址'
+  },
+  echartDemo: {
+    echart: '图表',
+    echartDes:
+      '基于 echarts 二次封装组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。'
+  },
+  countToDemo: {
+    countTo: '数字动画',
+    countToDes: '基于 vue-count-to 进行改造,支持所有 vue-count-to 参数。',
+    suffix: '后缀',
+    prefix: '前缀',
+    separator: '分割符号',
+    duration: '持续时间',
+    endVal: '结束值',
+    startVal: '开始值',
+    start: '开始',
+    pause: '暂停',
+    resume: '继续'
+  },
+  watermarkDemo: {
+    watermark: '水印',
+    createdWatermark: '创建水印',
+    clearWatermark: '清除水印',
+    resetWatermark: '重置水印'
   }
   }
 }
 }

+ 24 - 0
src/router/index.ts

@@ -103,6 +103,30 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
         meta: {
           title: t('router.icon')
           title: t('router.icon')
         }
         }
+      },
+      {
+        path: 'echart',
+        component: () => import('@/views/Components/Echart.vue'),
+        name: 'Echart',
+        meta: {
+          title: t('router.echart')
+        }
+      },
+      {
+        path: 'count-to',
+        component: () => import('@/views/Components/CountTo.vue'),
+        name: 'CountTo',
+        meta: {
+          title: t('router.countTo')
+        }
+      },
+      {
+        path: 'watermark',
+        component: () => import('@/views/Components/Watermark.vue'),
+        name: 'Watermark',
+        meta: {
+          title: t('router.watermark')
+        }
       }
       }
     ]
     ]
   },
   },

+ 100 - 0
src/views/Components/CountTo.vue

@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { CountTo } from '@/components/CountTo'
+import { ElRow, ElCol, ElInputNumber, ElInput, ElButton } from 'element-plus'
+import { ref, unref } from 'vue'
+
+const { t } = useI18n()
+
+const countRef = ref<ComponentRef<typeof CountTo>>()
+
+const startVal = ref(0)
+
+const endVal = ref(1314512)
+
+const duration = ref(3000)
+
+const decimals = ref(0)
+
+const separator = ref(',')
+
+const prefix = ref('¥ ')
+
+const suffix = ref(' rmb')
+
+const autoplay = ref(false)
+
+const start = () => {
+  unref(countRef)?.start()
+}
+
+const pauseResume = () => {
+  unref(countRef)?.pauseResume()
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('countToDemo.countTo')" :message="t('countToDemo.countToDes')">
+    <div class="text-center mb-40px">
+      <CountTo
+        ref="countRef"
+        :start-val="startVal"
+        :end-val="endVal"
+        :duration="duration"
+        :decimals="decimals"
+        :separator="separator"
+        :prefix="prefix"
+        :suffix="suffix"
+        :autoplay="autoplay"
+        class="text-30px font-bold text-[var(--el-color-primary)]"
+      />
+    </div>
+    <ElRow :gutter="20" justify="space-between">
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.startVal') }}:</span>
+          <ElInputNumber v-model="startVal" :min="0" />
+        </div>
+      </ElCol>
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.endVal') }}:</span>
+          <ElInputNumber v-model="endVal" :min="1" />
+        </div>
+      </ElCol>
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.duration') }}:</span>
+          <ElInputNumber v-model="duration" :min="1000" />
+        </div>
+      </ElCol>
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.separator') }}:</span>
+          <ElInput v-model="separator" />
+        </div>
+      </ElCol>
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.prefix') }}:</span>
+          <ElInput v-model="prefix" />
+        </div>
+      </ElCol>
+      <ElCol :xl="8" :lg="8" :md="12" :sm="24" :xs="24">
+        <div class="flex mb-20px items-center">
+          <span class="min-w-90px text-right">{{ t('countToDemo.suffix') }}:</span>
+          <ElInput v-model="suffix" />
+        </div>
+      </ElCol>
+      <ElCol :span="24">
+        <div class="text-center">
+          <ElButton type="primary" @click="start">{{ t('countToDemo.start') }}</ElButton>
+          <ElButton @click="pauseResume">
+            {{ t('countToDemo.pause') }}/{{ t('countToDemo.resume') }}
+          </ElButton>
+        </div>
+      </ElCol>
+    </ElRow>
+  </ContentWrap>
+</template>

+ 36 - 0
src/views/Components/Echart.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { pieOptions, barOptions, lineOptions, wordOptions } from '@/views/Dashboard/echarts-data'
+import { Echart } from '@/components/Echart'
+import { ElRow, ElCol, ElCard } from 'element-plus'
+
+const { t } = useI18n()
+</script>
+
+<template>
+  <ContentWrap :title="t('echartDemo.echart')" :message="t('echartDemo.echartDes')">
+    <ElRow :gutter="20" justify="space-between">
+      <ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-20px">
+          <Echart :options="pieOptions" :height="300" />
+        </ElCard>
+      </ElCol>
+      <ElCol :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
+        <ElCard shadow="hover" class="mb-20px">
+          <Echart :options="barOptions" :height="300" />
+        </ElCard>
+      </ElCol>
+      <ElCol :span="24">
+        <ElCard shadow="hover" class="mb-20px">
+          <Echart :options="lineOptions" :height="350" />
+        </ElCard>
+      </ElCol>
+      <ElCol :span="24">
+        <ElCard shadow="hover" class="mb-20px">
+          <Echart :options="wordOptions" :height="300" />
+        </ElCard>
+      </ElCol>
+    </ElRow>
+  </ContentWrap>
+</template>

+ 32 - 0
src/views/Components/Watermark.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElButton } from 'element-plus'
+import { useWatermark } from '@/hooks/web/useWatermark'
+import { computed, onBeforeUnmount } from 'vue'
+import { useAppStore } from '@/store/modules/app'
+
+const appStore = useAppStore()
+
+const title = computed(() => appStore.getTitle)
+
+const { setWatermark, clear } = useWatermark()
+
+const { t } = useI18n()
+
+onBeforeUnmount(() => {
+  clear()
+})
+</script>
+
+<template>
+  <ContentWrap :title="t('watermarkDemo.watermark')">
+    <ElButton type="primary" @click="setWatermark(title)">
+      {{ t('watermarkDemo.createdWatermark') }}
+    </ElButton>
+    <ElButton type="danger" @click="clear">{{ t('watermarkDemo.clearWatermark') }}</ElButton>
+    <ElButton type="warning" @click="setWatermark(`${title}-new`)">
+      {{ t('watermarkDemo.resetWatermark') }}
+    </ElButton>
+  </ContentWrap>
+</template>

+ 2 - 2
src/views/Dashboard/Analysis.vue

@@ -17,7 +17,7 @@ const { t } = useI18n()
 
 
 const loading = ref(true)
 const loading = ref(true)
 
 
-const pieOptionsData = reactive<EChartsOption>(pieOptions)
+const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
 
 
 // 用户来源
 // 用户来源
 const getUserAccessSource = async () => {
 const getUserAccessSource = async () => {
@@ -100,7 +100,7 @@ getAllApi()
     <ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
     <ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
       <ElCard shadow="hover" class="mb-20px">
       <ElCard shadow="hover" class="mb-20px">
         <ElSkeleton :loading="loading" animated>
         <ElSkeleton :loading="loading" animated>
-          <Echart :options="pieOptions" :height="300" />
+          <Echart :options="pieOptionsData" :height="300" />
         </ElSkeleton>
         </ElSkeleton>
       </ElCard>
       </ElCard>
     </ElCol>
     </ElCol>

+ 125 - 0
src/views/Dashboard/echarts-data.ts

@@ -1,4 +1,5 @@
 import { EChartsOption } from 'echarts'
 import { EChartsOption } from 'echarts'
+import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useI18n } from '@/hooks/web/useI18n'
 
 
 const { t } = useI18n()
 const { t } = useI18n()
@@ -183,3 +184,127 @@ export const radarOption: EChartsOption = {
     }
     }
   ]
   ]
 }
 }
+
+export const wordOptions: EChartsWordOption = {
+  series: [
+    {
+      type: 'wordCloud',
+      gridSize: 2,
+      sizeRange: [12, 50],
+      rotationRange: [-90, 90],
+      shape: 'pentagon',
+      width: 600,
+      height: 400,
+      drawOutOfBound: true,
+      textStyle: {
+        color: function () {
+          return (
+            'rgb(' +
+            [
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160)
+            ].join(',') +
+            ')'
+          )
+        }
+      },
+      emphasis: {
+        textStyle: {
+          shadowBlur: 10,
+          shadowColor: '#333'
+        }
+      },
+      data: [
+        {
+          name: 'Sam S Club',
+          value: 10000,
+          textStyle: {
+            color: 'black'
+          },
+          emphasis: {
+            textStyle: {
+              color: 'red'
+            }
+          }
+        },
+        {
+          name: 'Macys',
+          value: 6181
+        },
+        {
+          name: 'Amy Schumer',
+          value: 4386
+        },
+        {
+          name: 'Jurassic World',
+          value: 4055
+        },
+        {
+          name: 'Charter Communications',
+          value: 2467
+        },
+        {
+          name: 'Chick Fil A',
+          value: 2244
+        },
+        {
+          name: 'Planet Fitness',
+          value: 1898
+        },
+        {
+          name: 'Pitch Perfect',
+          value: 1484
+        },
+        {
+          name: 'Express',
+          value: 1112
+        },
+        {
+          name: 'Home',
+          value: 965
+        },
+        {
+          name: 'Johnny Depp',
+          value: 847
+        },
+        {
+          name: 'Lena Dunham',
+          value: 582
+        },
+        {
+          name: 'Lewis Hamilton',
+          value: 555
+        },
+        {
+          name: 'KXAN',
+          value: 550
+        },
+        {
+          name: 'Mary Ellen Mark',
+          value: 462
+        },
+        {
+          name: 'Farrah Abraham',
+          value: 366
+        },
+        {
+          name: 'Rita Ora',
+          value: 360
+        },
+        {
+          name: 'Serena Williams',
+          value: 282
+        },
+        {
+          name: 'NCAA baseball tournament',
+          value: 273
+        },
+        {
+          name: 'Point Break',
+          value: 265
+        }
+      ]
+    }
+  ]
+}