Vue.js组件(5):自定义组件

news2025/2/24 3:23:19

1 介绍

        下面的所有组件全部基于VUE3 + TS + element plus编写,其中部分组件可能涉及到其他技术栈,会进行单独说明。

2 基础组件

2.1 表格操作组件TableToolButton

        此组件用于对表格进行增加、编辑、删除、导出操作。

2.1.1 组件属性

  • addVisible:是否显示添加按钮,默认为true。
  • editVisible:是否显示编辑按钮,默认为true。
  • deleteVisible:是否显示删除按钮,默认为true。
  • exportVisible:是否显示导出按钮,默认为true。
  • selectedArray:传递的数组,用于对编辑和删除做预处理,默认为空数组。

2.1.2 组件事件

  • toAdd:添加按钮触发事件,参数1:"新增";参数2:空对象
  • toEdit:编辑按钮触发事件,会对selectedArray长度进行校验。不等于1时触发警告。参数1:"编辑";参数2:选中的对象
  • toDelete:删除按钮触发事件,会对selectedArray数据进行处理,获取数组中所有的id属性值,然后使用','进行拼接。参数1:拼接后的字符串
  • toExport:导出按钮触发事件。

2.1.3 组件展示

<script setup lang="ts">
// 表格按钮组件
import { Delete, Download, Edit, Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'

const props = withDefaults(defineProps<{
  addVisible?: boolean,
  editVisible?: boolean,
  deleteVisible?: boolean,
  exportVisible?: boolean,
  selectedArray: any // 选中的数据
}>(), {
  addVisible: true,
  editVisible: true,
  deleteVisible: true,
  exportVisible: true
})

const emit = defineEmits(['toAdd', 'toEdit', 'toDelete', 'toExport'])
// 处理添加方法
const addFunction = () => {
  emit('toAdd', '新增', {})
}

// 处理编辑方法
const editFunction = () => {
  if (typeof props.selectedArray == 'undefined' || props.selectedArray.length !== 1) {
    ElMessage({
      message: '请选择一条数据进行编辑',
      type: 'warning'
    })
    return
  } // 将对象进行深拷贝
  emit('toEdit', '编辑', JSON.parse(JSON.stringify(props.selectedArray[0])))
}

// 处理删除方法
const deleteFunction = () => {
  if (props.selectedArray.length === 0) {
    ElMessage({
      message: '请选择一条数据进行删除',
      type: 'warning'
    })
    return
  }
  ElMessageBox.confirm(
    '确认要删除吗?',
    '重要提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).then(() => {
    // 将对象中的id取出组成新的数组,然后使用join拼接
    const ids = props.selectedArray.map((item: any) => item.id)
    emit('toDelete', ids.join(','))
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消删除'
    })
  })
}

const exportFunction = () => {
  emit('toExport')
}
</script>

<template>
  <div class="table-tool-button">
    <el-tooltip content="添加" v-if="addVisible" placement="top">
      <el-button type="success" :icon="Plus" @click="addFunction" circle />
    </el-tooltip>
    <el-tooltip content="编辑" v-if="editVisible" placement="top">
      <el-button type="primary" :icon="Edit" @click="editFunction" circle />
    </el-tooltip>
    <el-tooltip content="删除" v-if="deleteVisible" placement="top">
      <el-button type="danger" :icon="Delete" @click="deleteFunction" circle />
    </el-tooltip>
    <el-tooltip content="导出" v-if="exportVisible" placement="top">
      <el-button type="warning" :icon="Download" @click="exportFunction" circle />
    </el-tooltip>
  </div>
</template>

<style scoped>

</style>

2.2 上传图片组件UploadImage

         此组件封装了上传图片功能,并在上传图片前对文件类型以及大小进行校验,并且在切换图片后触发服务端删除图片方法。

2.2.1 组件属性

  • imageName:图片名称,用于回显图片。

2.2.2 组件事件

  • success:上传图片后触发事件,参数1:上传的图片名。

2.2.3 组件展示

        上传接口:

import api from '../axios'

// 下载图片地址
//export const downloadImageUrl = 'http://192.168.2.21:8080/image/downloadImage?name='
export const downloadImageUrl = '/api/image/downloadImage?name='

