Vue - Vue3 封装组件(更新中...)

组件目录

  • 1、穿梭框 DataTransfer
  • 2、按钮组
  • 3、顶部导航菜单
  • 4、弹窗组件
  • 5、特色按钮
  • 5、PDF Word Excel 预览插件
  • 6、编辑器

1、穿梭框 DataTransfer

  • 示例
    Vue - Vue3 封装组件(更新中...)_第1张图片
  • 应用示例
<DataTransfer 
v-model="value" 
	:data="data" 
	:titles="['左侧数据','右侧数据']" 
	:buttonTexts="['To Left', 'To Right']" 
	:search="true"/>
  • 组件代码
<script setup>
import {ArrowLeft, ArrowRight, Search} from "@element-plus/icons-vue";
import {computed, getCurrentInstance, onMounted, ref, watch, watchEffect} from "vue";

const emit = defineEmits(["update:modelValue"])
const props = defineProps({
  data: {
    type: Array,
    default: []
  },
  titles: {
    type: Array,
    default: ['Source', 'Target']
  },
  modelValue: {
    type: Array,
    default: []
  },
  buttonTexts: {
    type: Array,
    default: ['', '']
  },
  search: {
    type: Boolean,
    default: false
  }
})
const {proxy} = getCurrentInstance()

/**目标数据*/
const targetValue = computed({
  get() {
    return props.data
        .filter(item => props.modelValue.includes(item.key))
        .filter(item => {
          if (targetSearchValue.value === '') return true
          return item.label.toLowerCase().includes(targetSearchValue.value.toLowerCase())
        })
        .filter(item => item !== '')
  },
  set(value) {
    emit("update:modelValue", value)
  }
})

/**源数据数据*/
const sourceData = computed(() => {
  if (sourceSearchValue.value !== '') {
    return props.data.filter(item => {
      return item.label.toLowerCase().includes(sourceSearchValue.value.toLowerCase())
    })
  } else {
    return props.data
  }
})

/**左表头统计数据*/
const sourceStatistics = computed(() => `${getSelectData()?.leftSelectData?.length}/${sourceData.value.length}`)

/**右表头统计数据*/
const targetStatistics = computed(() => `${getSelectData()?.rightSelectData?.length}/${targetValue.value.length}`)

const allData = ref()
const selectedData = ref()
const leftDisabled = ref(true)
const rightDisabled = ref(true)
const sourceSearchValue = ref('')
const targetSearchValue = ref('')

/**获取选择行数据
 * @return {Object}
 * */
const getSelectData = () => {
  const leftSelectData = allData.value?.getSelectionRows()
  const rightSelectData = selectedData.value?.getSelectionRows()
  return {leftSelectData, rightSelectData}
}

/**控制按钮禁用状态*/
watchEffect(() => {
  leftDisabled.value = getSelectData()?.rightSelectData?.length === 0
  rightDisabled.value = getSelectData()?.leftSelectData?.length === 0
      || getSelectData()?.leftSelectData?.filter(item => !props.modelValue.includes(item.key))?.length === 0
})


/**监控Target、Source数据,控制Source数据选中状态*/
watch([targetValue, sourceData], () => {
  Promise.resolve(1).then(() => {
    checkData()
  })
})

/**当页面初始加载时控制数据选中状态*/
onMounted(() => {
  // 禁止文字选中
  document.onselectstart = function () {
    return false
  }
  checkData()
})

/**选中Source当中与Target对应的数据*/
const checkData = () => {
  sourceData.value.filter(item => props.modelValue.includes(item.key)).forEach(i => {
    allData.value?.toggleRowSelection(i, true)
  })
}

/**Target 减少数据*/
const toLeft = () => {
  getSelectData().rightSelectData.map(item => item.key).forEach(i => {
    const _index = props.modelValue.indexOf(i)
    if (_index !== -1) props.modelValue.splice(_index, 1)
  })
}

/**Target 增加数据*/
const toRight = () => {
  getSelectData().leftSelectData.filter(item => !props.modelValue.includes(item.key)).forEach(i => {
    props.modelValue.push(i.key)
  })
  rightDisabled.value = true
}

/**根据Target数据判断Source对应数据是否可选中*/
const canSelectable = (row) => {
  return !props.modelValue.includes(row.key)
}

/**Source 行点击事件*/
const leftSourceRowClick = (row) => {
  if (!props.modelValue.includes(row.key)) {
    allData.value?.toggleRowSelection(row, undefined)
  }
}

