diff --git a/electron/electron.js b/electron/electron.js
index 950793217..5eedd0a14 100644
--- a/electron/electron.js
+++ b/electron/electron.js
@@ -1,7 +1,7 @@
const fs = require('fs')
const os = require("os");
const path = require('path')
-const {app, BrowserWindow, ipcMain, dialog, clipboard, nativeImage, shell, Tray, Menu, globalShortcut, Notification, BrowserView, nativeTheme} = require('electron')
+const {app, BrowserWindow, ipcMain, dialog, clipboard, nativeImage, shell, Tray, Menu, globalShortcut, Notification, BrowserView, nativeTheme, protocol} = require('electron')
const {autoUpdater} = require("electron-updater")
const log = require("electron-log");
const electronConf = require('electron-config')
@@ -52,6 +52,70 @@ if (fs.existsSync(devloadCachePath)) {
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
}
+// 在最开始就注册协议为特权协议
+protocol.registerSchemesAsPrivileged([
+ {
+ scheme: 'dootask-resources',
+ privileges: {
+ standard: true,
+ secure: true,
+ supportFetchAPI: true,
+ corsEnabled: true
+ }
+ }
+])
+
+/**
+ * 创建协议
+ */
+function createProtocol() {
+ protocol.handle('dootask-resources', async (request) => {
+ const url = request.url.replace(/^dootask-resources:\/\//, '')
+
+ if (url.includes('..')) {
+ return new Response('Access Denied', { status: 403 })
+ }
+
+ try {
+ const filePath = path.join(__dirname, devloadUrl ? '..' : '.', url)
+
+ if (!fs.existsSync(filePath)) {
+ return new Response('Not Found', { status: 404 })
+ }
+
+ const data = await fs.promises.readFile(filePath)
+ const mimeType = getMimeType(filePath)
+
+ return new Response(data, {
+ headers: {
+ 'Content-Type': mimeType
+ }
+ })
+ } catch (error) {
+ console.error('Protocol handler error:', error)
+ return new Response('Internal Error', { status: 500 })
+ }
+ })
+}
+
+/**
+ * MIME类型判断
+ * @param filePath
+ * @returns {*|string}
+ */
+function getMimeType(filePath) {
+ const ext = path.extname(filePath).toLowerCase()
+ const mimeTypes = {
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.png': 'image/png',
+ '.gif': 'image/gif',
+ '.svg': 'image/svg+xml',
+ '.webp': 'image/webp'
+ }
+ return mimeTypes[ext] || 'application/octet-stream'
+}
+
/**
* 创建主窗口
*/
@@ -596,6 +660,8 @@ if (!getTheLock) {
isReady = true
// SameSite
utils.useCookie()
+ // 创建协议
+ createProtocol()
// 创建主窗口
createMainWindow()
// 创建托盘
diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js
index 42472db11..ef8218fb6 100644
--- a/resources/assets/js/app.js
+++ b/resources/assets/js/app.js
@@ -62,6 +62,7 @@ import TagInput from './components/TagInput.vue'
import TableAction from './components/TableAction.vue'
import QuickEdit from './components/QuickEdit.vue'
import UserAvatar from './components/UserAvatar'
+import Imgs from './components/Replace/Imgs'
import ImgView from './components/ImgView.vue'
import Scrollbar from './components/Scrollbar'
@@ -72,6 +73,7 @@ Vue.component('TagInput', TagInput)
Vue.component('TableAction', TableAction);
Vue.component('QuickEdit', QuickEdit);
Vue.component('UserAvatar', UserAvatar);
+Vue.component('Imgs', Imgs);
Vue.component('ImgView', ImgView);
Vue.component('Scrollbar', Scrollbar);
diff --git a/resources/assets/js/components/Replace/Imgs.vue b/resources/assets/js/components/Replace/Imgs.vue
new file mode 100644
index 000000000..850cb9c23
--- /dev/null
+++ b/resources/assets/js/components/Replace/Imgs.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/resources/assets/js/components/Replace/utils.js b/resources/assets/js/components/Replace/utils.js
new file mode 100644
index 000000000..78e6524f5
--- /dev/null
+++ b/resources/assets/js/components/Replace/utils.js
@@ -0,0 +1,62 @@
+const publicImageResources = (() => {
+ let initialized = false
+ let appPreUrl = null
+ let serverPreUrl = null
+ let urlRegex = null
+
+ // 将 escapeRegExp 移到闭包内部
+ const escapeRegExp = (string) => {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ }
+
+ // 初始化函数
+ const initialize = () => {
+ if (initialized) return
+
+ // 设置应用前缀URL
+ if ($A.isEEUiApp) {
+ appPreUrl = $A.eeuiAppRewriteUrl('../public/')
+ } else if ($A.Electron) {
+ appPreUrl = "dootask-resources://public/"
+ }
+
+ // 如果没有特殊前缀,提前返回
+ if (!appPreUrl) return
+
+ // 获取服务器URL
+ serverPreUrl = $A.mainUrl()
+ const escapedPreUrl = escapeRegExp(serverPreUrl)
+
+ // 固定的模式
+ const patterns = [
+ 'images/',
+ // 可以继续添加其他路径...
+ ]
+
+ // 计算转义后的模式
+ const escapedPatterns = patterns.map(pattern => escapeRegExp(pattern))
+
+ // 编译正则表达式
+ urlRegex = new RegExp(`${escapedPreUrl}(${escapedPatterns.join('|')})`)
+
+ initialized = true
+ }
+
+ // 返回实际的处理函数
+ return (url) => {
+ // 第一次调用时初始化
+ initialize()
+
+ // 如果没有特殊前缀,直接返回原URL
+ if (!appPreUrl) {
+ return url
+ }
+
+ if (urlRegex.test(url)) {
+ return url.replace(serverPreUrl, appPreUrl)
+ }
+ return url
+ }
+})()
+
+export {publicImageResources}
diff --git a/resources/assets/js/functions/web.js b/resources/assets/js/functions/web.js
index e27b40b17..284ff878b 100755
--- a/resources/assets/js/functions/web.js
+++ b/resources/assets/js/functions/web.js
@@ -1,4 +1,5 @@
import {MarkdownPreview} from "../store/markdown";
+import {publicImageResources} from "../components/Replace/utils";
/**
* 页面专用
@@ -349,7 +350,7 @@ import {MarkdownPreview} from "../store/markdown";
const value = item.original
.replace(/\s+width=/, ` original-width=`)
.replace(/\s+height=/, ` original-height=`)
- .replace(/\s+src=(["'])([^'"]*)\1/i, ` style="width:${data.width}px;height:${data.height}px" src="${data.src}"`)
+ .replace(/\s+src=(["'])([^'"]*)\1/i, ` style="width:${data.width}px;height:${data.height}px" src="${publicImageResources(data.src)}"`)
text = text.replace(item.original, value)
} else {
text = text.replace(item.original, `