diff --git a/src/assets/bot_logo.png b/src/assets/bot_logo.png
new file mode 100644
index 0000000..b5ea9a6
Binary files /dev/null and b/src/assets/bot_logo.png differ
diff --git a/src/components/bottomInfo/bottomInfo.vue b/src/components/bottomInfo/bottomInfo.vue
index 376de05..9d362bd 100644
--- a/src/components/bottomInfo/bottomInfo.vue
+++ b/src/components/bottomInfo/bottomInfo.vue
@@ -11,22 +11,12 @@
Gin-Vue-Admin
-
-
-
-
-
Copyright
-
- flipped-aurora团队Echo
+
@@ -35,10 +25,4 @@
name: 'BottomInfo'
})
- console.log(
- `%c powered by %c flipped-aurorae %c`,
- 'background:#0081ff; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
- 'background:#354855; padding: 1px 5px; border-radius: 0 3px 3px 0; color: #fff; font-weight: bold;',
- 'background:transparent'
- )
diff --git a/src/components/richtext/rich-edit.vue b/src/components/richtext/rich-edit.vue
index 25158da..e238518 100644
--- a/src/components/richtext/rich-edit.vue
+++ b/src/components/richtext/rich-edit.vue
@@ -27,6 +27,7 @@
import { ElMessage } from 'element-plus'
import { getUrl } from '@/utils/image'
+ import botLogo from '@/assets/bot_logo.png'
const emits = defineEmits(['change', 'update:modelValue'])
@@ -39,6 +40,10 @@
modelValue: {
type: String,
default: ''
+ },
+ useWatermark: {
+ type: Boolean,
+ default: false
}
})
@@ -54,6 +59,60 @@
fieldName: 'file',
maxFileSize: 1024 * 1024 * 10, // 限制图片大小为10MB
maxNumberOfFiles: 1,
+ async customUpload(file, insertFn) {
+ // 未开启水印则直接上传原图
+ if (!props.useWatermark) {
+ const formData = new FormData()
+ formData.append('file', file)
+ const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', { method: 'POST', body: formData })
+ const res = await resp.json()
+ if (res.code === 0) {
+ const urlPath = getUrl(res.data.file.url)
+ insertFn(urlPath, res.data.file.name)
+ } else {
+ ElMessage.error(res.msg || '上传失败')
+ }
+ return
+ }
+ try {
+ const watermarkedBlob = await addBottomWatermark(file, {
+ stripRatio: 0.18, // 水印条高度占原图高度比例
+ background: 'rgba(255,255,255,0.96)',
+ text: '老陈机器人',
+ textColor: '#333',
+ fontFamily: 'PingFang SC, Microsoft YaHei, Arial',
+ logo: botLogo
+ })
+
+ const newFile = new File([watermarkedBlob], file.name, { type: watermarkedBlob.type || file.type })
+ const formData = new FormData()
+ formData.append('file', newFile)
+ const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', {
+ method: 'POST',
+ body: formData
+ })
+ const res = await resp.json()
+ if (res.code === 0) {
+ const urlPath = getUrl(res.data.file.url)
+ insertFn(urlPath, res.data.file.name)
+ } else {
+ ElMessage.error(res.msg || '上传失败')
+ }
+ } catch {
+ ElMessage.error('处理水印失败')
+ // 降级:直接走原图上传
+ const formData = new FormData()
+ formData.append('file', file)
+ const resp = await fetch(basePath + '/fileUploadAndDownload/upload?noSave=1', { method: 'POST', body: formData })
+ const res = await resp.json()
+ if (res.code === 0) {
+ const urlPath = getUrl(res.data.file.url)
+ insertFn(urlPath, res.data.file.name)
+ } else {
+ ElMessage.error(res.msg || '上传失败')
+ }
+ }
+ },
customInsert(res, insertFn) {
if (res.code === 0) {
const urlPath = getUrl(res.data.file.url)
@@ -110,6 +169,94 @@
valueHtml.value = props.modelValue
}
)
+
+ async function addBottomWatermark(file, 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 width = img.naturalWidth || img.width
+ const height = img.naturalHeight || img.height
+ const stripHeight = Math.max(60, Math.floor(height * stripRatio))
+
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height + stripHeight
+ const ctx = canvas.getContext('2d')
+
+ // 原图
+ ctx.drawImage(img, 0, 0, width, height)
+
+ // 底部水印条背景
+ ctx.fillStyle = background
+ ctx.fillRect(0, height, width, stripHeight)
+
+ // 左侧 Logo(可选)
+ let logoSize = Math.floor(stripHeight * 0.6)
+ let logoPadding = Math.floor(stripHeight * 0.2)
+ if (logo) {
+ try {
+ const logoImg = await srcToImage(logo)
+ const ratio = logoImg.width / logoImg.height
+ const drawW = logoSize
+ const drawH = Math.floor(drawW / ratio)
+ const y = height + Math.floor((stripHeight - drawH) / 2)
+ ctx.drawImage(logoImg, logoPadding, y, drawW, drawH)
+ } catch { void 0 }
+ }
+
+ // 右侧文字
+ ctx.fillStyle = textColor
+ const fontSize = Math.floor(stripHeight * 0.38)
+ ctx.font = `${fontSize}px ${fontFamily}`
+ ctx.textBaseline = 'middle'
+ ctx.textAlign = 'right'
+ const textPadding = Math.floor(stripHeight * 0.25)
+ ctx.fillText(text, width - textPadding, height + Math.floor(stripHeight / 2))
+
+ const blob = await canvasToBlob(canvas, file.type || 'image/png')
+ return blob
+ }
+
+ function fileToImage(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.onload = () => {
+ const img = new Image()
+ img.onload = () => resolve(img)
+ img.onerror = reject
+ img.src = reader.result
+ }
+ reader.onerror = reject
+ reader.readAsDataURL(file)
+ })
+ }
+
+ function srcToImage(src) {
+ return new Promise((resolve, reject) => {
+ const img = new Image()
+ img.crossOrigin = 'anonymous'
+ img.onload = () => resolve(img)
+ img.onerror = reject
+ img.src = src
+ })
+ }
+
+ function canvasToBlob(canvas, mime) {
+ return new Promise((resolve) => {
+ if (canvas.toBlob) {
+ canvas.toBlob((blob) => resolve(blob), mime, 0.92)
+ } else {
+ // 兼容处理
+ const dataURL = canvas.toDataURL(mime)
+ const arr = dataURL.split(',')
+ const bstr = atob(arr[1])
+ let n = bstr.length
+ const u8arr = new Uint8Array(n)
+ while (n--) u8arr[n] = bstr.charCodeAt(n)
+ resolve(new Blob([u8arr], { type: mime }))
+ }
+ })
+ }
diff --git a/src/pathInfo.json b/src/pathInfo.json
index e688821..0fa9f30 100644
--- a/src/pathInfo.json
+++ b/src/pathInfo.json
@@ -26,6 +26,7 @@
"/src/view/goods/article/edit.vue": "Edit",
"/src/view/goods/article/index.vue": "Index",
"/src/view/goods/index.vue": "goods",
+ "/src/view/goods/vip/index.vue": "VipList",
"/src/view/init/index.vue": "Init",
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
"/src/view/layout/aside/asideComponent/index.vue": "AsideComponent",
diff --git a/src/view/bot/bot/botForm.vue b/src/view/bot/bot/botForm.vue
index 3afe384..b4773c9 100644
--- a/src/view/bot/bot/botForm.vue
+++ b/src/view/bot/bot/botForm.vue
@@ -29,8 +29,6 @@ defineOptions({
name: 'BotForm'
})
-// 自动获取字典
-import { getDictFunc } from '@/utils/format'
import { useRoute, useRouter } from "vue-router"
import { ElMessage } from 'element-plus'
import { ref, reactive } from 'vue'