// 上传文件
export function uploadImageApi(file: File) {
  const formData = new FormData()
  formData.append('file', file)
  return api.post('/image/uploadImage', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
}

// 下载图片
export function downloadImageApi(imageName: string) {
  return api.get('/image/downloadImage', { params: { name: imageName }, responseType: 'blob' })
}

// 删除图片
export function deleteImageApi(imageName: string) {
  return api.delete('/image/removeImage', { params: { name: imageName } })
}

        组件代码:

<script setup lang="ts">
// 上传图片组件
import { onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'
import { deleteImageApi, downloadImageApi, uploadImageApi } from '@/assets/api/file'

// 图片名
const props = defineProps<{
  imageName: string
}>()

// 上传成功回调方法,返回图片地址
const emit = defineEmits(['success'])

// 图片名
const imageName = ref(props.imageName)
// 图片地址 img标签src的值
const imageUrl = ref()

// 上传图片
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
  if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
    ElMessage.error('图片必须是jpg或png格式!')
    return false
  } else if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error('图片大小不能超过2MB!')
    return false
  }

  uploadImageApi(rawFile).then((res) => {
    if (res.data.code === 200) {
      ElMessage.success('上传成功')
      imageName.value = res.data.data
      echoImage()
      emit('success', imageName.value)
    }
  })
  return false // 返回false,阻止默认上传行为
}

// 回显图片
const echoImage = () => {
  if (imageName.value != '') {
    downloadImageApi(imageName.value).then((res) => {
      imageUrl.value = URL.createObjectURL(res.data)
    })
  }
}

// 删除图片
const beforeRemove = () => {
  if (imageName.value != '') {
    deleteImageApi(imageName.value).then((res) => {
      ElMessage.info(res.data.data)
    })
  }
}

onMounted(() => {
  echoImage()
})
</script>

<template>
  <!-- 上传图片 -->
  <el-upload
    class="avatar-uploader"
    action="#"
    accept="image/*"
    :show-file-list="false"
    :before-upload="beforeAvatarUpload"
    :before-remove="beforeRemove"
  >
    <img v-if="imageUrl" :src="imageUrl" class="avatar" alt="" />
    <el-icon v-else class="avatar-uploader-icon">
      <Plus />
    </el-icon>
  </el-upload>
</template>

<style scoped>
.avatar-uploader .avatar {
  width: 250px;
  height: 178px;
  display: block;
}

.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  text-align: center;
}
</style>

2.3 富文本编辑器WangEditor

        此组件用于在页面中使用富文本编辑器,需要在项目中进行下载安装。官网地址:wangEditor。Vue3安装命令:

yarn add @wangeditor/editor-for-vue@next
# 或者 npm install @wangeditor/editor-for-vue@next --save

        注意ts项目需要在env.d.ts文件里添加类型声明:

declare module "@wangeditor/editor-for-vue" {
  const Editor: any;
  const Toolbar: any;
  type IEditorConfig = any;
}

2.3.1 组件属性

  • valueHtml:编辑器里的内容,默认为空。
  • size:编辑器中内容长度,此长度包含html标签,默认值2000。
  • enableImg:是否开启向内容中插入图片功能,默认为false,注意如果开启请确保项目中已经引入了2.2.3章节中上传图片组件里的上传接口。

2.3.2 组件事件

  • save:当编辑器内容发生改变时触发,对内容长度进行校验,如果开启了插入图片工具则会对插入的图片进行校验,触发服务器端删除图片功能。参数1:编辑器中的内容。

2.3.3 组件展示

<script setup lang="ts">
//富文本编辑组件
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { deleteImageApi, downloadImageUrl, uploadImageApi } from '@/assets/api/file'

const props = withDefaults(defineProps<{
  valueHtml?: string // 内容
  size?: number // 内容长度
  enableImg?: boolean // 是否启用图片上传
}>(), {
  valueHtml: '',
  size: 2000,
  enableImg: false
})

const emit = defineEmits<{
  save: [valueHtml: string]
}>()

// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()

// 内容 HTML
const valueHtml = ref(props.valueHtml)

// 工具栏配置 默认屏蔽工具栏上的图片、视频按钮
const toolbarConfig = ref({
  excludeKeys: ['group-image', 'insertVideo']
})

