|
|
@@ -0,0 +1,249 @@
|
|
|
+<template>
|
|
|
+ <div style="border: 1px solid #ccc">
|
|
|
+ <Toolbar
|
|
|
+ style="border-bottom: 1px solid #ccc"
|
|
|
+ :editor="editorRef"
|
|
|
+ :defaultConfig="toolbarConfig"
|
|
|
+ mode="simple"
|
|
|
+ />
|
|
|
+ <Editor
|
|
|
+ style="height: 500px; overflow-y: hidden;"
|
|
|
+ v-model="valueHtml"
|
|
|
+ :defaultConfig="editorConfig"
|
|
|
+ mode="simple"
|
|
|
+ @onDestroyed="handleDestroyed"
|
|
|
+ @onChange="handleChange"
|
|
|
+ @onCreated="handleCreated"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+<script lang="ts" setup>
|
|
|
+import '@wangeditor/editor/dist/css/style.css' // 引入 css
|
|
|
+import {onBeforeUnmount, onMounted, ref, shallowRef} from 'vue'
|
|
|
+import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
|
|
|
+import {IEditorConfig} from '@wangeditor/editor'
|
|
|
+import {Session} from "/@/utils/storage";
|
|
|
+
|
|
|
+
|
|
|
+// 编辑器实例,必须用 shallowRef
|
|
|
+const editorRef = shallowRef()
|
|
|
+
|
|
|
+const emit = defineEmits(['update:modelValue'])
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 内容 HTML
|
|
|
+const valueHtml = ref('<p>hello</p>')
|
|
|
+
|
|
|
+// 模拟 ajax 异步获取内容
|
|
|
+onMounted(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ valueHtml.value = '<p>模拟 Ajax 异步设置内容</p>'
|
|
|
+ }, 1500)
|
|
|
+})
|
|
|
+
|
|
|
+const toolbarConfig = {}
|
|
|
+
|
|
|
+const editorConfig: Partial<IEditorConfig> = { // TS 语法
|
|
|
+ MENU_CONF: {},
|
|
|
+ placeholder: '请输入内容...'
|
|
|
+}
|
|
|
+
|
|
|
+let url = import.meta.env.VITE_API_URL;
|
|
|
+if (!url) {
|
|
|
+ url = `${location.origin}/admin/`;
|
|
|
+}
|
|
|
+let at = Session.get('accessToken')
|
|
|
+editorConfig.MENU_CONF['uploadImage'] = {
|
|
|
+ fieldName: 'file',
|
|
|
+ server: `${url}file/upload`,
|
|
|
+ // 自定义增加 http header
|
|
|
+ headers: {
|
|
|
+ accessToken: at
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传之前触发
|
|
|
+ onBeforeUpload(file: File) { // TS 语法
|
|
|
+ // onBeforeUpload(file) { // JS 语法
|
|
|
+ // file 选中的文件,格式如 { key: file }
|
|
|
+ return file
|
|
|
+
|
|
|
+ // 可以 return
|
|
|
+ // 1. return file 或者 new 一个 file ,接下来将上传
|
|
|
+ // 2. return false ,不上传这个 file
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传进度的回调函数
|
|
|
+ onProgress(progress: number) { // TS 语法
|
|
|
+ // onProgress(progress) { // JS 语法
|
|
|
+ // progress 是 0-100 的数字
|
|
|
+ console.log('progress', progress)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 单个文件上传成功之后
|
|
|
+ onSuccess(file: File, res: any) { // TS 语法
|
|
|
+ // onSuccess(file, res) { // JS 语法
|
|
|
+ console.log(`${file.name} 上传成功`, res)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 单个文件上传失败
|
|
|
+ onFailed(file: File, res: any) { // TS 语法
|
|
|
+ // onFailed(file, res) { // JS 语法
|
|
|
+ console.log(`${file.name} 上传失败`, res)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传错误,或者触发 timeout 超时
|
|
|
+ onError(file: File, err: any, res: any) { // TS 语法
|
|
|
+ // onError(file, err, res) { // JS 语法
|
|
|
+ console.log(`${file.name} 上传出错`, err, res)
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+// 组件销毁时,也及时销毁编辑器
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ const editor = editorRef.value
|
|
|
+ if (editor == null) return
|
|
|
+ editor.destroy()
|
|
|
+})
|
|
|
+
|
|
|
+const handleCreated = (editor) => {
|
|
|
+ editorRef.value = editor // 记录 editor 实例,重要!
|
|
|
+}
|
|
|
+
|
|
|
+const handleChange = (editor) => {
|
|
|
+ emit('update:modelValue', editor.getHtml())
|
|
|
+}
|
|
|
+
|
|
|
+const handleDestroyed = (editor) => {
|
|
|
+ console.log('destroyed', editor)
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+export default {
|
|
|
+ name: 'rich-text',
|
|
|
+ props: {
|
|
|
+ value: String
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ content: '',
|
|
|
+ editor: {},
|
|
|
+ toolId: '',
|
|
|
+ editorId: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ value(val) {
|
|
|
+ if (val) {
|
|
|
+ if (val !== this.content) {
|
|
|
+ this.setContent(val);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.setContent('');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ content(val) {
|
|
|
+ this.$emit('input', val);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.toolId = this.randomString(12)
|
|
|
+ this.editorId = this.randomString(12)
|
|
|
+ this.content = this.value
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.editor = new wangEditor(this.$refs[this.toolId], this.$refs[this.editorId])
|
|
|
+ this.editor.customConfig.onchange = (html) => {
|
|
|
+ // 监控变化,同步更新到 textarea
|
|
|
+ this.content = html
|
|
|
+ }
|
|
|
+ //配置图片上传服务器接口
|
|
|
+ this.editor.customConfig.uploadImgServer = process.env.VUE_APP_BASE_API + '/file/upload/customizeDirUpload'
|
|
|
+ // 文件名
|
|
|
+ this.editor.customConfig.uploadFileName = 'file'
|
|
|
+ // 配置上传图片请求头部
|
|
|
+ // this.editor.customConfig.uploadImgHeaders = {}
|
|
|
+ // 上传图片钩子函数
|
|
|
+ this.editor.customConfig.uploadImgHooks = {
|
|
|
+ before: function(xhr, editor, files) {
|
|
|
+ // 图片上传之前触发
|
|
|
+ // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,files 是选择的图片文件
|
|
|
+
|
|
|
+ // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
|
|
|
+ // return {
|
|
|
+ // prevent: true,
|
|
|
+ // msg: '放弃上传'
|
|
|
+ // }
|
|
|
+ },
|
|
|
+ success: function(xhr, editor, result) {
|
|
|
+ // 图片上传并返回结果,图片插入成功之后触发
|
|
|
+ // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果
|
|
|
+ // alert('成功')
|
|
|
+ },
|
|
|
+ fail: function(xhr, editor, result) {
|
|
|
+ // 图片上传并返回结果,但图片插入错误时触发
|
|
|
+ // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果
|
|
|
+ },
|
|
|
+ error: function(xhr, editor) {
|
|
|
+ // 图片上传出错时触发
|
|
|
+ // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象
|
|
|
+ },
|
|
|
+ timeout: function(xhr, editor) {
|
|
|
+ // 图片上传超时时触发
|
|
|
+ // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象
|
|
|
+ },
|
|
|
+
|
|
|
+ // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置
|
|
|
+ // (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错)
|
|
|
+ customInsert: function(insertImg, result, editor) {
|
|
|
+ // 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
|
|
|
+ // insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
|
|
|
+
|
|
|
+ // 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片:
|
|
|
+ var url = result.data
|
|
|
+ insertImg(url)
|
|
|
+
|
|
|
+ // result 必须是一个 JSON 格式字符串!!!否则报错
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.editor.create()
|
|
|
+ this.editor.txt.html(this.value)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 生成随机字符串id
|
|
|
+ randomString(len) {
|
|
|
+ len = len || 32
|
|
|
+ let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /!** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****!/
|
|
|
+ let maxPos = $chars.length
|
|
|
+ let pwd = ''
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
|
|
|
+ }
|
|
|
+ return 'a' + pwd
|
|
|
+ },
|
|
|
+ setContent(val) {
|
|
|
+ this.editor.txt.html(val)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}*/
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.toolbar {
|
|
|
+ border: 1px solid #ccc;
|
|
|
+}
|
|
|
+
|
|
|
+.text {
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ min-height: 200px;
|
|
|
+}
|
|
|
+</style>
|