🎨 修复富文本组件水印功能的bug&分类功能新增文章分类字段
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { getUrl } from '@/utils/image'
|
import { getUrl } from '@/utils/image'
|
||||||
import botLogo from '@/assets/bot_logo.png'
|
import botLogo from '@/assets/bot_logo.png'
|
||||||
|
import { useUserStore } from '@/pinia/modules/user'
|
||||||
|
|
||||||
const emits = defineEmits(['change', 'update:modelValue'])
|
const emits = defineEmits(['change', 'update:modelValue'])
|
||||||
|
|
||||||
@@ -47,20 +48,14 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 强制启用水印功能(临时解决方案)
|
|
||||||
const forceWatermark = true
|
|
||||||
|
|
||||||
// 调试:监听 props 变化
|
// 调试:监听 props 变化
|
||||||
watch(() => props.useWatermark, (newVal) => {
|
watch(() => props.useWatermark, (newVal) => {
|
||||||
console.log('useWatermark prop changed to:', newVal)
|
console.log('useWatermark prop changed to:', newVal)
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 调试:打印所有 props
|
|
||||||
console.log('All props:', props)
|
|
||||||
console.log('useWatermark value:', props.useWatermark)
|
|
||||||
|
|
||||||
const editorRef = shallowRef()
|
const editorRef = shallowRef()
|
||||||
const valueHtml = ref('')
|
const valueHtml = ref('')
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const toolbarConfig = {}
|
const toolbarConfig = {}
|
||||||
|
|
||||||
@@ -68,17 +63,22 @@
|
|||||||
const createCustomUpload = () => {
|
const createCustomUpload = () => {
|
||||||
return async (file, insertFn) => {
|
return async (file, insertFn) => {
|
||||||
// 直接获取当前的 props.useWatermark 值
|
// 直接获取当前的 props.useWatermark 值
|
||||||
const shouldUseWatermark = props.useWatermark || forceWatermark
|
const shouldUseWatermark = props.useWatermark
|
||||||
console.log('customUpload called, useWatermark:', shouldUseWatermark)
|
console.log('customUpload called, useWatermark:', shouldUseWatermark)
|
||||||
console.log('props.useWatermark:', props.useWatermark)
|
console.log('props.useWatermark:', props.useWatermark)
|
||||||
console.log('forceWatermark:', forceWatermark)
|
|
||||||
|
|
||||||
// 未开启水印则直接上传原图
|
// 未开启水印则直接上传原图
|
||||||
if (!shouldUseWatermark) {
|
if (!shouldUseWatermark) {
|
||||||
console.log('直接上传原图,不加水印')
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', { method: 'POST', body: formData })
|
const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'x-token': userStore.token,
|
||||||
|
'x-user-id': userStore.userInfo.ID
|
||||||
|
}
|
||||||
|
})
|
||||||
const res = await resp.json()
|
const res = await resp.json()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const urlPath = getUrl(res.data.file.url)
|
const urlPath = getUrl(res.data.file.url)
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log('开始添加水印')
|
console.log('开始添加水印,文件:', file.name)
|
||||||
const watermarkedBlob = await addBottomWatermark(file, {
|
const watermarkedBlob = await addBottomWatermark(file, {
|
||||||
stripRatio: 0.18, // 水印条高度占原图高度比例
|
stripRatio: 0.18, // 水印条高度占原图高度比例
|
||||||
background: 'rgba(255,255,255,0.96)',
|
background: 'rgba(255,255,255,0.96)',
|
||||||
@@ -98,14 +98,18 @@
|
|||||||
fontFamily: 'PingFang SC, Microsoft YaHei, Arial',
|
fontFamily: 'PingFang SC, Microsoft YaHei, Arial',
|
||||||
logo: botLogo
|
logo: botLogo
|
||||||
})
|
})
|
||||||
console.log('水印处理完成')
|
console.log('水印处理完成,新文件大小:', watermarkedBlob.size)
|
||||||
|
|
||||||
const newFile = new File([watermarkedBlob], file.name, { type: watermarkedBlob.type || file.type })
|
const newFile = new File([watermarkedBlob], file.name, { type: watermarkedBlob.type || file.type })
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', newFile)
|
formData.append('file', newFile)
|
||||||
const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', {
|
const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'x-token': userStore.token,
|
||||||
|
'x-user-id': userStore.userInfo.ID
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const res = await resp.json()
|
const res = await resp.json()
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -183,7 +187,6 @@
|
|||||||
const config = editor.getConfig()
|
const config = editor.getConfig()
|
||||||
if (config.MENU_CONF && config.MENU_CONF.uploadImage) {
|
if (config.MENU_CONF && config.MENU_CONF.uploadImage) {
|
||||||
config.MENU_CONF.uploadImage.customUpload = createCustomUpload()
|
config.MENU_CONF.uploadImage.customUpload = createCustomUpload()
|
||||||
console.log('动态更新上传配置,useWatermark:', props.useWatermark)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,25 +219,25 @@
|
|||||||
// 监听 useWatermark 变化,动态更新编辑器配置
|
// 监听 useWatermark 变化,动态更新编辑器配置
|
||||||
watch(
|
watch(
|
||||||
() => props.useWatermark,
|
() => props.useWatermark,
|
||||||
(newVal) => {
|
() => {
|
||||||
console.log('useWatermark changed to:', newVal)
|
|
||||||
if (editorRef.value && editorRef.value.getConfig) {
|
if (editorRef.value && editorRef.value.getConfig) {
|
||||||
const config = editorRef.value.getConfig()
|
const config = editorRef.value.getConfig()
|
||||||
if (config.MENU_CONF && config.MENU_CONF.uploadImage) {
|
if (config.MENU_CONF && config.MENU_CONF.uploadImage) {
|
||||||
config.MENU_CONF.uploadImage.customUpload = createCustomUpload()
|
config.MENU_CONF.uploadImage.customUpload = createCustomUpload()
|
||||||
console.log('动态更新上传配置,useWatermark:', newVal)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
async function addBottomWatermark(file, options) {
|
async function addBottomWatermark(file, options) {
|
||||||
|
console.log('addBottomWatermark 开始处理,文件:', file.name, '大小:', file.size)
|
||||||
const { stripRatio = 0.18, background = 'rgba(255,255,255,0.96)', text = '', textColor = '#333', fontFamily = 'Arial', logo } = options || {}
|
const { stripRatio = 0.18, background = 'rgba(255,255,255,0.96)', text = '', textColor = '#333', fontFamily = 'Arial', logo } = options || {}
|
||||||
|
|
||||||
const img = await fileToImage(file)
|
const img = await fileToImage(file)
|
||||||
const width = img.naturalWidth || img.width
|
const width = img.naturalWidth || img.width
|
||||||
const height = img.naturalHeight || img.height
|
const height = img.naturalHeight || img.height
|
||||||
const stripHeight = Math.max(60, Math.floor(height * stripRatio))
|
const stripHeight = Math.max(60, Math.floor(height * stripRatio))
|
||||||
|
console.log('图片尺寸:', width, 'x', height, '水印条高度:', stripHeight)
|
||||||
|
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
canvas.width = width
|
canvas.width = width
|
||||||
@@ -280,11 +283,20 @@
|
|||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.onload = () => resolve(img)
|
img.onload = () => {
|
||||||
img.onerror = reject
|
console.log('图片加载成功,尺寸:', img.naturalWidth, 'x', img.naturalHeight)
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
img.onerror = (error) => {
|
||||||
|
console.error('图片加载失败:', error)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
img.src = reader.result
|
img.src = reader.result
|
||||||
}
|
}
|
||||||
reader.onerror = reject
|
reader.onerror = (error) => {
|
||||||
|
console.error('文件读取失败:', error)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -293,8 +305,14 @@
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.crossOrigin = 'anonymous'
|
img.crossOrigin = 'anonymous'
|
||||||
img.onload = () => resolve(img)
|
img.onload = () => {
|
||||||
img.onerror = reject
|
console.log('Logo 加载成功,尺寸:', img.naturalWidth, 'x', img.naturalHeight)
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
img.onerror = (error) => {
|
||||||
|
console.error('Logo 加载失败:', error, 'src:', src)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
img.src = src
|
img.src = src
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -302,7 +320,10 @@
|
|||||||
function canvasToBlob(canvas, mime) {
|
function canvasToBlob(canvas, mime) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (canvas.toBlob) {
|
if (canvas.toBlob) {
|
||||||
canvas.toBlob((blob) => resolve(blob), mime, 0.92)
|
canvas.toBlob((blob) => {
|
||||||
|
console.log('Canvas 转 Blob 成功,大小:', blob.size, '类型:', blob.type)
|
||||||
|
resolve(blob)
|
||||||
|
}, mime, 0.92)
|
||||||
} else {
|
} else {
|
||||||
// 兼容处理
|
// 兼容处理
|
||||||
const dataURL = canvas.toDataURL(mime)
|
const dataURL = canvas.toDataURL(mime)
|
||||||
@@ -311,7 +332,9 @@
|
|||||||
let n = bstr.length
|
let n = bstr.length
|
||||||
const u8arr = new Uint8Array(n)
|
const u8arr = new Uint8Array(n)
|
||||||
while (n--) u8arr[n] = bstr.charCodeAt(n)
|
while (n--) u8arr[n] = bstr.charCodeAt(n)
|
||||||
resolve(new Blob([u8arr], { type: mime }))
|
const blob = new Blob([u8arr], { type: mime })
|
||||||
|
console.log('Canvas 转 Blob (兼容模式) 成功,大小:', blob.size, '类型:', blob.type)
|
||||||
|
resolve(blob)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -93,7 +93,7 @@
|
|||||||
<el-input v-model="formData.keyword" :clearable="true" placeholder="请输入关键词" />
|
<el-input v-model="formData.keyword" :clearable="true" placeholder="请输入关键词" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="内容:" prop="content" >
|
<el-form-item label="内容:" prop="content" >
|
||||||
<RichEdit v-model="formData.content"/>
|
<RichEdit v-model="formData.content" :useWatermark="true"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
@@ -18,7 +18,12 @@
|
|||||||
|
|
||||||
|
|
||||||
<template v-if="showAllQuery">
|
<template v-if="showAllQuery">
|
||||||
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
|
<el-form-item label="分类类型">
|
||||||
|
<el-select v-model="searchInfo.isArticle" placeholder="请选择分类类型" clearable style="width: 180px">
|
||||||
|
<el-option label="文章分类" :value="1" />
|
||||||
|
<el-option label="非文章分类" :value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -67,9 +72,12 @@
|
|||||||
<el-table-column align="left" label="是否启用" prop="active" width="120">
|
<el-table-column align="left" label="是否启用" prop="active" width="120">
|
||||||
<template #default="scope">{{ formatBoolean(scope.row.active) }}</template>
|
<template #default="scope">{{ formatBoolean(scope.row.active) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="left" label="首页展示" prop="active" width="120">
|
<el-table-column align="left" label="首页展示" prop="index" width="120">
|
||||||
<template #default="scope">{{ formatBoolean(scope.row.index) }}</template>
|
<template #default="scope">{{ formatBoolean(scope.row.index) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column align="left" label="分类类型" prop="isArticle" width="120">
|
||||||
|
<template #default="scope">{{ scope.row.isArticle === 1 ? '文章分类' : '非文章分类' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column align="left" label="父ID" prop="parentId" width="120" />
|
<el-table-column align="left" label="父ID" prop="parentId" width="120" />
|
||||||
<el-table-column align="left" label="操作" fixed="right" :min-width="appStore.operateMinWith">
|
<el-table-column align="left" label="操作" fixed="right" :min-width="appStore.operateMinWith">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@@ -120,6 +128,12 @@
|
|||||||
<el-form-item label="是否首页展示:" prop="index" >
|
<el-form-item label="是否首页展示:" prop="index" >
|
||||||
<el-switch v-model="formData.index" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
|
<el-switch v-model="formData.index" active-color="#13ce66" inactive-color="#ff4949" active-text="是" inactive-text="否" clearable ></el-switch>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="分类类型:" prop="isArticle" >
|
||||||
|
<el-select v-model="formData.isArticle" placeholder="请选择分类类型" style="width: 100%">
|
||||||
|
<el-option label="文章分类" :value="1" />
|
||||||
|
<el-option label="非文章分类" :value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="父ID:" prop="parentId" >
|
<el-form-item label="父ID:" prop="parentId" >
|
||||||
<el-input v-model.number="formData.parentId" :clearable="false" placeholder="请输入父ID" />
|
<el-input v-model.number="formData.parentId" :clearable="false" placeholder="请输入父ID" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -150,6 +164,9 @@
|
|||||||
<el-descriptions-item label="首页展示">
|
<el-descriptions-item label="首页展示">
|
||||||
{{ detailFrom.index }}
|
{{ detailFrom.index }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="分类类型">
|
||||||
|
{{ detailFrom.isArticle === 1 ? '文章分类' : '非文章分类' }}
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="父ID">
|
<el-descriptions-item label="父ID">
|
||||||
{{ detailFrom.parentId }}
|
{{ detailFrom.parentId }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -197,7 +214,8 @@ const formData = ref({
|
|||||||
active: false,
|
active: false,
|
||||||
parentId: 0,
|
parentId: 0,
|
||||||
index: 0,
|
index: 0,
|
||||||
icon: ''
|
icon: '',
|
||||||
|
isArticle: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -409,7 +427,8 @@ const closeDialog = () => {
|
|||||||
active: false,
|
active: false,
|
||||||
parentId: 0,
|
parentId: 0,
|
||||||
index: 0,
|
index: 0,
|
||||||
icon: ''
|
icon: '',
|
||||||
|
isArticle: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 弹窗确定
|
// 弹窗确定
|
||||||
|
@@ -60,18 +60,19 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 文章介绍(放在文章详情上方,使用富文本) -->
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="文章详情" prop="content">
|
<el-form-item label="文章介绍" prop="desc">
|
||||||
<RichEdit style="width: 100%" v-model="formData.content"/>
|
<RichEdit style="width: 100%" v-model="formData.desc"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="备注" prop="desc">
|
<el-form-item label="文章详情" prop="content">
|
||||||
<el-input type="textarea" v-model="formData.desc" placeholder="请输入文章备注信息" />
|
<RichEdit style="width: 100%" v-model="formData.content"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -140,6 +141,10 @@
|
|||||||
coverImg: [
|
coverImg: [
|
||||||
{ required: true, message: '请上传封面', trigger: 'blur' }
|
{ required: true, message: '请上传封面', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
|
// 文章介绍改为富文本,也做必填校验
|
||||||
|
desc: [
|
||||||
|
{ required: true, message: '请输入文章介绍', trigger: 'blur' }
|
||||||
|
],
|
||||||
content: [
|
content: [
|
||||||
{ required: true, message: '请输入文章内容', trigger: 'blur' }
|
{ required: true, message: '请输入文章内容', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user