Compare commits
3 Commits
d8603e1a35
...
68c77b35d7
Author | SHA1 | Date | |
---|---|---|---|
![]() |
68c77b35d7 | ||
![]() |
261932cb84 | ||
![]() |
c20347d95e |
src
api/base
components/IFrame/src
hooks/setting
logics
router/guard
settings
types
utils
views/infra/bookmark
@ -3,3 +3,9 @@ export interface UploadApiResult {
|
|||||||
code: number
|
code: number
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UploadFullApiResult<T> {
|
||||||
|
msg: string
|
||||||
|
code: number
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type { AxiosProgressEvent } from 'axios'
|
import type { AxiosProgressEvent } from 'axios'
|
||||||
import type { UploadApiResult } from './model/uploadModel'
|
import type { UploadApiResult, UploadFullApiResult } from './model/uploadModel'
|
||||||
import { defHttp } from '@/utils/http/axios'
|
import { defHttp } from '@/utils/http/axios'
|
||||||
import type { UploadFileParams } from '@/types/axios'
|
import type { FileDO, UploadFileParams } from '@/types/axios'
|
||||||
import { useGlobSetting } from '@/hooks/setting'
|
import { useGlobSetting } from '@/hooks/setting'
|
||||||
|
|
||||||
const { uploadUrl = '' } = useGlobSetting()
|
const { uploadUrl = '', uploadUrlFull } = useGlobSetting()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: Upload interface
|
* @description: Upload interface
|
||||||
@ -18,3 +18,12 @@ export function uploadApi(params: UploadFileParams, onUploadProgress: (progressE
|
|||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadOneFile(params: UploadFileParams) {
|
||||||
|
return defHttp.uploadFile<UploadFullApiResult<FileDO>>(
|
||||||
|
{
|
||||||
|
url: uploadUrlFull,
|
||||||
|
},
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ const props = defineProps({
|
|||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const height = ref<string | number>('')
|
const height = ref<string | number>('')
|
||||||
const frameRef = ref<null | HTMLIFrameElement>(null)
|
const frameRef = ref<null | HTMLIFrameElement>(null)
|
||||||
@ -25,22 +26,8 @@ onMounted(() => {
|
|||||||
}, 300)
|
}, 300)
|
||||||
})
|
})
|
||||||
|
|
||||||
function sendMessageToIframe(message) {
|
|
||||||
if (frameRef.value && frameRef.value.contentWindow)
|
|
||||||
frameRef.value.contentWindow.postMessage(message, '*')
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
sendMessageToIframe,
|
frameRef,
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 监听响应
|
|
||||||
window.addEventListener('message', (event) => {
|
|
||||||
// 处理响应
|
|
||||||
if (event.data)
|
|
||||||
console.log(event.data)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export function useGlobSetting(): Readonly<GlobConfig> {
|
|||||||
shortName: VITE_GLOB_APP_SHORT_NAME,
|
shortName: VITE_GLOB_APP_SHORT_NAME,
|
||||||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||||
uploadUrl: `${VITE_GLOB_API_URL}/infra/file/upload`,
|
uploadUrl: `${VITE_GLOB_API_URL}/infra/file/upload`,
|
||||||
|
uploadUrlFull: `${VITE_GLOB_API_URL}/infra/file/upload-full`,
|
||||||
tenantEnable: VITE_GLOB_APP_TENANT_ENABLE,
|
tenantEnable: VITE_GLOB_APP_TENANT_ENABLE,
|
||||||
captchaEnable: VITE_GLOB_APP_CAPTCHA_ENABLE,
|
captchaEnable: VITE_GLOB_APP_CAPTCHA_ENABLE,
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import { getCommonStoragePrefix, getStorageShortName } from '@/utils/env'
|
|||||||
import { Persistent } from '@/utils/cache/persistent'
|
import { Persistent } from '@/utils/cache/persistent'
|
||||||
import { deepMerge } from '@/utils'
|
import { deepMerge } from '@/utils'
|
||||||
import { ThemeEnum } from '@/enums/appEnum'
|
import { ThemeEnum } from '@/enums/appEnum'
|
||||||
|
import { isInIframe } from '@/utils/iframe'
|
||||||
|
|
||||||
// Initial project configuration
|
// Initial project configuration
|
||||||
export function initAppConfigStore() {
|
export function initAppConfigStore() {
|
||||||
@ -47,6 +48,11 @@ export function initAppConfigStore() {
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果当前界面被当作iframe使用,设置页面全屏显示
|
||||||
|
projCfg.fullContent = isInIframe()
|
||||||
|
projCfg.showSettingButton = !isInIframe()
|
||||||
|
|
||||||
appStore.setProjectConfig(projCfg)
|
appStore.setProjectConfig(projCfg)
|
||||||
|
|
||||||
// init dark mode
|
// init dark mode
|
||||||
|
@ -31,6 +31,10 @@ export function createPermissionGuard(router: Router) {
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const routerToken = to.query.token || to.query.access_token || undefined
|
||||||
|
if (routerToken)
|
||||||
|
userStore.setAccessToken(routerToken as string)
|
||||||
|
|
||||||
const token = userStore.getAccessToken
|
const token = userStore.getAccessToken
|
||||||
|
|
||||||
// Whitelist can be directly entered
|
// Whitelist can be directly entered
|
||||||
|
@ -44,7 +44,7 @@ const setting: ProjectConfig = {
|
|||||||
// 色弱模式
|
// 色弱模式
|
||||||
colorWeak: false,
|
colorWeak: false,
|
||||||
// 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内
|
// 是否取消菜单,顶部,多标签页显示, 用于可能内嵌在别的系统内
|
||||||
fullContent: false,
|
fullContent: true,
|
||||||
// 主题内容宽度
|
// 主题内容宽度
|
||||||
contentMode: ContentEnum.FULL,
|
contentMode: ContentEnum.FULL,
|
||||||
// 是否显示logo
|
// 是否显示logo
|
||||||
@ -155,8 +155,8 @@ const setting: ProjectConfig = {
|
|||||||
// 是否使用全局错误捕获
|
// 是否使用全局错误捕获
|
||||||
useErrorHandle: false,
|
useErrorHandle: false,
|
||||||
// 是否开启回到顶部
|
// 是否开启回到顶部
|
||||||
useOpenBackTop: true,
|
useOpenBackTop: false,
|
||||||
// 是否可以嵌入iframe页面
|
// 是否可以嵌入iframe页面
|
||||||
canEmbedIFramePage: true,
|
canEmbedIFramePage: true,
|
||||||
// 切换界面的时候是否删除未关闭的message及notify
|
// 切换界面的时候是否删除未关闭的message及notify
|
||||||
closeMessageOnSwitch: true,
|
closeMessageOnSwitch: true,
|
||||||
|
18
src/types/axios.d.ts
vendored
18
src/types/axios.d.ts
vendored
@ -54,3 +54,21 @@ export interface UploadFileParams {
|
|||||||
filename?: string
|
filename?: string
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 文件完整信息
|
||||||
|
export interface FileDO {
|
||||||
|
// 编号,数据库自增
|
||||||
|
id: string
|
||||||
|
// 配置编号 关联 FileConfigDO. getId()
|
||||||
|
configId: string
|
||||||
|
// 原文件名
|
||||||
|
name: string
|
||||||
|
// 路径,即文件名
|
||||||
|
path: string
|
||||||
|
// 访问地址
|
||||||
|
url: string
|
||||||
|
// 文件的 MIME 类型,例如 "application/ octet-stream"
|
||||||
|
type: string
|
||||||
|
// 文件大小
|
||||||
|
size: string
|
||||||
|
}
|
||||||
|
2
src/types/config.d.ts
vendored
2
src/types/config.d.ts
vendored
@ -157,6 +157,8 @@ export interface GlobConfig {
|
|||||||
apiUrl: string
|
apiUrl: string
|
||||||
// Upload url
|
// Upload url
|
||||||
uploadUrl?: string
|
uploadUrl?: string
|
||||||
|
// Upload url 返回完整文件信息
|
||||||
|
uploadUrlFull?: string
|
||||||
// Service interface url prefix
|
// Service interface url prefix
|
||||||
urlPrefix?: string
|
urlPrefix?: string
|
||||||
// Project abbreviation
|
// Project abbreviation
|
||||||
|
@ -4,10 +4,14 @@ import { getAccessToken } from '@/utils/auth'
|
|||||||
/**
|
/**
|
||||||
* 获取文件预览地址
|
* 获取文件预览地址
|
||||||
* @param fileId
|
* @param fileId
|
||||||
|
* @param wopi_client
|
||||||
|
* @param wopi_server
|
||||||
*/
|
*/
|
||||||
export async function initFilePreviewUrl(fileId: string | number): Promise<string> {
|
export async function initFilePreviewUrl(fileId: string | number, wopi_client?: string | undefined, wopi_server?: string | undefined): Promise<string> {
|
||||||
const wopi_client = await getConfigKey('wopi_client_addr')
|
if (!wopi_client || !wopi_server) {
|
||||||
const wopi_server = await getConfigKey('wopi_server_ip_addr')
|
wopi_client = await getConfigKey('wopi_client_addr')
|
||||||
|
wopi_server = await getConfigKey('wopi_server_ip_addr')
|
||||||
|
}
|
||||||
const wopi = `${wopi_server}/admin-api/infra/file/preview/wopi/files/${fileId}?access_token=${getAccessToken()}`
|
const wopi = `${wopi_server}/admin-api/infra/file/preview/wopi/files/${fileId}?access_token=${getAccessToken()}`
|
||||||
return `${wopi_client}?lang=zh-cn&WOPISrc=${encodeURIComponent(wopi)}`
|
return `${wopi_client}?lang=zh-cn&WOPISrc=${encodeURIComponent(wopi)}`
|
||||||
}
|
}
|
||||||
|
10
src/utils/iframe.ts
Normal file
10
src/utils/iframe.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// 检测当前页面是否被嵌套在 iframe 中
|
||||||
|
export function isInIframe(): boolean {
|
||||||
|
try {
|
||||||
|
return window.self !== window.top
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// 如果跨域访问被拒绝,catch 会捕获异常
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
25
src/views/infra/bookmark/event.data.ts
Normal file
25
src/views/infra/bookmark/event.data.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 第三方调用者发送事件格式
|
||||||
|
*/
|
||||||
|
export type ReceiveEventData =
|
||||||
|
| {
|
||||||
|
MessageId: 'OPEN_FILE'
|
||||||
|
Values: Blob | File
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
MessageId: 'ACTION_SAVE'
|
||||||
|
Values?: null
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
MessageId: never
|
||||||
|
Values?: never
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器主动回调第三方
|
||||||
|
*/
|
||||||
|
export type CallbackEventData =
|
||||||
|
| {
|
||||||
|
Action: 'SAVE'
|
||||||
|
Values?: never
|
||||||
|
}
|
@ -1,215 +1,237 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button } from 'ant-design-vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { ref, watchEffect } from 'vue'
|
import { useEventListener } from '@vueuse/core'
|
||||||
import { useFileDialog } from '@vueuse/core'
|
import axios from 'axios'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { IFrame } from '@/components/IFrame'
|
import { IFrame } from '@/components/IFrame'
|
||||||
import { FileSelectModal } from '@/components/FileSelect'
|
|
||||||
import { useModal } from '@/components/Modal'
|
import { isInIframe } from '@/utils/iframe'
|
||||||
import type { FileVO } from '@/api/infra/file'
|
|
||||||
import { initFilePreviewUrl } from '@/utils/file/preview'
|
|
||||||
import type { BookmarkGenData, BookmarkQueryRespVo, PictureExData, TextExData } from '@/api/infra/bookmark'
|
|
||||||
import { getAllBookmarks, getDoBookmarkReplace } from '@/api/infra/bookmark'
|
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { convertFileToBase64 } from '@/utils/file/base64Conver'
|
import { uploadOneFile } from '@/api/base/upload'
|
||||||
|
import { initFilePreviewUrl } from '@/utils/file/preview'
|
||||||
|
|
||||||
|
import { getConfigKey } from '@/api/infra/config'
|
||||||
|
import type { FileDO } from '@/types/axios'
|
||||||
|
import { useGlobSetting } from '@/hooks/setting'
|
||||||
|
import { getAccessToken } from '@/utils/auth'
|
||||||
|
|
||||||
defineOptions({ name: 'BookmarkReplace' })
|
defineOptions({ name: 'BookmarkReplace' })
|
||||||
|
const isEmbed = isInIframe()
|
||||||
|
const globSetting = useGlobSetting()
|
||||||
|
|
||||||
const previewUrl = ref<string | undefined>(undefined)
|
const previewUrl = ref<string | undefined>(undefined)
|
||||||
|
const frameComponent = ref<{ frameRef: HTMLIFrameElement } | null>(null)
|
||||||
const [registerModal, { openModal }] = useModal()
|
|
||||||
|
|
||||||
const currentEditFile = ref<FileVO | undefined>(undefined)
|
|
||||||
|
|
||||||
async function handleSelect(file: FileVO) {
|
|
||||||
currentEditFile.value = file
|
|
||||||
|
|
||||||
previewUrl.value = await initFilePreviewUrl(file.id)
|
|
||||||
|
|
||||||
await handleQueryBookmark()
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookmarks = ref<BookmarkQueryRespVo[]>([])
|
|
||||||
|
|
||||||
interface FormItemType {
|
|
||||||
name: string
|
|
||||||
type: 'PICTURE_DESC' | 'PICTURE' | 'TEXT'
|
|
||||||
value: string | File
|
|
||||||
fileName: string
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const formItems = ref<FormItemType[]>([])
|
|
||||||
const initFinish = ref<boolean>(false)
|
|
||||||
watchEffect(() => {
|
|
||||||
if (currentEditFile.value) {
|
|
||||||
bookmarks.value = []
|
|
||||||
formItems.value = []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { createMessage } = useMessage()
|
const { createMessage } = useMessage()
|
||||||
|
|
||||||
async function handleQueryBookmark() {
|
const wopi_client = ref(undefined)
|
||||||
bookmarks.value = []
|
const wopi_server = ref(undefined)
|
||||||
formItems.value = []
|
|
||||||
initFinish.value = false
|
|
||||||
if (currentEditFile.value)
|
|
||||||
bookmarks.value = await getAllBookmarks(currentEditFile.value.id)
|
|
||||||
|
|
||||||
else
|
const editStatus = ref<{ Modified: boolean }>({ Modified: false })
|
||||||
createMessage.error('请选择文件!')
|
const currentEditFile = ref<FileDO | undefined>(undefined)
|
||||||
|
onMounted(async () => {
|
||||||
|
wopi_client.value = await getConfigKey('wopi_client_addr')
|
||||||
|
wopi_server.value = await getConfigKey('wopi_server_ip_addr')
|
||||||
|
|
||||||
for (const bk of bookmarks.value)
|
/**
|
||||||
formItems.value.push({ name: bk.name, type: bk.type, value: '', fileName: '', width: 0, height: 0 })
|
* 接收消息
|
||||||
|
*/
|
||||||
|
useEventListener(window, 'message', async (e: MessageEvent) => {
|
||||||
|
logEvent(e)
|
||||||
|
|
||||||
initFinish.value = true
|
let data: any = null
|
||||||
}
|
|
||||||
|
|
||||||
const { files, open, reset, onChange } = useFileDialog({
|
if (typeof e.data === 'string')
|
||||||
accept: 'image/*',
|
data = JSON.parse(e.data)
|
||||||
directory: false,
|
|
||||||
|
if (typeof e.data === 'object')
|
||||||
|
data = e.data
|
||||||
|
|
||||||
|
const ActionId = data.ActionId
|
||||||
|
const MessageId = data.MessageId
|
||||||
|
|
||||||
|
// 第三方调用发送的Event
|
||||||
|
switch (ActionId) {
|
||||||
|
case 'OPEN_FILE':
|
||||||
|
await handleFileBinary(e)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
console.warn(`MessageId:${MessageId}`)
|
||||||
|
// WOPI Client发送消息
|
||||||
|
switch (MessageId) {
|
||||||
|
// 更新文档是否被编辑
|
||||||
|
case 'Doc_ModifiedStatus':
|
||||||
|
handleUpdateModifiedStatus(e)
|
||||||
|
break
|
||||||
|
// 用户执行了保存报错
|
||||||
|
case 'UI_Save':
|
||||||
|
console.warn('UI_Save')
|
||||||
|
handleUserSaveOp()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let currentEdit
|
/**
|
||||||
|
* 更新是否编辑状态
|
||||||
function handleSelectPicture(item: FormItemType) {
|
* 保存后会更新是否编辑为否,正常编辑后会更新是否编辑为是
|
||||||
reset()
|
* 用于区分是否要向第三方发送保存文件的回调
|
||||||
open()
|
* @param e
|
||||||
currentEdit = item
|
*/
|
||||||
|
function handleUpdateModifiedStatus(e: MessageEvent) {
|
||||||
|
editStatus.value.Modified = JSON.parse(e.data).Values.Modified
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(() => {
|
let pollingInterval: NodeJS.Timeout | null = null
|
||||||
if (files.value?.length && files.value?.length > 0 && currentEdit) {
|
let lastCallTime: number | null = null
|
||||||
const file = files.value.item(0)
|
|
||||||
if (file) {
|
async function handleUserSaveOp() {
|
||||||
currentEdit.value = file
|
console.warn('handleUserSaveOp')
|
||||||
currentEdit.fileName = file.name
|
|
||||||
}
|
// 情况 1: 如果当前状态未修改,直接发送消息
|
||||||
|
if (!editStatus.value.Modified) {
|
||||||
|
// 下载文件并发送
|
||||||
|
downloadAndSendCurrentEditFile()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const router = useRouter()
|
// 情况 2: 已处于修改状态,开始/重置等待流程
|
||||||
|
// 更新最后一次调用时间戳
|
||||||
|
lastCallTime = Date.now()
|
||||||
|
|
||||||
async function handleGenerateNew() {
|
// 清除已有定时器(实现调用重置)
|
||||||
if (currentEditFile.value) {
|
if (pollingInterval) {
|
||||||
const data: BookmarkGenData[] = []
|
clearInterval(pollingInterval)
|
||||||
for (const item of formItems.value) {
|
pollingInterval = null
|
||||||
if (!item.value)
|
}
|
||||||
continue
|
|
||||||
|
|
||||||
if (item.type === 'TEXT') {
|
// 启动新的轮询检测
|
||||||
data.push({ name: item.name, extData: {
|
pollingInterval = setInterval(() => {
|
||||||
type: 'TEXT',
|
// 子情况 2a: 状态已恢复未修改
|
||||||
value: item.value as string,
|
if (!editStatus.value.Modified) {
|
||||||
} as TextExData })
|
downloadAndSendCurrentEditFile()
|
||||||
}
|
cleanup()
|
||||||
if (item.type === 'PICTURE') {
|
|
||||||
const file = item.value as File
|
|
||||||
data.push({ name: item.name, extData: {
|
|
||||||
type: 'PICTURE',
|
|
||||||
value: await convertFileToBase64(item.value as File),
|
|
||||||
width: item.width,
|
|
||||||
height: item.height,
|
|
||||||
pictureName: file.name,
|
|
||||||
dataType: 'BASE64',
|
|
||||||
} as PictureExData })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === 'PICTURE_DESC') {
|
|
||||||
const file = item.value as File
|
|
||||||
data.push({ name: item.name, extData: {
|
|
||||||
type: 'PICTURE_DESC',
|
|
||||||
value: await convertFileToBase64(item.value as File),
|
|
||||||
pictureName: file.name,
|
|
||||||
dataType: 'BASE64',
|
|
||||||
} as PictureExData })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length <= 0) {
|
|
||||||
createMessage.error('请填写替换值!')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await getDoBookmarkReplace(currentEditFile.value.id, data)
|
// 子情况 2b: 检测超时(10秒逻辑)
|
||||||
const previewUrl = await initFilePreviewUrl(result)
|
const currentTime = Date.now()
|
||||||
const encode = encodeURIComponent(previewUrl)
|
if (lastCallTime && currentTime - lastCallTime >= 10000) {
|
||||||
await router.push(`/infra/file/file-preview?url=${encode}`)
|
createMessage.error('保存操作执行超时!')
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}, 500) // 每 500ms 检测一次
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
function cleanup() {
|
||||||
|
if (pollingInterval) {
|
||||||
|
clearInterval(pollingInterval)
|
||||||
|
pollingInterval = null
|
||||||
}
|
}
|
||||||
|
lastCallTime = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载当前编辑的文件,发送给调用者
|
||||||
|
*/
|
||||||
|
async function downloadAndSendCurrentEditFile() {
|
||||||
|
console.warn('downloadAndSendCurrentEditFile')
|
||||||
|
|
||||||
|
if (!currentEditFile.value)
|
||||||
|
return
|
||||||
|
try {
|
||||||
|
// 1. 发送请求(关键配置 responseType: 'arraybuffer')
|
||||||
|
const response = await axios.get(`${globSetting.apiUrl}/infra/file/download/${currentEditFile.value.id}`, {
|
||||||
|
responseType: 'arraybuffer', // [!code focus]
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${getAccessToken()}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 验证响应状态
|
||||||
|
if (response.status !== 200)
|
||||||
|
createMessage.error(`请求失败,状态码:${response.status}`)
|
||||||
|
|
||||||
|
// 3. 获取 ArrayBuffer 数据
|
||||||
|
const arrayBuffer: ArrayBuffer = response.data
|
||||||
|
|
||||||
|
sendMessageToCaller({
|
||||||
|
ActionId: 'SAVE_FILE',
|
||||||
|
Payload: {
|
||||||
|
name: currentEditFile.value.name,
|
||||||
|
buffer: arrayBuffer,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('文件下载失败:', error)
|
||||||
|
createMessage.error('文件下载失败,请查看控制台日志')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方上传文件
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
async function handleFileBinary(e: MessageEvent) {
|
||||||
|
const { name, type, buffer } = e.data.Payload
|
||||||
|
const blob = new Blob([buffer], { type })
|
||||||
|
const uploadResult = await uploadOneFile({
|
||||||
|
filename: name,
|
||||||
|
file: new File([blob], name, { type }),
|
||||||
|
})
|
||||||
|
currentEditFile.value = uploadResult.data.data as FileDO
|
||||||
|
previewUrl.value = await initFilePreviewUrl(currentEditFile.value.id, wopi_client.value, wopi_server.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息给WopiClient
|
||||||
|
* 用于执行书签相关操作
|
||||||
|
*/
|
||||||
|
// function sendMessageToWopiClient(data: any) {
|
||||||
|
// if (!frameComponent.value?.frameRef.contentWindow)
|
||||||
|
// createMessage.error('WOPI Client iframe 未就绪')
|
||||||
|
// try {
|
||||||
|
// frameComponent.value?.frameRef.contentWindow?.postMessage(data, '*')
|
||||||
|
// }
|
||||||
|
// catch (e) {
|
||||||
|
// console.error(e)
|
||||||
|
// createMessage.error(`消息发送失败,查看控制台日志!`)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息给第三方调用者
|
||||||
|
* 用于转发部分WopiClient回调
|
||||||
|
*/
|
||||||
|
function sendMessageToCaller(data: any) {
|
||||||
|
try {
|
||||||
|
const targetWindow = window.parent
|
||||||
|
targetWindow.postMessage(data, '*')
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
createMessage.error(`消息发送失败,查看控制台日志!`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logEvent(e: MessageEvent) {
|
||||||
|
console.log('=============receive message start=======')
|
||||||
|
console.log(e.data)
|
||||||
|
console.log(e.origin)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col p-2 pt-4">
|
<div class="flex flex-col" :class="{ 'p-2 pt-4': !isEmbed }">
|
||||||
<div class="h-[calc(100vh-105px)] w-full flex flex-row">
|
<div class="w-full flex flex-row" :class="{ 'h-[calc(100vh)]': isEmbed, 'h-[calc(100vh-105px)]': !isEmbed }">
|
||||||
<div class="mr-2 h-full w-25% flex flex-col">
|
<div class="w-full flex flex-row bg-white dark:bg-black">
|
||||||
<div class="mb-2 flex flex-row items-center justify-between rounded bg-white px-2 py-2 dark:bg-black">
|
<i-frame v-if="previewUrl" ref="frameComponent" class="w-full bg-white dark:bg-black" :src="previewUrl" :height="isEmbed ? 'calc(100vh)' : 'calc(100vh) - 105px'" />
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<Button type="primary" class="mr-2" @click="openModal()">
|
|
||||||
选择文件
|
|
||||||
</Button>
|
|
||||||
: {{ currentEditFile?.name }}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<Button @click="handleQueryBookmark">
|
|
||||||
获取书签
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="h-full w-full overflow-scroll rounded bg-white p-4 dark:bg-black">
|
|
||||||
<a-form
|
|
||||||
v-if="initFinish"
|
|
||||||
class="w-full"
|
|
||||||
layout="vertical"
|
|
||||||
:label-col="{ span: 16 }"
|
|
||||||
>
|
|
||||||
<a-col v-for="(item) in formItems" :key="item.name">
|
|
||||||
<a-form-item :label="item.name">
|
|
||||||
<a-radio-group v-model:value="item.type" :disabled="item.type === 'PICTURE_DESC'" @change="item.value = null">
|
|
||||||
<a-radio value="TEXT">
|
|
||||||
文本
|
|
||||||
</a-radio>
|
|
||||||
<a-radio value="PICTURE">
|
|
||||||
图片
|
|
||||||
</a-radio>
|
|
||||||
<a-radio v-if="item.type === 'PICTURE_DESC'" value="PICTURE_DESC" :disabled="true">
|
|
||||||
图片(替换)
|
|
||||||
</a-radio>
|
|
||||||
</a-radio-group>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="替换值">
|
|
||||||
<a-input v-if="item.type === 'TEXT'" v-model:value="item.value" />
|
|
||||||
<a-button v-if="item.type.includes('PICTURE')" @click="handleSelectPicture(item)">
|
|
||||||
选择图片
|
|
||||||
</a-button>
|
|
||||||
<p v-if="item.type.includes('PICTURE') && item.value && item?.fileName" class="overflow-hidden">
|
|
||||||
{{ item?.fileName }}
|
|
||||||
</p>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="item.type === 'PICTURE'" label="图片 宽(px)">
|
|
||||||
<a-input v-model:value="item.width" type="number" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="item.type === 'PICTURE'" label="图片 高(px)">
|
|
||||||
<a-input v-model:value="item.height" type="number" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-divider />
|
|
||||||
</a-col>
|
|
||||||
</a-form>
|
|
||||||
|
|
||||||
<a-button v-if="formItems.length > 0" type="primary" @click="handleGenerateNew">
|
|
||||||
生成替换后文件
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-75% flex flex-row bg-white dark:bg-black">
|
|
||||||
<i-frame v-if="previewUrl" class="w-full bg-white dark:bg-black" :src="previewUrl" height="calc(100vh - 105px)" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FileSelectModal v-bind="$attrs" @register="registerModal" @select="handleSelect" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user