init: 初始化 web_hgcg_admin 后台管理项目
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
<!--
|
||||
* @Author:
|
||||
* @Mail:
|
||||
* @Date: 2021-11-13 14:46:20
|
||||
* @LastEditTime: 2023-01-02 19:27:36
|
||||
* @LastEditors: Please set LastEditors
|
||||
* @FilePath: /web_admin/src/components/QuillEditor/index.vue
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<quill-editor
|
||||
v-model="content"
|
||||
ref="myQuillEditor"
|
||||
:options="editorOption"
|
||||
@ready="onEditorReady($event)"></quill-editor>
|
||||
<!-- el-uploader不好手动触发上传功能 -->
|
||||
<uploader
|
||||
:style="{ display: 'none' }"
|
||||
:options="{ ...options, target: '/api/web/file/uploadImg' }"
|
||||
class="uploader-example"
|
||||
@file-success="imageSuccess">
|
||||
<uploader-drop>
|
||||
<uploader-btn ref="editorUploadImg" :attrs="attrsImg"></uploader-btn>
|
||||
</uploader-drop>
|
||||
</uploader>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { handleVerAutoImg } from '@/utils/index.js'
|
||||
import 'quill/dist/quill.core.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import 'quill/dist/quill.bubble.css'
|
||||
import Quill from 'quill'
|
||||
const Link = Quill.import('formats/link')
|
||||
Link.PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel', 'yinseinner', 'blob']
|
||||
import { quillEditor } from 'vue-quill-editor'
|
||||
import { ImageDrop } from 'quill-image-drop-module'
|
||||
import ImageResize from 'quill-image-resize-module'
|
||||
Quill.register('modules/imageDrop', ImageDrop)
|
||||
Quill.register('modules/imageResize', ImageResize)
|
||||
|
||||
// Quill 默认 Image.sanitize 只允许 http/https/data,解密后的 blob: 会被清成 //:0
|
||||
const ImageFormat = Quill.import('formats/image')
|
||||
const originalImageSanitize = ImageFormat.sanitize
|
||||
ImageFormat.sanitize = function(url) {
|
||||
if (url && typeof url === 'string' && url.indexOf('blob:') === 0) {
|
||||
return url
|
||||
}
|
||||
return originalImageSanitize.call(this, url)
|
||||
}
|
||||
Quill.register(ImageFormat, true)
|
||||
|
||||
// 自定义字体类型
|
||||
var fonts = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'Times-New-Roman', 'sans-serif']
|
||||
var Font = Quill.import('formats/font')
|
||||
Font.whitelist = fonts // 将字体加入到白名单
|
||||
Quill.register(Font, true)
|
||||
|
||||
export default {
|
||||
name: 'Editor',
|
||||
components: {
|
||||
quillEditor
|
||||
},
|
||||
props: {
|
||||
textNode: {
|
||||
default: ''
|
||||
}
|
||||
|
||||
},
|
||||
computed: {
|
||||
currentDomain() {
|
||||
return this.domain || this.$store.getters.getSystemConfig.imgWebCdn || ''
|
||||
},
|
||||
options() {
|
||||
return {
|
||||
chunkSize: 1024 * 1024 * 30, // 切片大小
|
||||
method: 'application/json',
|
||||
testChunks: false,
|
||||
allowDuplicateUploads: false, // 是否可以重复选择
|
||||
query: {},
|
||||
singleFile: false, // 是否只能选择单项
|
||||
headers: {
|
||||
Authorization: this.$store.getters.token
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const _that = this
|
||||
return {
|
||||
content: '',
|
||||
imgPath: [],
|
||||
domain: this.$store.getters.getSystemConfig.imgWebCdn, // 不能暴露源站地址
|
||||
attrsImg: {
|
||||
accept: ['image/*']
|
||||
},
|
||||
trackId: undefined,
|
||||
editorOption: {
|
||||
placeholder: '请输入帖子内容',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{ header: 1 }, { header: 2 }], // custom button values
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
|
||||
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
|
||||
[{ direction: 'rtl' }], // text direction
|
||||
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
|
||||
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
['link', 'image'],
|
||||
['clean']
|
||||
],
|
||||
handlers: {
|
||||
// 自定义上传图片
|
||||
image: value => {
|
||||
_that.$refs.editorUploadImg.$el.childNodes[0].click()
|
||||
}
|
||||
}
|
||||
},
|
||||
// //配置富文本框图片大小调整功能
|
||||
imageDrop: true,
|
||||
imageResize: {
|
||||
displayStyles: {
|
||||
backgroundColor: 'black',
|
||||
border: 'none',
|
||||
color: 'white'
|
||||
},
|
||||
modules: ['Resize', 'DisplaySize', 'Toolbar']
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 暂存图片base64
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 与 ShowImg.buildImageUrl 保持一致
|
||||
buildImageUrl(path) {
|
||||
if (!path) return ''
|
||||
if (/^(blob:|https?:\/\/|data:)/.test(path)) return path
|
||||
const base = this.currentDomain || ''
|
||||
if (!base) return path
|
||||
const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
||||
return `${normalizedBase}${normalizedPath}`
|
||||
},
|
||||
/**
|
||||
* 回显:先粘贴原始 HTML,再按 ShowImg 方式逐张解密并写回 DOM
|
||||
*/
|
||||
async pasteAndDecryptImages(html) {
|
||||
const sourceHtml = html || ''
|
||||
if (!this.$refs.myQuillEditor || !this.$refs.myQuillEditor.quill) return
|
||||
const myeditor = this.$refs.myQuillEditor.quill
|
||||
myeditor.setContents([])
|
||||
if (sourceHtml) {
|
||||
myeditor.clipboard.dangerouslyPasteHTML(0, sourceHtml)
|
||||
}
|
||||
|
||||
const imgNodes = Array.from(myeditor.container.querySelectorAll('img'))
|
||||
for (const node of imgNodes) {
|
||||
// 已有 data-custom 说明业务侧已解密过,src 已是 blob
|
||||
const existingCustom = node.getAttribute('data-custom')
|
||||
if (existingCustom) continue
|
||||
|
||||
const originalSrc = node.getAttribute('src')
|
||||
if (!originalSrc || /^(blob:|data:)/.test(originalSrc)) continue
|
||||
|
||||
try {
|
||||
const blobUrl = await handleVerAutoImg(this.buildImageUrl(originalSrc))
|
||||
if (blobUrl) {
|
||||
node.setAttribute('data-custom', originalSrc)
|
||||
node.setAttribute('src', blobUrl)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('富文本图片解密失败:', originalSrc, e)
|
||||
}
|
||||
}
|
||||
this.content = myeditor.root.innerHTML
|
||||
},
|
||||
getdata() {
|
||||
const myeditor = this.$refs.myQuillEditor.quill
|
||||
|
||||
// 找到所有包含 data-custom 属性的 img 标签
|
||||
const imgNodes = myeditor.container.querySelectorAll('img[data-custom]')
|
||||
imgNodes.forEach((node) => {
|
||||
const dataCustomValue = node.getAttribute('data-custom') // 获取 data-custom 的值
|
||||
// 交换 src 和 data-custom 的值
|
||||
node.setAttribute('src', dataCustomValue)
|
||||
node.removeAttribute('data-custom') // 删除 data-custom 属性
|
||||
})
|
||||
|
||||
// 检查内容是否为空
|
||||
if (this.content === '<p><br></p>' || this.content.trim() === '') {
|
||||
this.content = '' // 将内容设置为空字符串
|
||||
} else {
|
||||
// 直接更新 this.content 中的内容
|
||||
this.content = myeditor.root.innerHTML // 获取编辑器内容
|
||||
}
|
||||
return this.content
|
||||
},
|
||||
onEditorReady() {
|
||||
if (!this.textNode) return
|
||||
this.pasteAndDecryptImages(this.textNode)
|
||||
},
|
||||
extractImageSources(htmlString) {
|
||||
const imgSrcRegex = /<img[^>]+src="([^">]+)"/g
|
||||
const srcArray = []
|
||||
let match
|
||||
|
||||
while ((match = imgSrcRegex.exec(htmlString)) !== null) {
|
||||
srcArray.push(match[1])
|
||||
}
|
||||
return srcArray
|
||||
},
|
||||
imageSuccess(rootFile, file, message) {
|
||||
const { code, data } = JSON.parse(message)
|
||||
if (code === 200) {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = (e) => {
|
||||
const base64Data = e.target.result // 获取转换后的 Data URL
|
||||
|
||||
const myeditor = this.$refs.myQuillEditor.quill
|
||||
const index = myeditor.getSelection().index
|
||||
|
||||
// 插入图片
|
||||
myeditor.insertEmbed(index, 'image', base64Data)
|
||||
|
||||
// 使用 setTimeout 确保 DOM 更新
|
||||
setTimeout(() => {
|
||||
// 查找插入的 img 元素
|
||||
const imgNode = myeditor.container.querySelector(`img[src="${base64Data}"]`)
|
||||
if (imgNode) {
|
||||
imgNode.setAttribute('data-custom', data.path) // 设置自定义属性
|
||||
} else {
|
||||
console.log('Image not found in the DOM') // 调试信息
|
||||
}
|
||||
}, 0)
|
||||
// 设置光标位置
|
||||
myeditor.setSelection(index + 1)
|
||||
}
|
||||
|
||||
// 读取 Blob 数据
|
||||
reader.readAsDataURL(file.file)
|
||||
}
|
||||
},
|
||||
|
||||
clearInput() {
|
||||
this.value = ''
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user