useSync.hook.ts 11 KB

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