// 向服务器插入图片集合
const insertImage = ref<string[]>([])

// 编辑器配置
const editorConfig = {
  placeholder: '输入内容...',
  MENU_CONF: {
    uploadImage: { // 上传图片的配置
      // 自定义上传
      async customUpload(file: File, insertFn: any) {
        if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
          ElMessage.info('图片必须是jpg或png格式!')
          return
        }
        if (file.size > 1024 * 1024 * 2) {
          ElMessage.info('图片大小不能超过2M!')
          return
        }
        // 上传图片
        uploadImageApi(file).then((res: any) => {
          // 上传成功后,获取图片地址,并插入到编辑器中
          if (res.data.code === 200) {
            insertFn(downloadImageUrl + res.data.data, '无法显示')
          }
        })
      }
    },
    insertImage: {
      onInsertedImage(imageNode: any | null) {
        if (imageNode == null) return
        const { src } = imageNode
        insertImage.value.push(src)
      }
    }
  }
}

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

// 编辑器创建完毕的回调,记录 editor 实例,重要!
const handleCreated = (editor: any) => {
  editorRef.value = editor
}

// 保存
const save = (editor: any) => {
  if (valueHtml.value.length > props.size) {
    ElMessageBox.alert('内容超出可存储范围,请删减内容!', '警告', {
      confirmButtonText: 'OK'
    })
  } else {
    // 获取编辑器中存在的图片
    const imageTag = editor.getElemsByType('image')
    // 获取删除的图片
    let deleteImageList: any[]
    if (imageTag.length > 0) {
      // 获取编辑器中不存在的图片
      const imageList = imageTag.map((item: any) => item.src)
      deleteImageList = insertImage.value.filter(item => !imageList.includes(item))
    } else {
      // 如果编辑器中没有图片则删除所有插入的图片
      deleteImageList = insertImage.value
      insertImage.value = []
    }
    // 删除图片
    if (deleteImageList.length > 0) {
      let deleteImageListStr = ''
      deleteImageList.forEach(item => {
        // 获取图片名
        deleteImageListStr += item.substring(item.indexOf('name=') + 5) + ','
      })
      deleteImageList.length = 0
      deleteImageApi(deleteImageListStr).then((res: any) => {
        if (res.data.code === 200) {
          ElMessage.success('删除图片成功')
        }
      })
    }
    emit('save', valueHtml.value)
  }
}

onMounted(() => {
  if (props.enableImg) {
    // 监听图片上传
    toolbarConfig.value = {
      excludeKeys: ['insertVideo']
    }
  }
})

</script>

<template>
  <div style="border: 1px solid #ccc;z-index: 1000">
    <Toolbar
      style="border-bottom: 1px solid #ccc"
      :editor="editorRef"
      :defaultConfig="toolbarConfig"
      mode="simple"
    />
    <Editor
      style="height: 400px; overflow-y: hidden;"
      v-model="valueHtml"
      :defaultConfig="editorConfig"
      mode="simple"
      @onCreated="handleCreated"
      @onChange="save"
    />
  </div>
</template>

<style scoped>

</style>

2.4 权限按钮组PermissionGroup

        此组件通用性不高,只是在编辑角色时对系统中权限列表展示做分类处理。

2.4.1 组件属性

  • permissionList:权限对象数组,必须。
  • echoPermissions:需要回显的权限数组,必须。

2.4.2 组件事件

  • update:permissions:按钮组选中状态发生改变时触发,返回选中的权限id数组。

2.4.3 组件展示

<script setup lang="ts">
// 权限分组组件,接受权限数据,根据对应菜单进行分组展示
import { computed, ref } from 'vue'

const props = defineProps<{
  permissionList: any[], // 权限数据
  echoPermissions: any[], // 回显选中的权限
}>()

// 定义事件,更新选中权限触发事件
const emits = defineEmits(['update:permissions'])

// 定义选中的权限
const selectedPermissions = ref(props.echoPermissions)

// 定义权限根据菜单分组展示
const permissionGroupByMenu = computed(() => {
  let map: any = {}
  let arr = props.permissionList
  for (let i = 0; i < arr.length; i++) {
    let ai = arr[i]
    if (!map[ai.menuNames]) {
      map[ai.menuNames] = [ai]
    } else {
      map[ai.menuNames].push(ai)
    }
  }
  let res: any = []
  Object.keys(map).forEach(key => {
    res.push({
      id: key,
      data: map[key]
    })
  })
  return res
})

