【VUE】localStorage、indexedDB跨域数据操作实战笔记

由于业务需求,最近研究localStorage、indexedDB等如何跨域进行CRUD管理,经过一番研究,封装了如下代码并做个笔记

环境

  • vue: ^3.3.4

实战

发送端(即触发站点)

App.vue中引入CrossDomainStorage组件(后面有实现过程)

<script setup>
import { ref } from 'vue'
import CrossDomainStorage from "@/components/CrossDomainStorage/index.vue";
const crossDomainStorageRef = ref(null)

function sendTest() {
  if (crossDomainStorageRef.value){
    crossDomainStorageRef.value.sendMessage("getItem", {key:'APP_THEME_SCREEN'})
  }
}
</script>

<template>
  <div>
    <button @click="sendTest">测试</button>
    <CrossDomainStorage :src="'http://xxxx.xxx/'" ref="crossDomainStorageRef"/>
  </div>
</template>

接收端(即目标站点)

为了方便直接在App.vue中实践

<script setup>
import ParentMsgListener from '@/utils/parentMsgListener'

const parentMsgListener = new ParentMsgListener()
parentMsgListener.addPermissionModule({ // 设置权限key对应处理方法
	localStorage: {
		setItem: localStorage.setItem,
		getItem: localStorage.getItem,
	},
	asyncTest: {
		text: async ()=> await 'asyncValue',
		text2: () => 'testValue',
	}
});
parentMsgListener.addPermission([ // 设置可操作的对象列表
	'localStorage'
])
parentMsgListener.start();
</script>

实现代码

CrossDomainStorage组件

<script setup lang="ts">
import {ref, reactive, onMounted} from 'vue';

defineOptions({
  name: 'CrossDomainStorage'
});

const iframeRef = ref();
const emit = defineEmits(['onLoad','response'])
const props = defineProps({
  src: {type: String, default:()=>"", required:true}
});
const status = ref(false)
const iframeStyles = reactive({
  position: 'fixed',
  top:0,
  left:0,
  width: 0,
  height: 0,
  zIndex:-1
});

window.addEventListener('message', function (e) {
  if (e.data && e.data.request){
    console.log('[发送端]从iframe获取数据', e.data)
    emit('response', e.data||null)
  }
});

function sendMessage(method: string, param: object, key: string) {
  if (status.value===false)return;
  key = key?key:'localStorage'
  if (iframeRef?.value){
    const iframeNode = iframeRef.value.contentWindow;
    let request = {key, method, param}
    console.log('[发送端]向iframe发送request:', request)
    setTimeout(function () {
      iframeNode.postMessage(request, '*')
    }, 500)
  }
}

defineExpose({
  sendMessage: (method: string, param: object, key: string)=>sendMessage(method, param, key)
})

onMounted(()=>{
  if (!!iframeRef.value && !!iframeRef.value.contentWindow){
    iframeRef.value.onload = function () {
      status.value = true;
      emit('onLoad', true)
    }
  }
})
</script>

<template>
  <iframe ref="iframeRef" :src="props.src"
          :style="iframeStyles"
          frameborder="0"
          scrolling="no"/>
</template>

parentMsgListener.ts封装消息管理类

// src/utils/parentMsgListener.ts
const messageLog = function (log, ...arg){
    arg.unshift(`[接收端]`, log)
    console.log.apply(console, arg)
}

interface permissionOptions {
    key: string[],
    module: object
}

/**
 * 监听响应拦截
 *
 * @param {object}   e          接收数据
 * @param {array}   permission 权限列表
 * @param {function} response   接收回调
 *
 * @return {void}
 */
const listenerResponse = async function (e, permission: permissionOptions, response) {
    if (e.data && !!e.data.key && permission.key.includes(e.data.key)){
        let result = undefined;
        let lib = permission.module[e.data.key] || undefined;
        let method = e.data?.method || '';
        let param  = e.data?.param||{};
        if (!!lib){
            let _param = [];
            for (let key in param){
                _param.push(`'${param[key]}'`)
            }
            try { // 调用非js内置方法且兼容异步调用处理方法
                result = await ((lib[method]).apply(lib[method], Object.values(param)));
            }catch (error) { // 调用js内置方法
                result = eval(`${e.data.key}.${method}(${_param.join(',')})`);
            }
        }
        messageLog('response:', result)
        response({
            request: e.data,
            response: result
        }, '*')
    }
}

class ParentMsgListener {

    /**
     * 消息调用权限对应处理
     * @var {object}
     */
    private permissionMap = {};

    /**
     * 消息允许调用权限
     * @var {string[]}
     */
    private permission = []

    constructor(permission) {
        if (permission && permission.length>0){
            this.addPermission(permission)
        }
    }

    /**
     * 添加授权权限key对应处理模块
     *
     * @param {string|object} name 权限key
     * @param {function} fn 权限key处理方法
     *
     * @return this
     */
    addPermissionModule(name:string|object, fn){
        if (!fn && typeof name === 'object'){ // 批量导入
            for (let moduleKey in name){
                this.addPermissionModule.call(this, moduleKey, name[moduleKey]);
            }
        }else if(typeof name === 'string' && !!fn) { // 逐个导入
            this.permissionMap[name] = fn;
        }
        return this;
    }

    /**
     * 添加授权权限
     *
     * @param {string|string[]} permissionKey 权限key
     *
     * @return this
     */
    addPermission(permissionKey: string|string[]){
        if (permissionKey instanceof Array){
            let that = this;
            permissionKey.forEach((key)=>{
                that.permission.push(key)
            })
        }else{
            this.permission.push(permissionKey)
        }
        return this;
    }

    /**
     * 发送消息
     *
     * @param {object} message      发送内容
     * @param {string} targetOrigin 默认:*
     *
     * @return {void}
     */
    sendMessage(message:any, targetOrigin:string = '*'){
        messageLog('发送消息', message)
        window.parent.postMessage(message, targetOrigin)
    }

    /**
     * 启动监听
     */
    start(){
        window.addEventListener(
            'message',
            (e)=>listenerResponse(e, {
                key: this.permission,
                module: this.permissionMap
            }, this.sendMessage)
        )
    }
}

export default ParentMsgListener

你可能感兴趣的:(Web前端,vue.js,笔记,前端)