/**Target 行点击事件*/
const rightTargetRowClick = (row) => {
  selectedData.value?.toggleRowSelection(row, undefined)
}

script>

<template>
  <div class="transfer-area">
    <div class="source-area">
      <el-table ref="allData" :data="sourceData" @row-click="leftSourceRowClick" cell-mouse-enter="hover">
        <el-table-column type="selection" :selectable="canSelectable"/>
        <el-table-column>
          <template #header>
            <div class="table-head">
              <div>{{ titles[0] }}div>
              <div class="statistics">{{ sourceStatistics }}div>
            div>
          template>
          <template #default="scope"><p class="change-style">{{ scope.row.label }}p>template>
        el-table-column>
      el-table>
      <el-input v-show="search" v-model="sourceSearchValue" class="input-with-select" placeholder="输入以查询"
                :prefix-icon="Search"
                clearable/>
    div>

    <el-button type="primary" @click="toLeft" :disabled="leftDisabled">
      <el-icon class="el-icon--left">
        <ArrowLeft/>
      el-icon>
      {{ buttonTexts[0] }}
    el-button>
    <el-button type="primary" @click="toRight" :disabled="rightDisabled">
      {{ buttonTexts[1] }}
      <el-icon class="el-icon--right">
        <ArrowRight/>
      el-icon>
    el-button>

    <div class="target-area">
      <el-table ref="selectedData" :data="targetValue" @row-click="rightTargetRowClick">
        <el-table-column type="selection"/>
        <el-table-column>
          <template #header>
            <div class="table-head">
              <div>{{ titles[1] }}div>
              <div class="statistics">{{ targetStatistics }}div>
            div>
          template>
          <template #default="scope"><p class="change-style">{{ scope.row.label }}p>template>
        el-table-column>
      el-table>
      <el-input v-show="search" v-model="targetSearchValue" class="input-with-select" placeholder="输入以查询"
                :prefix-icon="Search"
                clearable/>
    div>
  div>
template>

