图表组件和富文本组件
说明:图表组件使用Echarts做二次封装,富文本组件使用Vditor插件做二次封装。
1.实现效果


2.图表组件Charts.vue
xml
<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
ini
/// <reference types="vditor/dist/types" />
export type VditorOptions = IOptions
export interface EditorProps {
options: VditorOptions
}
封装的组件Editor.vue
xml
<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
xml
<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>