useSync.hook.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { onUnmounted } from 'vue';
  2. import html2canvas from 'html2canvas'
  3. import { getUUID, httpErrorHandle, fetchRouteParamsLocation, base64toFile } from '@/utils'
  4. import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  5. import { EditCanvasTypeEnum, ChartEditStoreEnum, ProjectInfoEnum, ChartEditStorage } from '@/store/modules/chartEditStore/chartEditStore.d'
  6. import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
  7. import { StylesSetting } from '@/components/Pages/ChartItemSetting'
  8. import { useSystemStore } from '@/store/modules/systemStore/systemStore'
  9. import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
  10. import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
  11. import { fetchChartComponent, fetchConfigComponent, createComponent } from '@/packages/index'
  12. import { saveInterval } from '@/settings/designSetting'
  13. import throttle from 'lodash/throttle'
  14. // 接口状态
  15. import { ResultEnum } from '@/enums/httpEnum'
  16. // 接口
  17. import { saveProjectApi, fetchProjectApi, uploadFile, updateProjectApi } from '@/api/path'
  18. // 画布枚举
  19. import { SyncEnum } from '@/enums/editPageEnum'
  20. import { BaseEvent, EventLife, CreateComponentType, CreateComponentGroupType, ConfigType } from '@/packages/index.d'
  21. import { PublicGroupConfigClass } from '@/packages/public/publicConfig'
  22. import merge from 'lodash/merge'
  23. /**
  24. * * 画布-版本升级对旧数据无法兼容的补丁
  25. * @param object
  26. */
  27. const canvasVersionUpdatePolyfill = (object: any) => {
  28. return object
  29. }
  30. /**
  31. * * 组件-版本升级对旧数据无法兼容的补丁
  32. * @param newObject
  33. * @param sources
  34. */
  35. const componentVersionUpdatePolyfill = (newObject: any, sources: any) => {
  36. try {
  37. // 判断是否是组件
  38. if (sources.id) {
  39. // 处理事件补丁
  40. const hasVnodeBeforeMount = 'vnodeBeforeMount' in sources.events
  41. const hasVnodeMounted = 'vnodeMounted' in sources.events
  42. if (hasVnodeBeforeMount) {
  43. newObject.events.advancedEvents.vnodeBeforeMount = sources?.events.vnodeBeforeMount
  44. }
  45. if (hasVnodeMounted) {
  46. newObject.events.advancedEvents.vnodeMounted = sources?.events.vnodeMounted
  47. }
  48. if (hasVnodeBeforeMount || hasVnodeMounted) {
  49. sources.events = {
  50. baseEvent: {
  51. [BaseEvent.ON_CLICK]: undefined,
  52. [BaseEvent.ON_DBL_CLICK]: undefined,
  53. [BaseEvent.ON_MOUSE_ENTER]: undefined,
  54. [BaseEvent.ON_MOUSE_LEAVE]: undefined
  55. },
  56. advancedEvents: {
  57. [EventLife.VNODE_MOUNTED]: undefined,
  58. [EventLife.VNODE_BEFORE_MOUNT]: undefined
  59. }
  60. }
  61. }
  62. return newObject
  63. }
  64. } catch (error) {
  65. return newObject
  66. }
  67. }
  68. /**
  69. * * 合并处理
  70. * @param newObject 新的模板数据
  71. * @param sources 新拿到的数据
  72. * @returns object
  73. */
  74. const componentMerge = (newObject: any, sources: any, notComponent = false) => {
  75. // 处理组件补丁
  76. componentVersionUpdatePolyfill(newObject, sources)
  77. // 非组件不处理
  78. if (notComponent) return merge(newObject, sources)
  79. // 组件排除 newObject
  80. const option = sources.option
  81. if (!option) return merge(newObject, sources)
  82. // 为 undefined 的 sources 来源对象属性将被跳过详见 https://www.lodashjs.com/docs/lodash.merge
  83. sources.option = undefined
  84. if (option) {
  85. return {
  86. ...merge(newObject, sources),
  87. option: option
  88. }
  89. }
  90. }
  91. // 请求处理
  92. export const useSync = () => {
  93. const chartEditStore = useChartEditStore()
  94. const chartHistoryStore = useChartHistoryStore()
  95. const systemStore = useSystemStore()
  96. const chartLayoutStore = useChartLayoutStore()
  97. /**
  98. * * 组件动态注册
  99. * @param projectData 项目数据
  100. * @param isReplace 是否替换数据
  101. * @returns
  102. */
  103. const updateComponent = async (projectData: ChartEditStorage, isReplace = false, changeId = false) => {
  104. if (isReplace) {
  105. // 清除列表
  106. chartEditStore.componentList = []
  107. // 清除历史记录
  108. chartHistoryStore.clearBackStack()
  109. chartHistoryStore.clearForwardStack()
  110. }
  111. // 画布补丁处理
  112. projectData.editCanvasConfig = canvasVersionUpdatePolyfill(projectData.editCanvasConfig)
  113. // 列表组件注册
  114. projectData.componentList.forEach(async (e: CreateComponentType | CreateComponentGroupType) => {
  115. const intComponent = (target: CreateComponentType) => {
  116. if (!window['$vue'].component(target.chartConfig.chartKey)) {
  117. window['$vue'].component(target.chartConfig.chartKey, fetchChartComponent(target.chartConfig))
  118. window['$vue'].component(target.chartConfig.conKey, fetchConfigComponent(target.chartConfig))
  119. }
  120. }
  121. if (e.isGroup) {
  122. (e as CreateComponentGroupType).groupList.forEach(groupItem => {
  123. intComponent(groupItem)
  124. })
  125. } else {
  126. intComponent(e as CreateComponentType)
  127. }
  128. })
  129. // 创建函数-重新创建是为了处理类种方法消失的问题
  130. const create = async (
  131. _componentInstance: CreateComponentType,
  132. callBack?: (componentInstance: CreateComponentType) => void
  133. ) => {
  134. // 补充 class 上的方法
  135. let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig)
  136. if (callBack) {
  137. if (changeId) {
  138. callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() }))
  139. } else {
  140. callBack(componentMerge(newComponent, _componentInstance))
  141. }
  142. } else {
  143. if (changeId) {
  144. chartEditStore.addComponentList(
  145. componentMerge(newComponent, { ..._componentInstance, id: getUUID() }),
  146. false,
  147. true
  148. )
  149. } else {
  150. chartEditStore.addComponentList(componentMerge(newComponent, _componentInstance), false, true)
  151. }
  152. }
  153. }
  154. // 数据赋值
  155. for (const key in projectData) {
  156. // 组件
  157. if (key === ChartEditStoreEnum.COMPONENT_LIST) {
  158. let loadIndex = 0
  159. const listLength = projectData[key].length;
  160. console.log(listLength)
  161. for (const comItem of projectData[key]) {
  162. // 设置加载数量
  163. let percentage = parseInt((parseFloat(`${++loadIndex / listLength}`) * 100).toString())
  164. chartLayoutStore.setItemUnHandle(ChartLayoutStoreEnum.PERCENTAGE, percentage)
  165. // 判断类型
  166. if (comItem.isGroup) {
  167. // 创建分组
  168. let groupClass = new PublicGroupConfigClass()
  169. if (changeId) {
  170. groupClass = componentMerge(groupClass, { ...comItem, id: getUUID() })
  171. } else {
  172. groupClass = componentMerge(groupClass, comItem)
  173. }
  174. // 异步注册子应用
  175. const targetList: CreateComponentType[] = []
  176. for (const groupItem of (comItem as CreateComponentGroupType).groupList) {
  177. await create(groupItem, e => {
  178. targetList.push(e)
  179. })
  180. }
  181. groupClass.groupList = targetList
  182. // 分组插入到列表
  183. chartEditStore.addComponentList(groupClass, false, true)
  184. } else {
  185. await create(comItem as CreateComponentType)
  186. }
  187. }
  188. } else {
  189. // 非组件(顺便排除脏数据)
  190. if (key !== 'editCanvasConfig' && key !== 'requestGlobalConfig') return
  191. componentMerge(chartEditStore[key], projectData[key], true)
  192. }
  193. }
  194. // 清除数量
  195. chartLayoutStore.setItemUnHandle(ChartLayoutStoreEnum.PERCENTAGE, 0)
  196. }
  197. /**
  198. * * 赋值全局数据
  199. * @param projectData 项目数据
  200. * @returns
  201. */
  202. const updateStoreInfo = (projectData: {
  203. id: string,
  204. projectName: string,
  205. indexImage: string,
  206. remarks: string,
  207. state: number
  208. }) => {
  209. const { id, projectName, remarks, indexImage, state } = projectData
  210. // ID
  211. chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_ID, id)
  212. // 名称
  213. chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_NAME, projectName)
  214. // 描述
  215. chartEditStore.setProjectInfo(ProjectInfoEnum.REMARKS, remarks)
  216. // 缩略图
  217. chartEditStore.setProjectInfo(ProjectInfoEnum.THUMBNAIL, indexImage)
  218. // 发布
  219. chartEditStore.setProjectInfo(ProjectInfoEnum.RELEASE, state === 1)
  220. }
  221. // * 数据获取
  222. const dataSyncFetch = async () => {
  223. // FIX:重新执行dataSyncFetch需清空chartEditStore.componentList,否则会导致图层重复
  224. // 切换语言等操作会导致重新执行 dataSyncFetch,此时pinia中并未清空chartEditStore.componentList,导致图层重复
  225. chartEditStore.componentList = []
  226. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START)
  227. try {
  228. const res = await fetchProjectApi({ projectId: fetchRouteParamsLocation() })
  229. if (res && res.code === ResultEnum.SUCCESS) {
  230. if (res.data) {
  231. updateStoreInfo(res.data)
  232. // 更新全局数据
  233. await updateComponent(JSON.parse(res.data.content))
  234. return
  235. }else {
  236. chartEditStore.setProjectInfo(ProjectInfoEnum.PROJECT_ID, fetchRouteParamsLocation())
  237. }
  238. setTimeout(() => {
  239. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS)
  240. }, 1000)
  241. return
  242. }
  243. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
  244. } catch (error) {
  245. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
  246. httpErrorHandle()
  247. }
  248. }
  249. // * 数据保存
  250. const dataSyncUpdate = throttle(async (updateImg = true) => {
  251. if(!fetchRouteParamsLocation()) return
  252. let projectId = chartEditStore.getProjectInfo[ProjectInfoEnum.PROJECT_ID];
  253. if(projectId === null || projectId === ''){
  254. window['$message'].error('数据初未始化成功,请刷新页面!')
  255. return
  256. }
  257. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.START)
  258. // 异常处理:缩略图上传失败不影响JSON的保存
  259. try {
  260. if (updateImg) {
  261. // 获取缩略图片
  262. const range = document.querySelector('.go-edit-range') as HTMLElement
  263. // 生成图片
  264. const canvasImage: HTMLCanvasElement = await html2canvas(range, {
  265. backgroundColor: null,
  266. allowTaint: true,
  267. useCORS: true
  268. })
  269. // 上传预览图
  270. let uploadParams = new FormData()
  271. uploadParams.append('object', base64toFile(canvasImage.toDataURL(), `${fetchRouteParamsLocation()}_index_preview.png`))
  272. const uploadRes = await uploadFile(uploadParams)
  273. // 保存预览图
  274. if(uploadRes && uploadRes.code === ResultEnum.SUCCESS) {
  275. await updateProjectApi({
  276. id: fetchRouteParamsLocation(),
  277. indexImage: `${systemStore.getFetchInfo.OSSUrl}${uploadRes.data.fileName}`
  278. })
  279. }
  280. }
  281. } catch (e) {
  282. console.log(e)
  283. }
  284. // 保存数据
  285. let params = new FormData()
  286. params.append('projectId', projectId)
  287. params.append('content', JSON.stringify(chartEditStore.getStorageInfo || {}))
  288. const res= await saveProjectApi(params)
  289. if (res && res.code === ResultEnum.SUCCESS) {
  290. // 成功状态
  291. setTimeout(() => {
  292. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.SUCCESS)
  293. }, 1000)
  294. return
  295. }
  296. // 失败状态
  297. chartEditStore.setEditCanvas(EditCanvasTypeEnum.SAVE_STATUS, SyncEnum.FAILURE)
  298. }, 3000)
  299. // * 定时处理
  300. const intervalDataSyncUpdate = () => {
  301. // 定时获取数据
  302. const syncTiming = setInterval(() => {
  303. dataSyncUpdate()
  304. }, saveInterval * 1000)
  305. // 销毁
  306. onUnmounted(() => {
  307. clearInterval(syncTiming)
  308. })
  309. }
  310. return {
  311. updateComponent,
  312. updateStoreInfo,
  313. dataSyncFetch,
  314. dataSyncUpdate,
  315. intervalDataSyncUpdate
  316. }
  317. }