<style scoped lang="less">
.transfer-area {
  width: 100%;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

.source-area, .target-area {
  width: 30%;
}

.el-table {
  --el-table-border: none;
  border: #E6E8EB 1px solid;
  border-radius: 7px;
  width: 100%;
  height: 60vh;
}

.el-table::v-deep(th.el-table__cell) {
  background: #F5F7FA;
}

.el-table::v-deep(.el-table__header-wrapper) {
  background: #F5F7FA;
  border-bottom: #E6E8EB 1px solid;
}

.el-table::v-deep(.el-table__inner-wrapper::before) {
  background: none;
}

.input-with-select {
  margin: 5px 0 5px 0;
}

.change-style {
  cursor: pointer;
  padding: 0;
  margin: 0;
}

.change-style:hover {
  color: #409EFF;
}

.table-head {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.statistics {
  color: #409EFF;
  font-size: smaller;
  font-weight: lighter;
}
style>

2、按钮组

  • 示例
    Vue - Vue3 封装组件(更新中...)_第2张图片
    在这里插入图片描述
  • 应用示例
<template>
<ButtonGroup :buttonArray="Buttons"/>
template>
<script setup>
import {getCurrentInstance, ref} from "vue";
const {proxy} = getCurrentInstance()
const Buttons=ref([
	{
		name: '文件管理',
		    type: "primary",
		    plain: true,
		    round: false,
		    circle: false,
		    color: "#cb966a",
		    data: [
			    {
			    	text: '下载文件',
                    icon: 'el-icon-download',
                    onClick: () => {
                    	proxy.$message.warrning('下载文件')
					}
			    }{
			    	text: '删除文件',
                    icon: 'el-icon-delete',
                    onClick: () => {
                    	proxy.$message.warrning('删除文件')
					}
				}
		    ]
	}
])
script>
  • 组件代码
<script setup>
import {ArrowDown} from "@element-plus/icons-vue";

const props = defineProps({
  buttonArray: {
    type: Array,
    default: []
  },
  style: {
    type: String,
    default: "margin-left:10px;"
  }
})

function onClick(callback) {
  callback()
}
script>

<template>
  <el-dropdown size="small" v-for="buttonGroup in props.buttonArray" :style="props.style">
    <el-button :type="buttonGroup.type" :plain="buttonGroup.plain" :round="buttonGroup.round"
               :circle="buttonGroup.circle" :color="buttonGroup.color">{{ buttonGroup.name }}
      <el-icon class="el-icon--right">
        <arrow-down/>
      el-icon>
    el-button>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item v-for="button in buttonGroup.data">
          <div @click="onClick(button.onClick)">
            <i :class="button.icon">i>
            {{ button.text }}
          div>
        el-dropdown-item>
      el-dropdown-menu>
    template>
  el-dropdown>

template>

3、顶部导航菜单

  • 示例
    在这里插入图片描述
  • 应用示例
<template>
	<HeaderMenu :menus="menus" class="header-class"/>
template>
<script setup>
import {ref} from "vue";
const menus = ref([
      {
        text: '头部导航栏',
        icon:'el-icon-s-order',
        items: [
          {
            itemText: '选项一', onClick: () => toBacklogWorkFlow()
          }
        ]
      }
    ])
script>
<style lang="less" scoped>
.header-class:hover{
  background: #1a81ea;
}
style>
  • 组件代码
<script setup>
import {ArrowDown} from "@element-plus/icons-vue";

const props = defineProps({
  menus: {
    type: Array,
    default: []
  },
  menuType: {
    type: String,
    default: "lineMenu"
  },
  style:{
    type: String,
    default: ""
  }
})
const itemClick = (onClick) => {
  onClick()
}
script>

<template>
  <div v-if="menuType==='lineMenu'">
    <el-dropdown v-for="menu in props.menus" class="menu-class" :class="props.class" :style="props.style">
      <span>
        <i :class="menu.icon">i>
        {{ menu.text }}
        <el-icon class="el-icon--right">
          <arrow-down/>
        el-icon>
      span>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item v-for="item in menu.items" @click="itemClick(item.onClick)">{{ item.itemText }}
          el-dropdown-item>
        el-dropdown-menu>
      template>
    el-dropdown>
  div>
  <div v-else-if="menuType==='buttonMenu'">
    <el-dropdown v-for="menu in props.menus">
      <el-button :type="menu.buttonType">
        {{ menu.text }}
        <el-icon class="el-icon--right">
          <arrow-down/>
        el-icon>
      el-button>
      <template #dropdown>
        <el-dropdown-menu>
          <el-dropdown-item v-for="item in menu.items" @click="itemClick(item.onClick)">{{ item.text }}
          el-dropdown-item>
        el-dropdown-menu>
      template>
    el-dropdown>
  div>
template>

<style scoped lang="less">
.menu-class {
  color: white;
  height: 100%;
  line-height: 59px;
  padding: 0 20px;
  font-size: 16px;
}
style>

4、弹窗组件

  • 示例
    在这里插入图片描述
  • 应用示例
<DialogBox :lazy="true" v-model="activityPop" title="测试" :width="1000" :draggable="false"
         :padding="5" icon="el-icon-user">
  <InputArea v-model="person.name" @change="changePerson" title="人员选择">
    123
  InputArea>
  <template #footer>
    <div>
      <el-button type="warning" size="small" class="el-icon-check" @click="change">改变姓名el-button>
      <el-button type="primary" size="small" class="el-icon-close" @click="close">关闭el-button>
    div>
  template>
DialogBox >
  • 组件代码
<template>
  <div class="dialog-area">
    <el-dialog v-model="vmodel" :close-on-click-modal="false" :close-on-press-escape="false" :width="width"
      :fullscreen="fullscreen" :draggable="draggable" :modal="modal" :before-close="handleClose">
      <template #header>
        <i :class="icon">i> {{ title }}
        <button class="el-dialog__headerbtn" type="button" style="right: 35px; color: var(--el-color-info)" @click="handleFullScreen">
          <i class="el-icon el-icon-full-screen">i>
        button>
      template>
      <el-scrollbar :max-height="contentHeight">
        <div v-if="inited" style="min-height: 50px;" class="srcoll-content" :style="{ padding: padding + 'px' }">
          <slot name="content">slot>
          <slot>slot>
        div>
      el-scrollbar>
      <template #footer>
        <div class="dia-footer" v-if="footer">
          <slot name="footer">slot>
          <el-button type="primary" v-if="!footer" size="mini" @click="handleClose()"><i
              class="el-icon-close">i>关闭el-button>
        div>
      template>
    el-dialog>
  div>
template>

<script>
import { defineComponent, ref, watch, watchEffect } from 'vue';

export default defineComponent({
  props: {
    modelValue: false,
    lazy: {
      type: Boolean,
      default: false,
    },
    icon: {
      type: String,
      default: "el-icon-warning-outline",
    },
    title: {
      type: String,
      default: "",
    },
    height: {
      type: Number,
      default: 200,
    },
    width: {
      type: Number,
      default: 650,
    },
    padding: {
      type: Number,
      default: 16,
    },
    modal: {
      /遮罩层
      type: Boolean,
      default: true,
    },
    draggable: {
      //可拖拽功能
      type: Boolean,
      default: false,
    },
    mask: {
      type: Boolean,
      default: true,
    },
    onModelClose: {
      type: Function,
      default: (iconClick) => {
        return true;        
      }
    },
    footer:{ //是否显示底部按钮
      type: Boolean,
      default: true
    }

  },
  setup(props, context) {
    const clientHeight = document.body.clientHeight * 0.95 - 60;
    const inited = ref(true);
    const vmodel = ref(false);
    const footer = ref(false);
    const top = ref(100);
    vmodel.value = props.modelValue;
    footer.value = !!context.slots.footer;
    const contentHeight = ref(200);
    contentHeight.value = props.height;
    const handleClose = (done, iconClose) => {
      let result = props.onModelClose(!!iconClose);
      if (result === false) return;
      vmodel.value = false;
      context.emit("update:modelValue", false);
      done && done();
    };
    const calcHeight = (val) => {
      contentHeight.value = clientHeight - 30;
      return clientHeight / -2 + 'px';
    };
    top.value = calcHeight();
    watch(
      () => props.modelValue,
      (newVal, oldVal) => {
        vmodel.value = newVal;
      }
    );
    watch(
      () => props.height,
      (newVal, oldVal) => {
        top.value = calcHeight();
      }
    );
    const fullscreen=ref(false);
    const handleFullScreen=()=> {
      fullscreen.value = !fullscreen.value;
      context.emit("fullscreen", fullscreen.value);
    }
    return {
      handleClose,
      inited,
      vmodel,
      footer,
      top,
      calcHeight,
      contentHeight,
      fullscreen,
      handleFullScreen
    };
  }
});
script>

<style lang="less" scoped>
.dia-footer {
  text-align: right;
  width: 100%;
  border-top: 1px solid #e2e2e2;
  text-align: right;
  padding: 6px 8px;
}
style>

<style scoped lang="less">
.dialog-area ::v-deep(.el-overlay-dialog) {
  display: flex !important;
}

.dialog-area ::v-deep(.el-dialog) {
  margin: auto;
}

.dialog-area ::v-deep(.el-dialog) {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
}

.dialog-area ::v-deep(.el-dialog__header) {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  padding: 0px 13px;
  line-height: 53px;
  border-bottom: 1px solid #e6e6e6;
  height: 50px;
  color: rgb(79, 79, 79);
  font-weight: bold;
  font-size: 14px;
  margin: 0;
}

.dialog-area ::v-deep(.el-dialog__footer),
.dialog-area ::v-deep(.el-dialog__body) {
  padding: 0;
}

.dialog-area ::v-deep(.el-dialog__headerbtn) {
  top: 0;
  padding-top: 8px;
  height: 50px;
  width: 0;
  padding-right: 30px;
  padding-left: 5px;
}
style>

5、特色按钮

  • 示例1
    Vue - Vue3 封装组件(更新中...)_第3张图片
    Vue - Vue3 封装组件(更新中...)_第4张图片
    Vue - Vue3 封装组件(更新中...)_第5张图片
  • 应用示例1
<ColorButton type="1" @click="" prefix_icon="el-icon-info">
  HOVER ME
ColorButton>
<ColorButton type="2" @click="" prefix_icon="el-icon-info">
  HOVER ME
ColorButton>
  • 示例2
    在这里插入图片描述

Vue - Vue3 封装组件(更新中...)_第6张图片

  • 应用示例2
<ColorButton type="1" @click="">
  <template #visible>
    HOVER ME
  template>
  <template #invisible>
    EXPORT
    <i class="el-icon-top-right">i>
  template>
ColorButton>
  • 组件代码
<script setup>
import {computed} from "vue";

defineEmits(['click'])
const props = defineProps({
  type: {
    type: String,
    default: '1'
  },
  suffix_icon: {
    type: String,
    default: ''
  },
  prefix_icon: {
    type: String,
    default: ''
  },
  doubleSided: {
    type: Boolean,
    default: false
  },
  round: {
    type: Boolean,
    default: false
  },
  circle: {
    type: Boolean,
    default: false
  }
})
const borderType = computed(() => {
  if (props.round) return 'round'
  if (props.circle) return 'circle'
  return 'normal'
})
script>

<template>
  <div :class="['border-'+borderType]">
    <button v-if="type==='1'" class="button_1" @click="$emit('click')">
      <i v-if="prefix_icon" :class="prefix_icon" class="prefix_i">i>
      <slot/>
      <i v-if="suffix_icon" :class="suffix_icon" class="suffix_i">i>
      <span v-if="doubleSided" class="button_1__visible">
        <slot name="visible"/>
      span>
      <span v-if="doubleSided" class="button_1__invisible">
        <div>
          <slot name="invisible"/>
        div>
      span>
    button>

    <button v-if="type==='2'" class="button_2" @click="$emit('click')">
      <i v-if="prefix_icon" :class="prefix_icon" class="prefix_i">i>
      <slot/>
      <i v-if="suffix_icon" :class="suffix_icon" class="suffix_i">i>
      <div class="hoverEffect">
        <div>div>
      div>
    button>
  div>
template>

<style scoped lang="less">
.prefix_i {
  margin-right: 5px;
}

.suffix_i {
  margin-left: 5px;
}

.border-round {
  --border: 10rem;

  .button_1 {
    border-radius: var(--border);
  }

  .button_2 {
    border-radius: var(--border);
  }

  .button_1:before {
    border-radius: var(--border);
  }
}

.border-normal {
  --border: 5px;

  .button_1 {
    border-radius: var(--border);
  }

  .button_2 {
    border-radius: var(--border);
  }

  .button_1:before {
    border-radius: var(--border);
  }
}

.border-circle {
  --border: 50%;
  --size: 20px;
  --padding: 15px;

  .button_1 {
    border-radius: var(--border);
    padding: var(--padding);
    width: var(--size);
    height: var(--size);
  }

  .button_2 {
    border-radius: var(--border);
    padding: var(--padding);
    width: var(--size);
    height: var(--size);
  }

  .button_1:before {
    border-radius: var(--border);
    padding: var(--padding);
  }
}

// region button_1
.button_1 {
  text-decoration: none;
  position: relative;
  border: none;
  font-family: inherit;
  color: #fff;
  padding: 3px 20px 3px 20px;
  margin: 3px;
  text-align: center;
  background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
  background-size: 300%;
  border-radius: 5px;
  z-index: 1;
}

.button_1 > * {
  display: inline-block;
  transition: all ease-in-out .5s;
}

.button_1__visible {
  text-align: center;
}

.button_1__invisible {
  width: 100%;
  margin: auto;
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  left: 0;
  top: -200%;
}

.button_1:hover {
  animation: ani 8s linear infinite;
  border: none;
  opacity: 0.8;
}

.button_1:hover .button_1__visible {
  transform: translateY(200%);
  opacity: 0;
}

.button_1:hover .button_1__invisible {
  top: 0;
  bottom: 0;
}

.button_1:focus {
  outline: none;
}

@keyframes ani {
  0% {
    background-position: 0;
  }

  100% {
    background-position: 400%;
  }
}

.button_1:before {
  content: '';
  position: absolute;
  top: -5px;
  left: -5px;
  right: -5px;
  bottom: -5px;
  z-index: -1;
  background: linear-gradient(90deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
  background-size: 400%;
  border-radius: 5px;
  transition: 1s;
}

.button_1:hover::before {
  filter: blur(10px);
}

.button_1:active {
  background: linear-gradient(32deg, #03a9f4, #f441a5, #ffeb3b, #03a9f4);
}

// endregion

// region button_2
.button_2 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  border: 0;
  position: relative;
  overflow: hidden;
  border-radius: 10rem;
  transition: all 0.02s;
  font-weight: bold;
  color: rgb(37, 37, 37);
  z-index: 0;
  box-shadow: 0 0 7px -5px rgba(0, 0, 0, 0.5);
}

.button_2:hover {
  background: rgb(193, 228, 248);
  color: rgb(33, 0, 85);
}

.button_2:active {
  transform: scale(0.97);
}

.hoverEffect {
  position: absolute;
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.hoverEffect div {
  background: rgb(222, 0, 75);
  background: linear-gradient(90deg, rgba(222, 0, 75, 1) 0%, rgba(191, 70, 255, 1) 49%, rgba(0, 212, 255, 1) 100%);
  border-radius: 40rem;
  width: 10rem;
  height: 10rem;
  transition: 0.4s;
  filter: blur(20px);
  animation: effect infinite 3s linear;
  opacity: 0.5;
}

.button_2:hover .hoverEffect div {
  width: 8rem;
  height: 8rem;
}

@keyframes effect {

  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

// endregion
style>

5、PDF Word Excel 预览插件

  • 插件安装
npm install vue-demi vue-demi @vue-office/docx @vue-office/excel @vue-office/pdf

@vue-office插件安装方式详见博客Vue-Vue 集成 pdf word excel 预览功能

  • 应用示例
<script setup>
import DocumentPreview from "@/components/DocumentPreview.vue";
import {ref} from "vue";
import {ElMessage} from "element-plus";

// 文件路径
const url = 'http://XXXXX.docx'

function onError(e) {
  ElMessage.warning(e)
}

function onRendered() {
  console.log('预览成功')
}

script>

<template>
  <DocumentPreview :url="url" @onError="onError" @onRendered="onRendered"/>
template>
  • 组件代码
<script setup>
import VueOfficePdf from "@vue-office/pdf";
import VueOfficeExcel from "@vue-office/excel";
import VueOfficeDocx from "@vue-office/docx";
import {computed, getCurrentInstance, shallowRef} from "vue";
import * as url from "url";

const emit = defineEmits(['onRendered', 'onError'])
const props = defineProps({
  url: {
    type: String,
    default: ''
  }
})
const {proxy} = getCurrentInstance()
const componentMap = shallowRef({
  pdf: VueOfficePdf,
  xlsx: VueOfficeExcel,
  docx: VueOfficeDocx
})
const type = computed(() => {
  const documentType = props.url.split('.').pop()
  if (!componentMap.value[documentType]) {
    proxy.$message.warning('仅支持 pdf、xlsx、docx 格式的文件预览')
    return
  }
  return documentType
})

/** 文件预览成功时调用 * */
const onRendered = () => {
  emit('onRendered')
}

/** 文件预览失败时调用 **/
const onError = (e) => {
  emit('onError', e)
}

script>

<template>
  <component :is="componentMap[type]" :src="url" @rendered="onRendered" @error="onError"/>
template>

<style scoped lang="less">
style>

6、编辑器

  • 示例
    Vue - Vue3 封装组件(更新中...)_第7张图片
  • 插件安装
npm install @wangeditor/editor  @wangeditor/editor-for-vue@next --save

wangEditor 插件安装方式详见博客 Vue-Vue3 集成编辑器功能

-应用示例

<script setup>
import Editor from "@/components/Editor.vue";
import {ref} from "vue";

const editor = ref()
const editorValue = ref('')

const print = () => {
  console.log(editor.value.getHtml())
  console.log(editor.value.getText().split(/\n/))
}
script>

<template>
  <el-button type="primary" @click="print">测试el-button>
  <Editor ref="editor" v-model="editorValue" placeholder="请输入内容..."/>
template>
  • 组件代码
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import {DomEditor} from '@wangeditor/editor'
import {computed, onBeforeUnmount, ref, shallowRef} from 'vue'
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'

const emit = defineEmits(["update:modelValue"])
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  placeholder: {
    type: String,
    default: '请输入...'
  }
})
const inputValue = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit("update:modelValue", value)
  }
})
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
const mode = ref('default')
const test = ref(false)
const editorConfig = {placeholder: props.placeholder}
// 默认工具栏配置
const toolbarConfig = {}

/** 排除菜单组,写菜单组 key 的值即可 */
toolbarConfig.excludeKeys = [
  'group-image',
  'group-video',
  'fullScreen'
]

/** 组件销毁时,也及时销毁编辑器 */
onBeforeUnmount(() => {
  const editor = editorRef.value
  if (editor == null) return
  editor.destroy()
})

/** 记录 editor 实例,重要!*/
const handleCreated = (editor) => {
  editorRef.value = editor
}

/** 获取HTML格式内容方法 */
const getHtml = () => {
  return editorRef.value.getHtml()
}

/** 获取原始文本内容方法 */
const getText = () => {
  return editorRef.value.getText()
}

/** 暴露方法 */
defineExpose({getHtml, getText})
script>

<template>
  <div style="border: 1px solid #ccc">
    <Toolbar
        style="border-bottom: 1px solid #ccc"
        :editor="editorRef"
        :defaultConfig="toolbarConfig"
        :mode="mode"
    />
    <Editor
        style="height: 500px; overflow-y: hidden;"
        v-model="inputValue"
        :defaultConfig="editorConfig"
        :mode="mode"
        @onCreated="handleCreated"
    />
  div>
template>

<style scoped lang="less">
style>

你可能感兴趣的:(笔记,前端)