通用管理后台组件库-14-图表和富文本组件

1 阅读2分钟

图表组件和富文本组件

说明:图表组件使用Echarts做二次封装,富文本组件使用Vditor插件做二次封装。

1.实现效果

image.png

image.png

2.图表组件Charts.vue

<template>
  <div ref="chartRef" :style="chartStyle"></div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts'
import type { ECharts, EChartsOption } from 'echarts'
import type { CSSProperties } from 'vue'

interface ChartsProps {
  option: EChartsOption
  height?: number | string
  width?: number | string
  autoresize?: boolean
}
const props = withDefaults(defineProps<ChartsProps>(), {
  autoresize: true
})

const attrsStyle = useAttrs()

const emits = defineEmits(['init'])

// shallowRef浅层响应式
const chartRef = shallowRef()

const chartInstance = shallowRef<ECharts>()

const chartStyle = computed(() => {
  // 获取父组件传递的样式
  const style = (attrsStyle.style || {}) as CSSProperties
  return {
    height: props.height
      ? typeof props.height === 'number'
        ? props.height + 'px'
        : props.height
      : '400px',
    width: props.width
      ? typeof props.width === 'number'
        ? props.width + 'px'
        : props.width
      : '100%',
    ...style
  }
})

function initChart() {
  if (chartRef.value) {
    // 初始化echarts实例
    chartInstance.value = echarts.init(chartRef.value)
    // 使用指定的配置项和数据显示图表
    chartInstance.value.setOption(props.option as EChartsOption)
    // 导出echarts实例
    emits('init', chartInstance.value)
  }
}
function resizeChart() {
  if (chartInstance.value) {
    chartInstance.value.resize()
  }
}
watch(
  () => props.option,
  () => {
    if (chartInstance.value) {
      chartInstance.value.setOption(props.option as EChartsOption)
    }
  },
  { deep: true }
)
const fn = useThrottleFn(resizeChart, 50)
onMounted(() => {
  initChart()
  // 监听窗口变化
  if (props.autoresize) {
    window.addEventListener('resize', fn)
  }
})
onUnmounted(() => {
  // 销毁实例
  chartInstance.value && chartInstance.value?.dispose()
  // 移除监听
  if (props.autoresize) {
    window.removeEventListener('resize', fn)
  }
})
</script>

<style scoped></style>

demo文件charts.vue

  <!-- <VueEcharts :option="option" autoresize theme="dark" :height="300" /> -->
  <Charts :option="option" autoresize :height="300" @init="handleInit" />
  <el-button @click="handleClick">更换数据</el-button>
</template>

<script setup lang="ts">
import type { EChartsOption } from 'echarts'

definePage({
  meta: {
    title: '基础图表',
    icon: 'mdi:bee'
  }
})

const option = ref<EChartsOption>({
  title: {
    text: 'Traffic Sources',
    left: 'center'
  },
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c} ({d}%)'
  },
  legend: {
    orient: 'vertical',
    left: 'left',
    data: ['Direct', 'Email', 'Ad Networks', 'Video Ads', 'Search Engines']
  },
  series: [
    {
      name: 'Traffic Sources',
      type: 'pie',
      radius: '55%',
      center: ['50%', '60%'],
      data: [
        { value: 335, name: 'Direct' },
        { value: 310, name: 'Email' },
        { value: 234, name: 'Ad Networks' },
        { value: 135, name: 'Video Ads' },
        { value: 1548, name: 'Search Engines' }
      ],
      emphasis: {
        itemStyle: {
          shadowBlur: 10,
          shadowOffsetX: 0,
          shadowColor: 'rgba(0, 0, 0, 0.5)'
        }
      }
    }
  ]
})
const handleClick = () => {
  if (option.value.series && option.value.series[0] && option.value.series[0].data) {
    option.value.series[0].data[0].value = 100
  }
}
const handleInit = (instance) => {
  console.log('🚀 ~ handleInit ~ instance:', instance)
}

</script>

<style scoped></style>

3.富文本组件

类型文件types.d.ts

/// <reference types="vditor/dist/types" />

export type VditorOptions = IOptions

export interface EditorProps {
  options: VditorOptions
}

封装的组件Editor.vue

<template>
  <div ref="editorRef"></div>
</template>

<script setup lang="ts">
import Vditor from 'vditor'
import type { EditorProps, VditorOptions } from './types.d'
import 'vditor/dist/index.css'

const props = defineProps<EditorProps>()
const emits = defineEmits(['init'])

// 编辑器容器
const editorRef = ref()
// 编辑器实例
const editorInstance = shallowRef<Vditor>()

// 输入框内容
const modelValue = defineModel()

// 记录历史值
const history = ref('')

// 默认配置
const defaultOptions: VditorOptions = {
  rtl: false,
  mode: 'ir',
  value: '',
  debugger: false,
  typewriterMode: true,
  height: 'auto',
  minHeight: 400,
  width: 'auto',
  placeholder: '',
  fullscreen: { index: 90 },
  counter: {
    enable: false, // 默认值: false
    type: 'markdown' // 默认值: 'markdown'
  },
  link: {
    isOpen: true // 默认值: true
  },
  image: {
    isPreview: true // 默认值: true
  },
  cache: { enable: true, id: Math.random().toString(16).slice(2) },
  lang: 'zh_CN',
  theme: 'classic',
  icon: 'ant',
  cdn: 'https://unpkg.com/vditor@3.9.6'
}

// 监听输入框内容变化
watch(modelValue, (newValue) => {
  // 输入的值是否和旧值相同
  const isCommon = `${newValue}` !== editorInstance.value?.getValue()
  // 给编辑器赋值
  if (editorInstance.value && newValue && isCommon) {
    editorInstance.value?.setValue(newValue + '')
  }
})

// 监听props.options的变化
// watch(
//   // 使用箭头函数获取props.options作为监听源
//   () => props.options,
//   // 当options发生变化时执行的回调函数
//   (newOptions) => {
//     // 保存当前编辑器的内容到历史记录
//     history.value = editorInstance.value?.getValue() || ''
//     // 销毁当前的编辑器实例
//     editorInstance.value?.destroy()
//     // 使用新的options重新初始化编辑器
//     initEditor(newOptions)
//   },
//   { deep: true }
// )
// 初始化编辑器
function initEditor(options) {
  // 编辑器异步渲染完成后的回调方法
  const defaultAfter = options?.after
  // 输入后触发的回调方法
  const defaultInput = options?.input
  // 初始化编辑器实例
  const instance = new Vditor(
    editorRef.value,
    Object.assign(defaultOptions, {
      ...options,
      after: () => {
        defaultAfter && defaultAfter()
        // 如果有历史记录,则将历史记录赋值给编辑器
        if(history.value) {
          instance.setValue(history.value, true)
        }
        // 编辑器渲染完成时,获取输入框内容
        modelValue.value = instance.getValue()
      },
      input: (md) => {
        defaultInput && defaultInput(md)
        // 编辑器内容变化时,获取输入框内容
        modelValue.value = md
      }
    })
  )

  // 赋值初始值
  modelValue.value = props.options?.value || ''
}
onMounted(() => {
  initEditor(props.options)
  emits('init', editorInstance.value)
})

onBeforeUnmount(() => {
  editorInstance.value?.destroy()
})
</script>

<style scoped></style>

demo组件editor.vue

<template>
  <Editor :options="{ value: '2323' }" v-model="editorValue"></Editor>
</template>

<script setup lang="ts">

definePage({
  meta: {
    title: '基础编辑器',
    icon: 'mdi:chart-box-plus-outline'
  }
})


const editorValue = ref('')
</script>

<style scoped></style>