2026-02-05 18:00:11 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
2026-02-11 17:27:08 +08:00
|
|
|
|
import { nextTick, onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
2026-02-05 18:00:11 +08:00
|
|
|
|
import { getAliOssTokenAxios } from '@/service/api/upload'
|
|
|
|
|
|
import { browserPathJoin } from '@/utils/date'
|
|
|
|
|
|
import { initOSSClient, uploadFileToOSS } from '@/utils/oss'
|
|
|
|
|
|
import '@wangeditor/editor/dist/css/style.css'
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
modelValue: string
|
|
|
|
|
|
placeholder?: string
|
|
|
|
|
|
mode?: 'default' | 'simple'
|
|
|
|
|
|
height?: string
|
|
|
|
|
|
path?: string
|
2026-02-11 17:27:08 +08:00
|
|
|
|
excludeKeys?: string[]
|
2026-02-05 18:00:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
modelValue: '',
|
|
|
|
|
|
placeholder: '请输入内容...',
|
|
|
|
|
|
mode: 'default',
|
|
|
|
|
|
height: '300px',
|
|
|
|
|
|
path: 'temp',
|
2026-02-11 17:27:08 +08:00
|
|
|
|
excludeKeys: () => [],
|
2026-02-05 18:00:11 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
|
|
|
|
|
|
|
|
// Editor Logic
|
|
|
|
|
|
const editorRef = shallowRef()
|
|
|
|
|
|
const toolbarConfig = {
|
2026-02-11 17:27:08 +08:00
|
|
|
|
excludeKeys: props.excludeKeys || [],
|
2026-02-05 18:00:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type InsertFnType = (url: string, alt: string, href: string) => void
|
|
|
|
|
|
|
|
|
|
|
|
async function customUpload(file: File, insertFn: InsertFnType) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data: tokenData, error: tokenError } = await getAliOssTokenAxios()
|
|
|
|
|
|
if (tokenError || !tokenData) {
|
|
|
|
|
|
window.$message?.error('获取上传凭证失败')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const client = initOSSClient(tokenData?.data || {})
|
|
|
|
|
|
const path = `${props.path}/${Date.now()}/${file.name}`
|
|
|
|
|
|
|
|
|
|
|
|
await uploadFileToOSS(client, file, path)
|
|
|
|
|
|
|
|
|
|
|
|
const url = browserPathJoin(import.meta.env.VITE_BASE_OSS_URL, path)
|
|
|
|
|
|
insertFn(url, file.name, url)
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (error) {
|
|
|
|
|
|
console.error('上传失败', error)
|
|
|
|
|
|
window.$message?.error('上传失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const editorConfig = {
|
|
|
|
|
|
placeholder: props.placeholder,
|
|
|
|
|
|
MENU_CONF: {
|
|
|
|
|
|
uploadImage: {
|
|
|
|
|
|
customUpload,
|
|
|
|
|
|
},
|
|
|
|
|
|
uploadVideo: {
|
|
|
|
|
|
customUpload,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 placeholder 变化
|
|
|
|
|
|
// watch(() => props.placeholder, (_newVal) => {
|
|
|
|
|
|
// if (editorRef.value) {
|
|
|
|
|
|
// // 似乎 wangeditor 不支持动态修改 placeholder,这里作为占位
|
|
|
|
|
|
// }
|
|
|
|
|
|
// })
|
2026-02-11 17:27:08 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
|
console.log(toolbarConfig, 'toolbarConfig')
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
|
console.log(editorRef.value)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-02-05 18:00:11 +08:00
|
|
|
|
|
|
|
|
|
|
function handleCreated(editor: any) {
|
|
|
|
|
|
editorRef.value = editor
|
2026-02-11 17:27:08 +08:00
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
|
console.log('Toolbar keys:', editor.getAllMenuKeys())
|
2026-02-05 18:00:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleChange(editor: any) {
|
|
|
|
|
|
emit('update:modelValue', editor.getHtml())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
const editor = editorRef.value
|
|
|
|
|
|
if (editor == null)
|
|
|
|
|
|
return
|
|
|
|
|
|
editor.destroy()
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="overflow-hidden border border-gray-200 rounded-lg bg-white">
|
|
|
|
|
|
<Toolbar
|
|
|
|
|
|
style="border-bottom: 1px solid #eee"
|
|
|
|
|
|
:editor="editorRef"
|
|
|
|
|
|
:default-config="toolbarConfig"
|
|
|
|
|
|
:mode="mode"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div :style="{ height: props.height }">
|
|
|
|
|
|
<Editor
|
|
|
|
|
|
:model-value="modelValue"
|
|
|
|
|
|
:style="{ height: '100%', overflowY: 'hidden' }"
|
|
|
|
|
|
:default-config="editorConfig"
|
|
|
|
|
|
:mode="mode"
|
|
|
|
|
|
@on-created="handleCreated"
|
|
|
|
|
|
@on-change="handleChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
:deep(.w-e-text-container) {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|