import { onUnmounted } from 'vue'; import html2canvas from 'html2canvas' import { getUUID, httpErrorHandle, fetchRouteParamsLocation, base64toFile, JSONStringify, JSONParse } from '@/utils' import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' import { EditCanvasTypeEnum, ChartEditStoreEnum, ProjectInfoEnum, ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d' import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore' import { StylesSetting } from '@/components/Pages/ChartItemSetting' import { useSystemStore } from '@/store/modules/systemStore/systemStore' import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore' import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d' import { fetchChartComponent, fetchConfigComponent, createComponent } from '@/packages/index' import { saveInterval } from '@/settings/designSetting' import throttle from 'lodash/throttle' // 接口状态 import { ResultEnum } from '@/enums/httpEnum' // 接口 import { saveProjectApi, fetchProjectApi, uploadFile, updateProjectApi } from '@/api/path' // 画布枚举 import { SyncEnum } from '@/enums/editPageEnum' import { CreateComponentType, CreateComponentGroupType, ConfigType } from '@/packages/index.d' import { BaseEvent, EventLife } from '@/enums/eventEnum' import { PublicGroupConfigClass } from '@/packages/public/publicConfig' import merge from 'lodash/merge' /** * * 画布-版本升级对旧数据无法兼容的补丁 * @param object */ const canvasVersionUpdatePolyfill = (object: any) => { return object } /** * * 组件-版本升级对旧数据无法兼容的补丁 * @param newObject * @param sources */ const componentVersionUpdatePolyfill = (newObject: any, sources: any) => { try { // 判断是否是组件 if (sources.id) { // 处理事件补丁 const hasVnodeBeforeMount = 'vnodeBeforeMount' in sources.events const hasVnodeMounted = 'vnodeMounted' in sources.events if (hasVnodeBeforeMount) { newObject.events.advancedEvents.vnodeBeforeMount = sources?.events.vnodeBeforeMount } if (hasVnodeMounted) { newObject.events.advancedEvents.vnodeMounted = sources?.events.vnodeMounted } if (hasVnodeBeforeMount || hasVnodeMounted) { sources.events = { baseEvent: { [BaseEvent.ON_CLICK]: undefined, [BaseEvent.ON_DBL_CLICK]: undefined, [BaseEvent.ON_MOUSE_ENTER]: undefined, [BaseEvent.ON_MOUSE_LEAVE]: undefined }, advancedEvents: { [EventLife.VNODE_MOUNTED]: undefined, [EventLife.VNODE_BEFORE_MOUNT]: undefined }, interactEvents: [] } } return newObject } } catch (error) { return newObject } } /** * * 合并处理 * @param newObject 新的模板数据 * @param sources 新拿到的数据 * @returns object */ const componentMerge = (newObject: any, sources: any, notComponent = false) => { // 处理组件补丁 componentVersionUpdatePolyfill(newObject, sources) // 非组件不处理 if (notComponent) return merge(newObject, sources) // 组件排除 newObject const option = sources.option if (!option) return merge(newObject, sources) // 为 undefined 的 sources 来源对象属性将被跳过详见 https://www.lodashjs.com/docs/lodash.merge sources.option = undefined if (option) { return { ...merge(newObject, sources), option: option } } } // 请求处理 export const useSync = () => { const chartEditStore = useChartEditStore() const chartHistoryStore = useChartHistoryStore() const systemStore = useSystemStore() const chartLayoutStore = useChartLayoutStore() /** * * 组件动态注册 * @param projectData 项目数据 * @param isReplace 是否替换数据 * @returns */ const updateComponent = async (projectData: ChartEditStorage, isReplace = false, changeId = false) => { if (isReplace) { // 清除列表 chartEditStore.componentList = [] // 清除历史记录 chartHistoryStore.clearBackStack() chartHistoryStore.clearForwardStack() } // 画布补丁处理 projectData.editCanvasConfig = canvasVersionUpdatePolyfill(projectData.editCanvasConfig) // 列表组件注册 projectData.componentList.forEach(async (e: CreateComponentType | CreateComponentGroupType) => { const intComponent = (target: CreateComponentType) => { if (!window['$vue'].component(target.chartConfig.chartKey)) { window['$vue'].component(target.chartConfig.chartKey, fetchChartComponent(target.chartConfig)) window['$vue'].component(target.chartConfig.conKey, fetchConfigComponent(target.chartConfig)) } } if (e.isGroup) { (e as CreateComponentGroupType).groupList.forEach(groupItem => { intComponent(groupItem) }) } else { intComponent(e as CreateComponentType) } }) // 创建函数-重新创建是为了处理类种方法消失的问题 const create = async ( _componentInstance: CreateComponentType, callBack?: (componentInstance: CreateComponentType) => void ) => { // 补充 class 上的方法 let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig) if (_componentInstance.chartConfig.redirectComponent) { _componentInstance.chartConfig.dataset && (newComponent.option.dataset = _componentInstance.chartConfig.dataset) newComponent.chartConfig.title = _componentInstance.chartConfig.title newComponent.chartConfig.chartFrame = _componentInstance.chartConfig.chartFrame } if (callBack) { if (changeId) { callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() })) } else { callBack(componentMerge(newComponent, _componentInstance)) } } else { if (changeId) { chartEditStore.addComponentList( componentMerge(newComponent, { ..._componentInstance, id: getUUID() }), false, true ) } else { chartEditStore.addComponentList(componentMerge(newComponent, _componentInstance), false, true) } } } // 数据赋值 for (const key in projectData) { // 组件 if (key === ChartEditStoreEnum.COMPONENT_LIST) { let loadIndex = 0 const listLength = projectData[key].length for (const comItem of projectData[key]) { // 设置加载数量 let percentage = parseInt((parseFloat(`${++loadIndex / listLength}`) * 100).toString()) chartLayoutStore.setItemUnHandle(ChartLayoutStoreEnum.PERCENTAGE, percentage) // 判断类型 if (comItem.isGroup) { // 创建分组 let groupClass = new PublicGroupConfigClass() if (changeId) { groupClass = componentMerge(groupClass, { ...comItem, id: getUUID() }) } else { groupClass = componentMerge(groupClass, comItem) } // 异步注册子应用 const targetList: CreateComponentType[] = [] for (const groupItem of (comItem as CreateComponentGroupType).groupList) { await create(groupItem, e => { targetList.push(e) }) } groupClass.groupList = targetList // 分组插入到列表 chartEditStore.addComponentList(groupClass, false, true) } else { await create(comItem as CreateComponentType) } if (percentage === 100) { // 清除历史记录 chartHistoryStore.clearBackStack() chartHistoryStore.clearForwardStack() } } } else { // 非组件(顺便排除脏数据) if (key !== 'editCanvasConfig' && key !== 'requestGlobalConfig') return componentMerge(chartEditStore[key], projectData[key], true) } } // 清除数量 chartLayoutStore.setItemUnHandle(ChartLayoutStoreEnum.PERCENTAGE, 0) } /** * * 赋值全局数据 * @param projectData 项目数据 * @returns */ const updateStoreInfo = (projectData: { id: string, projectName: string, indexImage: string, remarks: string, state: number }) => { const { id, projectName, remarks, indexImage, state } = projectData // ID chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_ID, id) // 名称 chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_NAME, projectName) // 描述 chartEditStore.setProjectInfo(ProjectInfoEnum.REMARKS, remarks) // 缩略图 chartEditStore.setProjectInfo(ProjectInfoEnum.THUMBNAIL, indexImage) // 发布 chartEditStore.setProjectInfo(ProjectInfoEnum.RELEASE, state === 1) } // * 数据获取 const dataSyncFetch = async () => { // FIX:重新执行dataSyncFetch需清空chartEditStore.componentList,否则会导致图层重复 // 切换语言等操作会导致重新执行 dataSyncFetch,此时pinia中并未清空chartEditStore.componentList,导致图层重复 chartEditStore.componentList = [] chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START) try { const res = await fetchProjectApi({ projectId: fetchRouteParamsLocation() }) if (res && res.code === ResultEnum.SUCCESS) { if (res.data) { updateStoreInfo(res.data) // 更新全局数据 await updateComponent(JSONParse(res.data.content)) return }else { chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_ID, fetchRouteParamsLocation()) } setTimeout(() => { chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS) }, 1000) return } chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE) } catch (error) { chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE) httpErrorHandle() } } // * 数据保存 const dataSyncUpdate = throttle(async (updateImg = true) => { if(!fetchRouteParamsLocation()) return let projectId = chartEditStore.getProjectInfo[ProjectInfoEnum.PROJECT_ID]; if(projectId === null || projectId === ''){ window['$message'].error('数据初未始化成功,请刷新页面!') return } chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START) // 异常处理:缩略图上传失败不影响JSON的保存 try { if (updateImg) { // 获取缩略图片 const range = document.querySelector('.go-edit-range') as HTMLElement // 生成图片 const canvasImage: HTMLCanvasElement = await html2canvas(range, { backgroundColor: null, allowTaint: true, useCORS: true }) // 上传预览图 let uploadParams = new FormData() uploadParams.append('object', base64toFile(canvasImage.toDataURL(), `${fetchRouteParamsLocation()}_index_preview.png`)) const uploadRes = await uploadFile(uploadParams) // 保存预览图 if(uploadRes && uploadRes.code === ResultEnum.SUCCESS) { if (uploadRes.data.fileurl) { await updateProjectApi({ id: fetchRouteParamsLocation(), indexImage: `${uploadRes.data.fileurl}` }) } else { await updateProjectApi({ id: fetchRouteParamsLocation(), indexImage: `${systemStore.getFetchInfo.OSSUrl}${uploadRes.data.fileName}` }) } } } } catch (e) { console.log(e) } // 保存数据 let params = new FormData() params.append('projectId', projectId) params.append('content', JSONStringify(chartEditStore.getStorageInfo || {})) const res= await saveProjectApi(params) if (res && res.code === ResultEnum.SUCCESS) { // 成功状态 setTimeout(() => { chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS) }, 1000) return } // 失败状态 chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE) }, 3000) // * 定时处理 const intervalDataSyncUpdate = () => { // 定时获取数据 const syncTiming = setInterval(() => { dataSyncUpdate() }, saveInterval * 1000) // 销毁 onUnmounted(() => { clearInterval(syncTiming) }) } return { updateComponent, updateStoreInfo, dataSyncFetch, dataSyncUpdate, intervalDataSyncUpdate } }