useSync.hook.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import { onUnmounted } from 'vue';
  2. import html2canvas from 'html2canvas'
  3. import { getUUID, httpErrorHandle, fetchRouteParamsLocation, base64toFile, JSONStringify, JSONParse } 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 { CreateComponentType, CreateComponentGroupType, ConfigType } from '@/packages/index.d'
  21. import { BaseEvent, EventLife } from '@/enums/eventEnum'
  22. import { PublicGroupConfigClass } from '@/packages/public/publicConfig'
  23. import merge from 'lodash/merge'
  24. /**
  25. * * 画布-版本升级对旧数据无法兼容的补丁
  26. * @param object
  27. */
  28. const canvasVersionUpdatePolyfill = (object: any) => {
  29. return object
  30. }
  31. /**
  32. * * 组件-版本升级对旧数据无法兼容的补丁
  33. * @param newObject
  34. * @param sources
  35. */
  36. const componentVersionUpdatePolyfill = (newObject: any, sources: any) => {
  37. try {
  38. // 判断是否是组件
  39. if (sources.id) {
  40. // 处理事件补丁
  41. const hasVnodeBeforeMount = 'vnodeBeforeMount' in sources.events
  42. const hasVnodeMounted = 'vnodeMounted' in sources.events
  43. if (hasVnodeBeforeMount) {
  44. newObject.events.advancedEvents.vnodeBeforeMount = sources?.events.vnodeBeforeMount
  45. }
  46. if (hasVnodeMounted) {
  47. newObject.events.advancedEvents.vnodeMounted = sources?.events.vnodeMounted
  48. }
  49. if (hasVnodeBeforeMount || hasVnodeMounted) {
  50. sources.events = {
  51. baseEvent: {
  52. [BaseEvent.ON_CLICK]: undefined,
  53. [BaseEvent.ON_DBL_CLICK]: undefined,
  54. [BaseEvent.ON_MOUSE_ENTER]: undefined,
  55. [BaseEvent.ON_MOUSE_LEAVE]: undefined
  56. },
  57. advancedEvents: {
  58. [EventLife.VNODE_MOUNTED]: undefined,
  59. [EventLife.VNODE_BEFORE_MOUNT]: undefined
  60. }
  61. }
  62. }
  63. return newObject
  64. }
  65. } catch (error) {
  66. return newObject
  67. }
  68. }
  69. /**
  70. * * 合并处理
  71. * @param newObject 新的模板数据
  72. * @param sources 新拿到的数据
  73. * @returns object
  74. */
  75. const componentMerge = (newObject: any, sources: any, notComponent = false) => {
  76. // 处理组件补丁
  77. componentVersionUpdatePolyfill(newObject, sources)
  78. // 非组件不处理
  79. if (notComponent) return merge(newObject, sources)
  80. // 组件排除 newObject
  81. const option = sources.option
  82. if (!option) return merge(newObject, sources)
  83. // 为 undefined 的 sources 来源对象属性将被跳过详见 https://www.lodashjs.com/docs/lodash.merge
  84. sources.option = undefined
  85. if (option) {
  86. return {
  87. ...merge(newObject, sources),
  88. option: option
  89. }
  90. }
  91. }
  92. // 请求处理
  93. export const useSync = () => {
  94. const chartEditStore = useChartEditStore()
  95. const chartHistoryStore = useChartHistoryStore()
  96. const systemStore = useSystemStore()
  97. const chartLayoutStore = useChartLayoutStore()
  98. /**
  99. * * 组件动态注册
  100. * @param projectData 项目数据
  101. * @param isReplace 是否替换数据
  102. * @returns
  103. */
  104. const updateComponent = async (projectData: ChartEditStorage, isReplace = false, changeId = false) => {
  105. if (isReplace) {
  106. // 清除列表
  107. chartEditStore.componentList = []
  108. // 清除历史记录
  109. chartHistoryStore.clearBackStack()
  110. chartHistoryStore.clearForwardStack()
  111. }
  112. // 画布补丁处理
  113. projectData.editCanvasConfig = canvasVersionUpdatePolyfill(projectData.editCanvasConfig)
  114. // 列表组件注册
  115. projectData.componentList.forEach(async (e: CreateComponentType | CreateComponentGroupType) => {
  116. const intComponent = (target: CreateComponentType) => {
  117. if (!window['$vue'].component(target.chartConfig.chartKey)) {
  118. window['$vue'].component(target.chartConfig.chartKey, fetchChartComponent(target.chartConfig))
  119. window['$vue'].component(target.chartConfig.conKey, fetchConfigComponent(target.chartConfig))
  120. }
  121. }
  122. if (e.isGroup) {
  123. (e as CreateComponentGroupType).groupList.forEach(groupItem => {
  124. intComponent(groupItem)
  125. })
  126. } else {
  127. intComponent(e as CreateComponentType)
  128. }
  129. })
  130. // 创建函数-重新创建是为了处理类种方法消失的问题
  131. const create = async (
  132. _componentInstance: CreateComponentType,
  133. callBack?: (componentInstance: CreateComponentType) => void
  134. ) => {
  135. // 补充 class 上的方法
  136. let newComponent: CreateComponentType = await createComponent(_componentInstance.chartConfig)
  137. if (callBack) {
  138. if (changeId) {
  139. callBack(componentMerge(newComponent, { ..._componentInstance, id: getUUID() }))
  140. } else {
  141. callBack(componentMerge(newComponent, _componentInstance))
  142. }
  143. } else {
  144. if (changeId) {
  145. chartEditStore.addComponentList(
  146. componentMerge(newComponent, { ..._componentInstance, id: getUUID() }),
  147. false,
  148. true
  149. )
  150. } else {
  151. chartEditStore.addComponentList(componentMerge(newComponent, _componentInstance), false, true)
  152. }
  153. }
  154. }
  155. // 数据赋值
  156. for (const key in projectData) {
  157. // 组件
  158. if (key === ChartEditStoreEnum.COMPONENT_LIST) {
  159. let loadIndex = 0
  160. const listLength = projectData[key].length;
  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(JSONParse(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', JSONStringify(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. }