| | |
| | | }, |
| | | "dependencies": { |
| | | "@element-plus/icons-vue": "2.0.10", |
| | | "@tinymce/tinymce-vue": "^4.0.5", |
| | | "@vueup/vue-quill": "1.2.0", |
| | | "@vueuse/core": "9.5.0", |
| | | "@wangeditor/editor": "^5.1.18", |
| | | "@wangeditor/editor-for-vue": "^5.1.12", |
| | | "@wangeditor/plugin-upload-attachment": "^1.1.0", |
| | | "axios": "0.27.2", |
| | | "echarts": "5.4.0", |
| | | "element-plus": "2.2.27", |
| | |
| | | "nprogress": "0.2.0", |
| | | "pinia": "2.0.22", |
| | | "quill": "^2.0.0-dev.3", |
| | | "tinymce": "^5.10.2", |
| | | "vue": "3.2.45", |
| | | "vue-cropper": "1.0.3", |
| | | "vue-quill-editor": "^3.0.6", |
| | | "vue-router": "4.1.4" |
| | | "vue-router": "4.1.4", |
| | | "wangeditor5-for-vue3": "^0.1.0" |
| | | }, |
| | | "devDependencies": { |
| | | "@vitejs/plugin-vue": "3.1.0", |
对比新文件 |
| | |
| | | <template> |
| | | <div style="height: 600px"> |
| | | <we-editor |
| | | toolbar-class="toolbar" |
| | | editable-class="editable" |
| | | toolbar-style="border: 1px solid #d9d9d9" |
| | | editable-style="border: 1px solid #d9d9d9" |
| | | :toolbar-option="toolbar" |
| | | :editable-option="editable" |
| | | :toolbar-reloadbefore="onToolbarReloadBefore" |
| | | :editable-reloadbefore="onEditableReloadBefore" |
| | | v-model="formData.jarr" |
| | | v-model:json="formData.jstr" |
| | | v-model:html="formData.html" |
| | | /> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | import '@wangeditor/editor/dist/css/style.css' |
| | | |
| | | import { WeEditor, useWangEditor } from 'wangeditor5-for-vue3' |
| | | import {defineComponent, onBeforeUnmount, ref, shallowReactive, shallowRef, watch} from 'vue' |
| | | import {ElMessage} from "element-plus"; |
| | | import {getToken} from "@/utils/auth"; |
| | | import {nextTick} from 'vue' |
| | | export default defineComponent( { |
| | | name: "wangeditor", |
| | | components: { WeEditor }, |
| | | props: { |
| | | propData :String |
| | | }, |
| | | setup(prop) { |
| | | //编辑器配置 |
| | | const editableOption = { |
| | | config:{ |
| | | placeholder:"请在这里输入内容", |
| | | hoverbarKeys: { |
| | | attachment: { |
| | | menuKeys: ['downloadAttachment'], // “下载附件”菜单 |
| | | }, |
| | | }, |
| | | MENU_CONF:{ |
| | | // 配置默认字号 |
| | | fontSize:{ |
| | | fontSizeList: [ |
| | | // 元素支持两种形式 |
| | | // 1. 字符串; |
| | | // 2. { name: 'xxx', value: 'xxx' } |
| | | '16px', |
| | | '20px', |
| | | { name: '26px', value: '26px' }, |
| | | '40px', |
| | | ] |
| | | }, |
| | | // 配置上传图片 |
| | | uploadImage:{ |
| | | // 请求路径 |
| | | server: import.meta.env.VITE_APP_BASE_API + "/system/common/uploadFile", |
| | | // 后端接收的文件名称 |
| | | fieldName: "file", |
| | | maxFileSize: 1 * 1024 * 1024, // 1M |
| | | // 上传的图片类型 |
| | | allowedFileTypes: ["image/*"], |
| | | // 小于该值就插入 base64 格式(而不上传),默认为 0 |
| | | base64LimitSize: 10 * 1024, // 10MB |
| | | // 自定义插入返回格式【后端返回的格式】 |
| | | customInsert(res, insertFn) { |
| | | if(res.code != 200){ |
| | | ElMessage.error("上传文件失败,"+res.message) |
| | | return |
| | | } |
| | | const url = import.meta.env.VITE_APP_BASE_API + "/" +res.data.path |
| | | insertFn(url) |
| | | }, |
| | | // 携带的数据 |
| | | meta: { |
| | | Authorization: getToken() |
| | | }, |
| | | // 将 meta 拼接到 url 参数中,默认 false |
| | | metaWithUrl: true, |
| | | // 单个文件上传成功之后 |
| | | onSuccess(file, res) { |
| | | if(res.code == 200){ |
| | | ElMessage.success(`${file.name} 上传成功`) |
| | | return |
| | | }else { |
| | | ElMessage.warning(`${file.name} 上传出了点异常`) |
| | | return |
| | | } |
| | | // console.log(`${file.name} 上传成功`, res) |
| | | //ElMessage.success(`${file.name} 上传成功`, res) |
| | | }, |
| | | // 单个文件上传失败 |
| | | onFailed(file, res) { |
| | | console.log(res) |
| | | ElMessage.error(`${file.name} 上传失败`) |
| | | }, |
| | | // 上传错误,或者触发 timeout 超时 |
| | | onError(file, err, res) { |
| | | console.log(err, res) |
| | | ElMessage.error(`${file.name} 上传出错`) |
| | | }, |
| | | }, |
| | | // 上传附件 |
| | | uploadAttachment: { |
| | | server: import.meta.env.VITE_APP_BASE_API + "/system/common/uploadFile", |
| | | fieldName: 'file', |
| | | maxFileSize: 100 * 1024 * 1024, // 100M |
| | | // 携带的数据 |
| | | meta: { |
| | | Authorization: getToken() |
| | | }, |
| | | // 成功回调 |
| | | onSuccess(file, res) { |
| | | if (res.errno === 0) { |
| | | ElMessage.success(`${file}附件上传成功`) |
| | | } |
| | | }, |
| | | // 失败回调 |
| | | onFailed(file, res) { |
| | | if (res.errno === 1) { |
| | | ElMessage.success(`${file}附件上传失败,` + res.message) |
| | | } |
| | | }, |
| | | // 上传成功后,用户自定义插入文件 |
| | | customInsert(res, file, insertFn) { |
| | | console.log('customInsert', res) |
| | | const url = import.meta.env.VITE_APP_BASE_API + "/" +res.data.path |
| | | if (!url) throw new Error(`url is empty`) |
| | | // 插入附件到编辑器 |
| | | insertFn(`${file.name}`, url) |
| | | }, |
| | | // 插入到编辑器后的回调 |
| | | onInsertedAttachment(elem) { |
| | | console.log("elem",elem) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 菜单栏配置 |
| | | const toolbarOption = { |
| | | mode: 'simple', // 指定简介模式 |
| | | config:{ |
| | | toolbarKeys:[ |
| | | "fontSize",'header1', 'header2', 'header3','header4','|', |
| | | 'blockquote',"code","codeBlock",'|', |
| | | 'bold', 'underline', 'italic', 'through', 'color', 'bgColor', 'clearStyle', '|', |
| | | 'bulletedList', 'numberedList', 'todo', 'justifyLeft','justifyCenter', 'justifyRight', '|', |
| | | 'insertLink', |
| | | { |
| | | key: 'group-image', |
| | | title: '图片', |
| | | iconSvg: "<svg viewBox=\"0 0 1024 1024\"><path d=\"M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z\"></path></svg>", |
| | | menuKeys: ['insertImage', 'uploadImage'] |
| | | }, |
| | | "insertTable", |
| | | "|", |
| | | "undo","redo" |
| | | ], |
| | | // 插入哪些菜单 |
| | | insertKeys: { |
| | | index: 27, // 自定义插入的位置 |
| | | keys: ['uploadAttachment'], // “上传附件”菜单 |
| | | }, |
| | | } |
| | | } |
| | | |
| | | |
| | | // 防抖时长。当会触发重载的配置项发生变化 365ms 后,编辑器会重载 |
| | | const reloadDelary = 365 |
| | | |
| | | // 对于上面的三个对象,经过 useWangEditor 处理后,返回的 editable 和 toolbar 分别对应编辑器和菜单栏的配置项 |
| | | const { editable, toolbar ,syncContent,clearContent } = useWangEditor( |
| | | editableOption, |
| | | toolbarOption, |
| | | reloadDelary, |
| | | { |
| | | delay: 3000, // 无操作 3s 后才会同步表单数据 |
| | | config: { |
| | | placeholder: '表单提交前使用 syncContent API 强制同步数据,确保数据不被丢失', |
| | | }, |
| | | } |
| | | ) |
| | | // 获取数据 |
| | | const formData = shallowReactive({ html: '' }) |
| | | |
| | | // 监听父组件传来的参数 |
| | | watch(prop, (val) => { |
| | | // 监听从父组件接收的所有参数,只要有参数变化就会触发 |
| | | console.log('props接收的值', val) |
| | | formData.html = prop.propData |
| | | }, { |
| | | immediate:true |
| | | }) |
| | | |
| | | function submit() { |
| | | // 强制同步 v-model 数据 |
| | | syncContent() |
| | | // 表单验证 |
| | | if(formData.html!=''){ |
| | | this.$emit('childFn',formData.html) |
| | | }else { |
| | | // ElMessage.error("请在编辑器内编写内容...") |
| | | } |
| | | } |
| | | function setData(val) { |
| | | console.log("val",val) |
| | | } |
| | | |
| | | function clear() { |
| | | clearContent() |
| | | } |
| | | |
| | | |
| | | // 在可编辑的重新加载之前 |
| | | function onEditableReloadBefore(inst) { |
| | | console.log(inst) |
| | | console.log('editable 即将重载: ' + new Date().toLocaleString()) |
| | | } |
| | | |
| | | // 在工具栏上重新加载之前 |
| | | function onToolbarReloadBefore(inst) { |
| | | console.log(inst) |
| | | console.log('toolbar 即将重载: ' + new Date().toLocaleString()) |
| | | } |
| | | return { editable, toolbar, formData, submit, clear, prop, onEditableReloadBefore, onToolbarReloadBefore } |
| | | }, |
| | | }) |
| | | </script> |
| | | <style> |
| | | /*工具栏样式*/ |
| | | .toolbar{ |
| | | border: 1px solid #d9d9d9;margin-bottom: 10px; |
| | | } |
| | | /*编辑器样式*/ |
| | | .editable{ |
| | | margin-top: -11px; |
| | | border: 1px solid #d9d9d9; |
| | | height: 500px; |
| | | //width: 50%; |
| | | //margin: 30px auto 150px auto; |
| | | background-color: #fff; |
| | | box-shadow: 0 2px 10px rgb(0 0 0 / 12%); |
| | | //border: 1px solid #e8e8e8; |
| | | } |
| | | </style> |
| | |
| | | // 字典标签组件 |
| | | import DictTag from '@/components/DictTag' |
| | | |
| | | import { Boot } from '@wangeditor/editor' |
| | | import attachmentModule from '@wangeditor/plugin-upload-attachment' |
| | | |
| | | Boot.registerModule(attachmentModule) |
| | | const app = createApp(App) |
| | | |
| | | // 全局方法挂载 |
| | |
| | | <el-input v-model.trim="state.noticeForm.noticeTitle" v-if="!isReview" ></el-input> |
| | | <span v-else>{{state.noticeForm.noticeTitle}}</span> |
| | | </el-form-item> |
| | | <div style="margin: 0 0 15px 30px" v-if="!isReview"> |
| | | <el-upload |
| | | v-model:file-list="fileList" |
| | | class="upload-demo" |
| | | action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" |
| | | multiple |
| | | :on-preview="handlePreview" |
| | | :on-remove="handleRemove" |
| | | :before-remove="beforeRemove" |
| | | :limit="3" |
| | | > |
| | | <el-button type="primary">附件上传</el-button> |
| | | </el-upload> |
| | | </div> |
| | | <el-form-item v-else label="附件:" > |
| | | <div class="file"> |
| | | <el-link v-for="(item,index) in state.noticeForm.fileList" type="primary" :key="index" :underline="false"> |
| | | <i class="el-icon-paperclip" style="margin-right: 5px;color: #1e6abc"></i>{{item.fileName}} |
| | | </el-link> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item v-if="!isReview" style="margin-left: -80px" prop="noticeContent"> |
| | | <!-- <tinymce v-model="state.noticeForm.noticeContent"></tinymce>--> |
| | | <editor ref="myQuillEditor" v-model="state.noticeForm.noticeContent" :height="300"></editor> |
| | | |
| | | <el-form-item label="公告内容:" v-if="showEditor" prop="noticeContent"> |
| | | <we-editor ref="myEditor" :propData="state.noticeForm.noticeContent" @childFn="getEditorData" /> |
| | | </el-form-item> |
| | | <el-form-item label="公告内容:" v-else> |
| | | <div class="ql-container ql-snow" style="height: 300px;width: 100%;margin-top: 10px;" > |
| | | <div class="ql-container ql-snow" style="height: 500px;width: 100%;margin-top: 10px;" > |
| | | <div class="ql-editor"> |
| | | <div v-html="state.noticeForm.noticeContent"></div> |
| | | <div v-html="state.noticeForm.noticeContent" @click="showFile($event)"></div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | |
| | | </template> |
| | | <script setup> |
| | | import {reactive, ref, toRefs} from 'vue' |
| | | import WeEditor from "@/components/WeEditor/index.vue"; |
| | | import Editor from "@/components/Editor/index.vue"; |
| | | import {ElMessage} from "element-plus"; |
| | | import {addNotice, editNotice, getNoticeDetail} from "@/api/backManage/notice"; |
| | | import axios from "axios"; |
| | | import {getToken} from "@/utils/auth"; |
| | | |
| | | |
| | | |
| | | |
| | | const emit = defineEmits(["getList"]); |
| | | |
| | | const dialogVisible = ref(false); |
| | | const title = ref(""); |
| | | const noticeRef = ref(); |
| | | const fileList = ref([]); |
| | | const myQuillEditor = ref(); |
| | | const myEditor = ref(); |
| | | const isReview = ref(false); |
| | | const showEditor = ref(true); |
| | | const state = reactive({ |
| | | noticeForm: { |
| | | id: '', |
| | | noticeTitle: '', |
| | | noticeContent: '', |
| | | fileList: [] |
| | | }, |
| | | formRules:{ |
| | | noticeTitle: [{ required: true, message: '请填写公告标题', trigger: 'blur' }], |
| | |
| | | |
| | | const openDialog = async (type, value) => { |
| | | isReview.value = false; |
| | | showEditor.value = false |
| | | title.value = type === 'add' ? '新增' : type ==='edit' ? '编辑' : '查看' ; |
| | | if(type === 'edit' || type === 'review') { |
| | | const param = { |
| | |
| | | }else{ |
| | | ElMessage.warning(res.message) |
| | | } |
| | | |
| | | } |
| | | if(type === 'review') { |
| | | showEditor.value = false |
| | | isReview.value = true; |
| | | } |
| | | if(type === 'edit' || type === 'add') { |
| | | showEditor.value = true; |
| | | isReview.value = false; |
| | | } |
| | | if(type === 'add'){ |
| | | reset() |
| | | state.noticeForm.noticeContent = " "; |
| | | state.noticeForm.noticeTitle = " "; |
| | | } |
| | | dialogVisible.value = true; |
| | | } |
| | | |
| | | const getEditorData = (val) =>{ |
| | | state.noticeForm.noticeContent = val; |
| | | } |
| | | |
| | | const showFile = (e) => { |
| | | if(e.target.nodeName === 'A'){ |
| | | console.log("e",e) |
| | | e.preventDefault(); |
| | | const file = { |
| | | fileUrl: e.target.href, |
| | | fileName: e.target.download |
| | | } |
| | | axios.get( file.fileUrl,{ |
| | | headers: |
| | | { |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization':getToken(), |
| | | }, |
| | | responseType: 'blob' |
| | | } |
| | | ).then(res=>{ |
| | | if (res) { |
| | | const link = document.createElement('a') |
| | | let blob = new Blob([res.data],{type: res.data.type}) |
| | | link.style.display = "none"; |
| | | link.href = URL.createObjectURL(blob); // 创建URL |
| | | link.setAttribute("download", file.fileName); |
| | | document.body.appendChild(link); |
| | | link.click(); |
| | | document.body.removeChild(link); |
| | | } else { |
| | | this.$message.error('获取文件失败') |
| | | } |
| | | this.handleClose(); |
| | | }) |
| | | } |
| | | } |
| | | const onSubmit = async () => { |
| | | myEditor.value.submit(); |
| | | const valid = await noticeRef.value.validate(); |
| | | if(valid){ |
| | | if(state.noticeForm.noticeContent === "<p><br></p>"){ |
| | | ElMessage({ |
| | | type: 'warning', |
| | | message: '请输入公告内容' |
| | | }); |
| | | return; |
| | | } |
| | | if(title.value === '新增'){ |
| | | const param = { |
| | | content: state.noticeForm.noticeContent, |
| | |
| | | } |
| | | emit("getList") |
| | | reset(); |
| | | // myEditor.value.clear(); |
| | | noticeRef.value.clearValidate(); |
| | | dialogVisible.value = false; |
| | | }else if(title.value === '编辑') { |
| | | const nowDate = new Date() |
| | | const param = { |
| | | id: state.noticeForm.id, |
| | | content: state.noticeForm.noticeContent, |
| | |
| | | } |
| | | emit("getList") |
| | | reset(); |
| | | // myEditor.value.clear(); |
| | | noticeRef.value.clearValidate(); |
| | | dialogVisible.value = false; |
| | | } |
| | |
| | | } |
| | | |
| | | const handleClose = () => { |
| | | if(title ==="新增"|| title ==='编辑'){ |
| | | myEditor.value.clear(); |
| | | showEditor.value=false |
| | | } |
| | | |
| | | noticeRef.value.clearValidate(); |
| | | dialogVisible.value = false; |
| | | } |
| | |
| | | noticeContent: '' |
| | | } |
| | | } |
| | | const handleRemove = (file, fileList) => { |
| | | console.log(file, fileList); |
| | | } |
| | | const handlePreview = (uploadFile) => { |
| | | console.log(uploadFile) |
| | | } |
| | | const beforeRemove = (file, fileList) => { |
| | | return this.$confirm(`确定移除 ${ file.name }?`); |
| | | } |
| | | |
| | | |
| | | defineExpose({ |
| | | openDialog |