diff --git a/src/main/utils/common.ts b/src/main/utils/common.ts index c85087a1..36641f50 100644 --- a/src/main/utils/common.ts +++ b/src/main/utils/common.ts @@ -8,7 +8,7 @@ import { gunzipSync, gzipSync, strFromU8 } from 'fflate' import FormData from 'form-data' import fs from 'fs-extra' import { IPicGo } from 'piclist' -import { isReactive, isRef, toRaw, unref } from 'vue' +import { isProxy, isRef, toRaw, unref } from 'vue' import { configPaths } from '~/utils/configPaths' import { IShortUrlServer } from '~/utils/enum' @@ -17,17 +17,39 @@ import { IShortUrlServer } from '~/utils/enum' * get raw data from reactive or ref */ export const getRawData = (args: any): any => { - if (isRef(args)) return unref(args) - if (isReactive(args)) return toRaw(args) - if (Array.isArray(args)) return args.map(getRawData) - if (typeof args === 'object' && args !== null) { - const data = {} as Record - for (const key in args) { - data[key] = getRawData(args[key]) + if (args === null || typeof args !== 'object') { + return args + } + const raw = isRef(args) ? unref(args) : isProxy(args) ? toRaw(args) : args + if (raw instanceof Date) return new Date(raw) + if (raw instanceof RegExp) return new RegExp(raw) + if (raw instanceof Map) { + const result = new Map() + raw.forEach((value, key) => { + result.set(getRawData(key), getRawData(value)) + }) + return result + } + if (raw instanceof Set) { + const result = new Set() + raw.forEach(value => { + result.add(getRawData(value)) + }) + return result + } + if (Array.isArray(raw)) { + return raw.map(item => getRawData(item)) + } + if (typeof raw === 'object') { + const data: Record = {} + for (const key in raw) { + if (Object.prototype.hasOwnProperty.call(raw, key)) { + data[key] = getRawData(raw[key]) + } } return data } - return args + return raw } const getExtension = (fileName: string) => path.extname(fileName).slice(1) diff --git a/src/preload/index.ts b/src/preload/index.ts index 7d40ee74..80384e7e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -4,21 +4,43 @@ import path from 'node:path' import { clipboard, contextBridge, ipcRenderer, IpcRendererEvent, webFrame, webUtils } from 'electron' import fs from 'fs-extra' import mime from 'mime' -import { isReactive, isRef, toRaw, unref } from 'vue' +import { isProxy, isRef, toRaw, unref } from 'vue' import yaml from 'yaml' export const getRawData = (args: any): any => { - if (isRef(args)) return unref(args) - if (isReactive(args)) return toRaw(args) - if (Array.isArray(args)) return args.map(getRawData) - if (typeof args === 'object' && args !== null) { - const data = {} as Record - for (const key in args) { - data[key] = getRawData(args[key]) + if (args === null || typeof args !== 'object') { + return args + } + const raw = isRef(args) ? unref(args) : isProxy(args) ? toRaw(args) : args + if (raw instanceof Date) return new Date(raw) + if (raw instanceof RegExp) return new RegExp(raw) + if (raw instanceof Map) { + const result = new Map() + raw.forEach((value, key) => { + result.set(getRawData(key), getRawData(value)) + }) + return result + } + if (raw instanceof Set) { + const result = new Set() + raw.forEach(value => { + result.add(getRawData(value)) + }) + return result + } + if (Array.isArray(raw)) { + return raw.map(item => getRawData(item)) + } + if (typeof raw === 'object') { + const data: Record = {} + for (const key in raw) { + if (Object.prototype.hasOwnProperty.call(raw, key)) { + data[key] = getRawData(raw[key]) + } } return data } - return args + return raw } function sendToMain(channel: string, ...args: any[]) { diff --git a/src/renderer/utils/common.ts b/src/renderer/utils/common.ts index dfdaedd9..13a15068 100644 --- a/src/renderer/utils/common.ts +++ b/src/renderer/utils/common.ts @@ -1,20 +1,39 @@ -import { isReactive, isRef, toRaw, unref } from 'vue' +import { isProxy, isRef, toRaw, unref } from 'vue' -/** - * get raw data from reactive or ref - */ export const getRawData = (args: any): any => { - if (isRef(args)) return unref(args) - if (isReactive(args)) return toRaw(args) - if (Array.isArray(args)) return args.map(getRawData) - if (typeof args === 'object' && args !== null) { - const data = {} as Record - for (const key in args) { - data[key] = getRawData(args[key]) + if (args === null || typeof args !== 'object') { + return args + } + const raw = isRef(args) ? unref(args) : isProxy(args) ? toRaw(args) : args + if (raw instanceof Date) return new Date(raw) + if (raw instanceof RegExp) return new RegExp(raw) + if (raw instanceof Map) { + const result = new Map() + raw.forEach((value, key) => { + result.set(getRawData(key), getRawData(value)) + }) + return result + } + if (raw instanceof Set) { + const result = new Set() + raw.forEach(value => { + result.add(getRawData(value)) + }) + return result + } + if (Array.isArray(raw)) { + return raw.map(item => getRawData(item)) + } + if (typeof raw === 'object') { + const data: Record = {} + for (const key in raw) { + if (Object.prototype.hasOwnProperty.call(raw, key)) { + data[key] = getRawData(raw[key]) + } } return data } - return args + return raw } export const isUrl = (url: string): boolean => { diff --git a/tests/getraw.test.ts b/tests/getraw.test.ts new file mode 100644 index 00000000..a9ec59e7 --- /dev/null +++ b/tests/getraw.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from 'vitest' +import { isProxy, reactive, ref, shallowReactive } from 'vue' + +import { getRawData } from '../src/renderer/utils/common' + +describe('getRawData 深度扩展工具测试', () => { + it('应当能处理基本类型和 null', () => { + expect(getRawData(42)).toBe(42) + expect(getRawData('hello')).toBe('hello') + expect(getRawData(true)).toBe(true) + expect(getRawData(null)).toBeNull() + expect(getRawData(undefined)).toBeUndefined() + }) + + it('应当能处理简单的 Ref 和 Reactive 对象', () => { + const numRef = ref(100) + const strReactive = reactive({ text: 'vue' }) + + expect(getRawData(numRef)).toBe(100) + const result = getRawData(strReactive) + expect(result).toEqual({ text: 'vue' }) + expect(isProxy(result)).toBe(false) + }) + + it('应当能处理嵌套的数组和对象', () => { + const nested = reactive({ + list: [ref(1), reactive({ val: 2 }), 3], + info: { + name: ref('test'), + details: reactive({ age: 30 }), + }, + }) + + const result = getRawData(nested) + + expect(result).toEqual({ + list: [1, { val: 2 }, 3], + info: { + name: 'test', + details: { age: 30 }, + }, + }) + expect(isProxy(result)).toBe(false) + expect(isProxy(result.info)).toBe(false) + expect(isProxy(result.list[1])).toBe(false) + }) + + it('应当能处理深层嵌套的 Map', () => { + const map = reactive(new Map()) + const keyRef = ref('info') + const valReactive = reactive({ id: 1, tags: ref(['vue', 'ts']) }) + + map.set(keyRef, valReactive) + + const result = getRawData(map) + + expect(result).toBeInstanceOf(Map) + expect(result.has('info')).toBe(true) + const info = result.get('info') + expect(info.id).toBe(1) + expect(info.tags).toEqual(['vue', 'ts']) + expect(isProxy(info)).toBe(false) + }) + + it('应当能处理深层嵌套的 Set', () => { + const set = reactive(new Set()) + const item = reactive({ name: 'test' }) + set.add(item) + set.add(ref(100)) + + const result = getRawData(set) + + expect(result).toBeInstanceOf(Set) + const items = Array.from(result) + expect(items).toContainEqual({ name: 'test' }) + expect(items).toContain(100) + expect(isProxy(items[0])).toBe(false) + }) + + it('应当处理混合嵌套结构 (Object + Array + Set)', () => { + const complex = reactive({ + users: new Set([reactive({ id: 1, meta: ref('admin') }), { id: 2, meta: 'user' }]), + config: [ref(true), { active: ref(false) }], + }) + + const result = getRawData(complex) + + expect(result.users).toBeInstanceOf(Set) + const userArr = Array.from(result.users) as any[] + expect(userArr[0].meta).toBe('admin') + expect(result.config[0]).toBe(true) + expect(result.config[1].active).toBe(false) + }) + + it('应当正确克隆 Date 和 RegExp', () => { + const date = new Date('2024-01-01') + const reg = /test/g + const data = reactive({ date, reg }) + + const result = getRawData(data) + + expect(result.date).toBeInstanceOf(Date) + expect(result.date.getFullYear()).toBe(2024) + expect(result.date).not.toBe(date) + expect(result.reg).toBeInstanceOf(RegExp) + expect(result.reg.test('test')).toBe(true) + }) + + it('应当处理 shallowReactive', () => { + const shallow = shallowReactive({ + nested: { a: 1 }, + }) + const result = getRawData(shallow) + expect(result.nested.a).toBe(1) + expect(result).not.toBe(shallow) + }) +})