</script>

<template>
  <div class="permission-group">
    <div style="margin-bottom: 10px" v-for="permissionGroup in permissionGroupByMenu">
      <label class="group-name">{{ permissionGroup.id }}:</label>
      <el-checkbox-group v-model="selectedPermissions" size="large">
        <el-checkbox-button
          @change="emits('update:permissions', selectedPermissions)"
          style="margin: 1px"
          v-for="permission in permissionGroup.data"
          :key="permission.id"
          :value="permission.id"
        >
          {{ permission.permissionDesc }}
        </el-checkbox-button>
      </el-checkbox-group>
    </div>
  </div>
</template>

<style scoped>
.permission-group {
  height: 360px;
  overflow-y: scroll;
}

.group-name {
  font-size: 16px;
  font-weight: bold;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2265511.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ctfhub技能树——disable_functions

LD_PRELOAD 来到首页发现有一句话直接就可以用蚁剑连接 根目录里有/flag但是不能看;命令也被ban了就需要绕过了 绕过工具在插件市场就可以下载 如果进不去的话 项目地址: #本地仓库;插件存放 antSword\antData\plugins 绕过选择 上传后我们点进去可以看到多了一个绕过的文件;…

【PCIe 总线及设备入门学习专栏 1.1 -- PCIe 基础知识 lane和link介绍】

文章目录 OverivewLane 和 LinkRC 和 RPPCIe controllerPCIE ControllerPHY模块 Inbound 和 OutboundPCIe transaction modelPIODMAP2P Overivew PCIe&#xff0c;即PCI-Express总线&#xff08;Peripheral Component Interconnect Express&#xff09;&#xff0c;是一种高速…

golang LeetCode 热题 100(动态规划)-更新中

爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a;输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2&…

分布式专题(10)之ShardingSphere分库分表实战指南

一、ShardingSphere产品介绍 Apache ShardingSphere 是一款分布式的数据库生态系统&#xff0c; 可以将任意数据库转换为分布式数据库&#xff0c;并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。Apache ShardingSphere 设计哲学为 Database Plus&#xff0c;旨在…

Vue 3.5 编写 ref 时,自动插入.Value

如果是 Vue 3.2 &#xff0c;那么可能用的是Volar

深度学习中的并行策略概述:2 Data Parallelism

深度学习中的并行策略概述&#xff1a;2 Data Parallelism 数据并行&#xff08;Data Parallelism&#xff09;的核心在于将模型的数据处理过程并行化。具体来说&#xff0c;面对大规模数据批次时&#xff0c;将其拆分为较小的子批次&#xff0c;并在多个计算设备上同时进行处…

OneCode:开启高效编程新时代——企业定制出码手册

一、概述 OneCode 的 DSM&#xff08;领域特定建模&#xff09;出码模块是一个强大的工具&#xff0c;它支持多种建模方式&#xff0c;并具有强大的模型转换与集成能力&#xff0c;能够提升开发效率和代码质量&#xff0c;同时方便团队协作与知识传承&#xff0c;还具备方便的仿…

《Web 应用项目开发:从构思到上线的全过程》

目录 一、引言 二、项目启动与需求分析 三、设计阶段 四、技术选型 五、开发阶段 六、测试阶段 七、部署与上线 八、维护与更新 九、总结 一、引言 在数字化浪潮席卷全球的当下&#xff0c;Web 应用如繁星般在互联网的苍穹中闪烁&#xff0c;它们形态各异&#xff0c…

中小学教室多媒体电脑安全登录解决方案

中小学教室多媒体电脑面临学生随意登录的问题&#xff0c;主要涉及到设备使用、网络安全、教学秩序等多个方面。以下是对这一问题的详细分析&#xff1a; 一、设备使用问题 1. 设备损坏风险 学生随意登录可能导致多媒体电脑设备过度使用&#xff0c;增加设备损坏的风险。不当…

Odoo 免费开源 ERP:通过 JavaScript 创建对话框窗口的技术实践分享

作者 | 老杨 出品 | 上海开源智造软件有限公司&#xff08;OSCG&#xff09; 概述 在本文中&#xff0c;我们将深入研讨如何于 Odoo 18 中构建 JavaScript&#xff08;JS&#xff09;对话框或弹出窗口。对话框乃是展现重要讯息、确认用户操作以及警示用户留意警告或错误的行…

OOP面向对象编程:类与类之间的关系

OOP面向对象编程&#xff1a;类与类之间的关系 三大关系&#xff1a;复合&#xff08;适配器设计模式&#xff09;、委托&#xff08;桥接设计模式&#xff09;、继承 8、1复合Composition has-a -> 适配器模式 一个类里面含有另一个类的对象 —> 复合关系 has-a 适配器设…

集成 jacoco 插件,查看单元测试覆盖率

文章目录 前言集成 jacoco 插件&#xff0c;查看单元测试覆盖率1. 添加pom2. 配置完成、执行扫描3. 执行结果4. 单元测试报告 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞…

下载运行Vue开源项目vue-pure-admin

git地址&#xff1a;GitHub - pure-admin/vue-pure-admin: 全面ESMVue3ViteElement-PlusTypeScript编写的一款后台管理系统&#xff08;兼容移动端&#xff09; 安装pnpm npm install -g pnpm # 国内 淘宝 镜像源 pnpm config set registry https://registry.npmmirror.com/…

创建用于预测序列的人工智能模型,设计模型架构。

上一篇&#xff1a;《创建用于预测序列的人工智能模型&#xff0c;设计数据集》 序言&#xff1a;在前一篇中&#xff0c;我们创建了用于训练人工智能模型的数据集。接下来&#xff0c;就要设计模型的架构了。其实&#xff0c;人工智能模型的开发关键并不在于代码量&#xff0…

ubuntu22.04安装PPOCRLabel

可使用的模型参考模型列表&#xff0c;ppocr版本这里PPOCR版本作为预训练模型&#xff1a; &#xff08;经常用放在这里&#xff09; 基础电脑配置&#xff1a; cunda12.4 ubuntu22.04系统 pytorch2.5.0 &#xff08;python3.10不能运行&#xff0c;python3.8我之前可以正…

Linux网络——TCP的运用

系列文章目录 文章目录 系列文章目录一、服务端实现1.1 创建套接字socket1.2 指定网络接口并bind2.3 设置监听状态listen2.4 获取新链接accept2.5 接收数据并处理&#xff08;服务&#xff09;2.6 整体代码 二、客户端实现2.1 创建套接字socket2.2 指定网络接口2.3 发起链接con…

江苏捷科云:可视化平台助力制造企业智能化管理

公司简介 江苏捷科云信息科技有限公司&#xff08;以下简称“捷科”&#xff09;是一家专注于云平台、云储存、云管理等产品领域的创新型企业&#xff0c;集研发、生产和销售于一体&#xff0c;致力于在网络技术领域打造尖端品牌。在推动制造业企业数字化转型的进程中&#xf…

消息队列(一)消息队列的工作流程

什么是消息队列 首先&#xff0c;代入一个场景&#xff0c;我现在做一个多系统的集成&#xff0c;分别有系统A、B、C、D四个系统&#xff0c;A系统因为使用产生了业务数据&#xff0c;B、C、D需要使用这些数据做相关的业务处理和运算&#xff0c;最基本的做法就是通过接口通信…

施耐德变频器ATV320系列技术优势:创新与安全并重

在工业自动化领域&#xff0c;追求高效、安全与智能已成为不可阻挡的趋势。施耐德变频器ATV320系列凭借其强大的设计标准和全球认证&#xff0c;成为能够帮助企业降低安装成本&#xff0c;提高设备性能的创新解决方案。 【全球认证&#xff0c;品质保障】ATV320 系列秉持施耐德…

WEB入门——文件上传漏洞

文件上传漏洞 一、文件上传漏洞 1.1常见的WebShell有哪些&#xff1f;1.2 一句话木马演示1.2 文件上传漏洞可以利用需满足三个条件1.3 文件上传导致的危害 二、常用工具 2.1 搭建upload-labs环境2.2 工具准备 三、文件上传绕过 3.1 客户端绕过 3.1.1 实战练习 &#xff1a;upl…