<template>
|
<div class="ai-editor-container">
|
<div ref="editorRef" :style="{ height }" />
|
</div>
|
</template>
|
|
<script>
|
import { AiEditor } from "aieditor";
|
import 'aieditor/dist/style.css'
|
import { getAllocateIp } from '@/utils/utils'
|
import apiConfig from '@/utils/baseurl'
|
|
export default {
|
name: 'AiEditor',
|
props: {
|
value: {
|
type: String,
|
default: ''
|
},
|
height: {
|
type: String,
|
default: '500px'
|
},
|
placeholder: {
|
type: String,
|
default: '请输入内容...'
|
},
|
readOnly: {
|
type: Boolean,
|
default: false
|
},
|
toolbar: {
|
type: Array,
|
default: () => [
|
'bold',
|
'italic',
|
'underline',
|
'strikethrough',
|
'heading',
|
'quote',
|
'code',
|
'list',
|
'ordered-list',
|
'unordered-list',
|
'link',
|
'image',
|
'table'
|
]
|
}
|
},
|
data() {
|
return {
|
editor: null
|
}
|
},
|
watch: {
|
value: {
|
handler(newVal) {
|
if (this.editor && newVal !== this.editor.getHtml()) {
|
const contentWithIp = this.addIpToContent(newVal);
|
this.editor.setContent(contentWithIp)
|
}
|
},
|
immediate: true
|
},
|
readOnly: {
|
handler(newVal) {
|
if (this.editor) {
|
this.editor.setReadOnly(newVal)
|
}
|
},
|
immediate: true
|
}
|
},
|
mounted() {
|
this.initEditor()
|
},
|
beforeDestroy() {
|
this.destroyEditor()
|
},
|
methods: {
|
// 去除内容中的IP地址,只保留相对路径
|
removeIpFromContent(content) {
|
if (!content || typeof content !== 'string') return content;
|
|
// 获取当前IP地址
|
const currentIp = getAllocateIp();
|
|
// 使用正则表达式匹配并替换所有包含当前IP的URL
|
const ipRegex = new RegExp(currentIp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
return content.replace(ipRegex, '');
|
},
|
|
// 为内容中的相对路径添加IP地址,并处理已包含IP的绝对路径
|
addIpToContent(content) {
|
if (!content || typeof content !== 'string') return content;
|
|
// 获取当前IP地址
|
const currentIp = getAllocateIp();
|
|
// 匹配所有src、href、poster属性
|
const pathRegex = /(src|href|poster)="([^"]+)"/g;
|
|
return content.replace(pathRegex, (match, attr, path) => {
|
// 如果路径是相对路径(不以http开头),则添加当前IP地址
|
if (!path.startsWith('http')) {
|
// 检查路径是否已经以/images/开头,避免重复添加
|
if (path.startsWith('/images/')) {
|
// 如果currentIp已经以/images/结尾,则去掉路径开头的/
|
if (currentIp.endsWith('/images/')) {
|
return `${attr}="${currentIp}${path.substring(1)}"`;
|
} else {
|
return `${attr}="${currentIp}${path}"`;
|
}
|
} else {
|
return `${attr}="${currentIp}${path}"`;
|
}
|
}
|
|
// 如果路径已经包含当前IP地址,直接返回
|
if (path.startsWith(currentIp)) {
|
return match;
|
}
|
|
// 如果路径是绝对路径,提取路径部分并替换为当前IP地址
|
// 匹配形如 http://192.168.1.100:8080/xxx 的路径
|
const absolutePathRegex = /^https?:\/\/[^\/]+(\/.*)$/;
|
const matchResult = path.match(absolutePathRegex);
|
|
if (matchResult) {
|
// 提取路径部分,替换为当前IP地址
|
let pathPart = matchResult[1];
|
|
// 如果currentIp已经以/images/结尾,且pathPart以/images/开头,则去掉pathPart开头的/images/
|
if (currentIp.endsWith('/images/') && pathPart.startsWith('/images/')) {
|
pathPart = pathPart.substring(7); // 去掉开头的 '/images/'
|
// 如果pathPart以/开头,则去掉开头的/
|
if (pathPart.startsWith('/')) {
|
pathPart = pathPart.substring(1);
|
}
|
}
|
|
return `${attr}="${currentIp}${pathPart}"`;
|
}
|
|
// 其他情况(如外部链接)保持不变
|
|
return match;
|
});
|
},
|
|
initEditor() {
|
const options = {
|
element: this.$refs.editorRef,
|
placeholder: this.placeholder,
|
content: this.addIpToContent(this.value),
|
editable: !this.readOnly,
|
toolbar: this.toolbar,
|
toolbarExcludeKeys: ["ai"],
|
draggable: false,
|
onChange: (content) => {
|
// 确保content是字符串类型
|
const contentStr = typeof content === 'string' ? content : String(content || '');
|
const contentWithoutIp = this.removeIpFromContent(contentStr);
|
this.$emit('input', contentWithoutIp)
|
this.$emit('change', contentWithoutIp)
|
},
|
onFocus: () => {
|
this.$emit('focus')
|
},
|
onBlur: () => {
|
this.$emit('blur')
|
},
|
// 禁用 AI 功能
|
ai: false,
|
// 禁用调整大小功能
|
resize: false,
|
// 添加唯一标识
|
id: `editor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
// 自定义图片上传功能
|
image: {
|
uploadUrl: apiConfig.imgUrl,
|
uploadHeaders: {
|
Authorization: sessionStorage.getItem('token') || '',
|
// ...headers
|
},
|
uploader: (file, uploadUrl, headers, formName = 'file') => {
|
const formData = new FormData();
|
formData.append('file', file);
|
return new Promise((resolve, reject) => {
|
fetch(apiConfig.imgUrl, {
|
method: "post",
|
headers: { 'Accept': 'application/json', ...headers },
|
body: formData,
|
}).then((resp) => resp.json())
|
.then(json => {
|
let data = {
|
"errorCode": 0,
|
"data": {
|
"src": getAllocateIp() + json.msg,
|
"alt": "图片 alt"
|
}
|
}
|
resolve(data);
|
}).catch((error) => {
|
// reject(error);
|
})
|
});
|
},
|
},
|
video: {
|
uploadUrl: apiConfig.imgUrl,
|
uploadFormName: "video", //上传时的文件表单名称
|
uploadHeaders: {
|
Authorization: sessionStorage.getItem('token') || '',
|
},
|
uploader: (file, uploadUrl, headers, formName = 'file') => {
|
const formData = new FormData();
|
formData.append('file', file);
|
return new Promise((resolve, reject) => {
|
fetch(apiConfig.imgUrl, {
|
method: "post",
|
headers: { 'Accept': 'application/json', ...headers },
|
body: formData,
|
}).then((resp) => resp.json())
|
.then(json => {
|
let data = {
|
"errorCode": 0,
|
"data": {
|
"src": getAllocateIp() + json.msg,
|
"poster": "http://your-domain.com/poster.jpg"
|
}
|
}
|
resolve(data);
|
}).catch((error) => {
|
// reject(error);
|
})
|
});
|
},
|
|
},
|
attachment: {
|
uploadUrl: apiConfig.imgUrl,
|
uploadFormName: "attachment", //上传时的文件表单名称
|
// uploadHeaders: {
|
// "jwt": "xxxxx",
|
// "other": "xxxx",
|
// },
|
uploadHeaders: () => {
|
return {
|
Authorization: sessionStorage.getItem('token') || '',
|
}
|
},
|
uploader: (file, uploadUrl, headers, formName = 'file') => {
|
const formData = new FormData();
|
formData.append('file', file);
|
return new Promise((resolve, reject) => {
|
fetch(apiConfig.imgUrl, {
|
method: "post",
|
headers: { 'Accept': 'application/json', ...headers },
|
body: formData,
|
}).then((resp) => resp.json())
|
.then(json => {
|
let data = {
|
"errorCode": 0,
|
"data": {
|
"href": getAllocateIp() + json.msg,
|
"fileName": file.name
|
}
|
}
|
resolve(data);
|
}).catch((error) => {
|
// reject(error);
|
})
|
});
|
},
|
|
},
|
|
}
|
|
console.log('this.addIpToContent(this.value)',this.addIpToContent(this.value))
|
try {
|
this.editor = new AiEditor(options)
|
} catch (error) {
|
console.error('Failed to initialize AiEditor:', error)
|
}
|
},
|
destroyEditor() {
|
if (this.editor) {
|
this.editor.destroy()
|
this.editor = null
|
}
|
},
|
// 获取编辑器内容(去除IP地址,只保留相对路径)
|
getContent() {
|
if (!this.editor) return '';
|
const content = this.editor.getHtml();
|
return this.removeIpFromContent(content);
|
},
|
// 设置编辑器内容(为相对路径添加IP地址)
|
setContent(content) {
|
if (this.editor) {
|
const contentWithIp = this.addIpToContent(content);
|
this.editor.setContent(contentWithIp);
|
}
|
},
|
// 清空编辑器内容
|
clear() {
|
if (this.editor) {
|
this.editor.setContent('')
|
}
|
},
|
// 获取编辑器实例
|
getEditor() {
|
return this.editor
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
:deep(aie-footer) {
|
display: none !important;
|
}
|
|
.ai-editor-container {
|
width: 100%;
|
border-radius: 4px;
|
background: #fff;
|
}
|
|
#editor {
|
width: 100%;
|
min-height: 300px;
|
}
|
|
:deep(.ai-editor) {
|
border: none !important;
|
}
|
|
:deep(.ai-editor-toolbar) {
|
border-bottom: 1px solid #dcdfe6 !important;
|
}
|
|
:deep(.ai-editor-content) {
|
border: none !important;
|
}
|
|
/* 隐藏右下角拖动图标 */
|
:deep(.ai-editor-resize) {
|
display: none !important;
|
}
|
|
/* 隐藏 AI 相关按钮 */
|
:deep(.ai-editor-ai-button) {
|
display: none !important;
|
}
|
</style>
|