chartEditStore.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. import { defineStore } from 'pinia'
  2. import { getUUID, loadingStart, loadingFinish, loadingError } from '@/utils'
  3. import { CreateComponentType } from '@/packages/index.d'
  4. import debounce from 'lodash/debounce'
  5. import cloneDeep from 'lodash/cloneDeep'
  6. import { defaultTheme, globalThemeJson } from '@/settings/chartThemes/index'
  7. import { requestInterval, previewScaleType } from '@/settings/designSetting'
  8. // 记录记录
  9. import { useChartHistoryStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
  10. // 全局设置
  11. import { useSettingStore } from '@/store/modules/settingStore/settingStore'
  12. // 历史类型
  13. import { HistoryActionTypeEnum, HistoryItemType, HistoryTargetTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
  14. // 画布枚举
  15. import { MenuEnum, SyncEnum } from '@/enums/editPageEnum'
  16. import {
  17. ProjectInfoType,
  18. ChartEditStoreEnum,
  19. ChartEditStorage,
  20. ChartEditStoreType,
  21. EditCanvasType,
  22. MousePositionType,
  23. TargetChartType,
  24. RecordChartType,
  25. RequestGlobalConfigType,
  26. EditCanvasConfigType
  27. } from './chartEditStore.d'
  28. const chartHistoryStore = useChartHistoryStore()
  29. const settingStore = useSettingStore()
  30. // 编辑区域内容
  31. export const useChartEditStore = defineStore({
  32. id: 'useChartEditStore',
  33. state: (): ChartEditStoreType => ({
  34. // 项目数据
  35. projectInfo: {
  36. projectName: '',
  37. remarks: '',
  38. thumbnail: '',
  39. release: false
  40. },
  41. // 画布属性
  42. editCanvas: {
  43. // 编辑区域 Dom
  44. editLayoutDom: null,
  45. editContentDom: null,
  46. // 偏移量
  47. offset: 20,
  48. // 系统控制缩放
  49. scale: 1,
  50. // 用户控制的缩放
  51. userScale: 1,
  52. // 锁定缩放
  53. lockScale: false,
  54. // 初始化
  55. isCreate: false,
  56. // 拖拽中
  57. isDrag: false,
  58. // 同步中
  59. saveStatus: SyncEnum.PENDING
  60. },
  61. // 右键菜单
  62. rightMenuShow: false,
  63. // 鼠标定位
  64. mousePosition: {
  65. startX: 0,
  66. startY: 0,
  67. x: 0,
  68. y: 0
  69. },
  70. // 目标图表
  71. targetChart: {
  72. hoverId: undefined,
  73. selectId: undefined
  74. },
  75. // 记录临时数据(复制等)
  76. recordChart: undefined,
  77. // -----------------------
  78. // 画布属性(需存储给后端)
  79. editCanvasConfig: {
  80. // 默认宽度
  81. width: 1920,
  82. // 默认高度
  83. height: 1080,
  84. // 色相
  85. hueRotate: 0,
  86. // 饱和度
  87. saturate: 1,
  88. // 对比度
  89. contrast: 1,
  90. // 亮度
  91. brightness: 1,
  92. // 透明度
  93. opacity: 1,
  94. // 变换(暂不更改)
  95. rotateZ: 0,
  96. rotateX: 0,
  97. rotateY: 0,
  98. skewX: 0,
  99. skewY: 0,
  100. // 默认背景色
  101. background: undefined,
  102. backgroundImage: undefined,
  103. // 是否使用纯颜色
  104. selectColor: true,
  105. // chart 主题色
  106. chartThemeColor: defaultTheme || 'dark',
  107. // 全局配置
  108. chartThemeSetting: globalThemeJson,
  109. // 预览方式
  110. previewScaleType: previewScaleType
  111. },
  112. // 数据请求处理(需存储给后端)
  113. requestGlobalConfig: {
  114. requestOriginUrl: '',
  115. requestInterval: requestInterval
  116. },
  117. // 图表数组(需存储给后端)
  118. componentList: []
  119. }),
  120. getters: {
  121. getProjectInfo(): ProjectInfoType {
  122. return this.projectInfo
  123. },
  124. getMousePosition(): MousePositionType {
  125. return this.mousePosition
  126. },
  127. getRightMenuShow(): boolean {
  128. return this.rightMenuShow
  129. },
  130. getEditCanvas(): EditCanvasType {
  131. return this.editCanvas
  132. },
  133. getEditCanvasConfig(): EditCanvasConfigType {
  134. return this.editCanvasConfig
  135. },
  136. getTargetChart():TargetChartType {
  137. return this.targetChart
  138. },
  139. getRecordChart(): RecordChartType | undefined {
  140. return this.recordChart
  141. },
  142. getRequestGlobalConfig(): RequestGlobalConfigType {
  143. return this.requestGlobalConfig
  144. },
  145. getComponentList(): CreateComponentType[] {
  146. return this.componentList
  147. },
  148. // 获取需要存储的数据项
  149. getStorageInfo(): ChartEditStorage {
  150. return {
  151. [ChartEditStoreEnum.EDIT_CANVAS_CONFIG]: this.getEditCanvasConfig,
  152. [ChartEditStoreEnum.COMPONENT_LIST]: this.getComponentList,
  153. [ChartEditStoreEnum.REQUEST_GLOBAL_CONFIG]: this.getRequestGlobalConfig
  154. }
  155. }
  156. },
  157. actions: {
  158. // * 设置 peojectInfo 数据项
  159. setProjectInfo<T extends keyof ProjectInfoType, K extends ProjectInfoType[T]>(key: T, value: K) {
  160. this.projectInfo[key] = value
  161. },
  162. // * 设置 editCanvas 数据项
  163. setEditCanvas<T extends keyof EditCanvasType, K extends EditCanvasType[T]>(key: T, value: K) {
  164. this.editCanvas[key] = value
  165. },
  166. // * 设置 editCanvasConfig(需保存后端) 数据项
  167. setEditCanvasConfig<T extends keyof EditCanvasConfigType, K extends EditCanvasConfigType[T]>(key: T, value: K) {
  168. this.editCanvasConfig[key] = value
  169. },
  170. // * 设置右键菜单
  171. setRightMenuShow(value: boolean) {
  172. this.rightMenuShow = value
  173. },
  174. // * 设置目标数据 hover
  175. setTargetHoverChart(hoverId?:TargetChartType["hoverId"]) {
  176. this.targetChart.hoverId = hoverId
  177. },
  178. // * 设置目标数据 select
  179. setTargetSelectChart(selectId?:TargetChartType["selectId"]) {
  180. this.targetChart.selectId = selectId
  181. },
  182. // * 设置记录数据
  183. setRecordChart(item: RecordChartType | undefined) {
  184. this.recordChart = cloneDeep(item)
  185. },
  186. // * 设置鼠标位置
  187. setMousePosition(x?: number, y?: number, startX?: number, startY?: number): void {
  188. if (startX) this.mousePosition.startX = startX
  189. if (startY) this.mousePosition.startY = startY
  190. if (x) this.mousePosition.x = x
  191. if (y) this.mousePosition.y = y
  192. },
  193. // * 找到目标 id 数据下标位置(无则返回-1)
  194. fetchTargetIndex(id?: string): number {
  195. const targetId = id || this.getTargetChart.selectId
  196. if(!targetId) {
  197. loadingFinish()
  198. return -1
  199. }
  200. const index = this.componentList.findIndex(e => e.id === targetId)
  201. if (index === -1) {
  202. loadingError()
  203. }
  204. return index
  205. },
  206. /**
  207. * * 新增组件列表
  208. * @param chartConfig 新图表实例
  209. * @param isHead 是否头部插入
  210. * @param isHistory 是否进行记录
  211. * @returns
  212. */
  213. addComponentList(chartConfig: CreateComponentType, isHead = false, isHistory = false): void {
  214. if (isHistory) {
  215. chartHistoryStore.createAddHistory(chartConfig)
  216. }
  217. if (isHead) {
  218. this.componentList.unshift(chartConfig)
  219. return
  220. }
  221. this.componentList.push(chartConfig)
  222. },
  223. // * 删除组件列表
  224. removeComponentList(isHistory = true): void {
  225. try {
  226. loadingStart()
  227. const index = this.fetchTargetIndex()
  228. if (index !== -1) {
  229. isHistory ? chartHistoryStore.createDeleteHistory(this.getComponentList[index]) : undefined
  230. this.componentList.splice(index, 1)
  231. loadingFinish()
  232. return
  233. }
  234. } catch(value) {
  235. loadingError()
  236. }
  237. },
  238. // * 更新组件列表某一项的值
  239. updateComponentList(index: number, newData: CreateComponentType) {
  240. if (index < 1 && index > this.getComponentList.length) return
  241. this.componentList[index] = newData
  242. },
  243. // * 设置页面样式属性
  244. setPageStyle<T extends keyof CSSStyleDeclaration>(
  245. key: T,
  246. value: any
  247. ): void {
  248. const dom = this.getEditCanvas.editContentDom
  249. if (dom) {
  250. dom.style[key] = value
  251. }
  252. },
  253. // * 移动组件列表层级位置到两端
  254. setBothEnds(isEnd = false, isHistory = true): void {
  255. try {
  256. loadingStart()
  257. const length = this.getComponentList.length
  258. if (length < 2) {
  259. loadingFinish()
  260. return
  261. }
  262. const index = this.fetchTargetIndex()
  263. const targetData = this.getComponentList[index]
  264. if (index !== -1) {
  265. // 置底排除最底层, 置顶排除最顶层
  266. if ((isEnd && index === 0) || (!isEnd && index === length - 1 )) {
  267. loadingFinish()
  268. return
  269. }
  270. // 记录原有位置
  271. const setIndex = (t:CreateComponentType, i:number) => {
  272. const temp = cloneDeep(t)
  273. temp.attr.zIndex = i
  274. return temp
  275. }
  276. // 历史记录
  277. if (isHistory) {
  278. chartHistoryStore.createLaryerHistory(
  279. setIndex(targetData, index),
  280. isEnd ? HistoryActionTypeEnum.BOTTOM : HistoryActionTypeEnum.TOP
  281. )
  282. }
  283. // 插入两端
  284. this.addComponentList(targetData, isEnd)
  285. this.getComponentList.splice(isEnd ? index + 1: index, 1)
  286. loadingFinish()
  287. return
  288. }
  289. } catch(value) {
  290. loadingError()
  291. }
  292. },
  293. // * 置顶
  294. setTop(isHistory = true): void {
  295. this.setBothEnds(false, isHistory)
  296. },
  297. // * 置底
  298. setBottom(isHistory = true): void {
  299. this.setBothEnds(true, isHistory)
  300. },
  301. // * 上移/下移互换图表位置
  302. wrap(isDown = false, isHistory = true) {
  303. try {
  304. loadingStart()
  305. const length = this.getComponentList.length
  306. if (length < 2) {
  307. loadingFinish()
  308. return
  309. }
  310. const index:number = this.fetchTargetIndex()
  311. if (index !== -1) {
  312. // 下移排除最底层, 上移排除最顶层
  313. if ((isDown && index === 0) || (!isDown && index === length - 1)) {
  314. loadingFinish()
  315. return
  316. }
  317. // 互换位置
  318. const swapIndex = isDown ? index - 1 : index + 1
  319. const targetItem = this.getComponentList[index]
  320. const swapItem = this.getComponentList[swapIndex]
  321. // 历史记录
  322. if (isHistory) {
  323. chartHistoryStore.createLaryerHistory(
  324. targetItem,
  325. isDown ? HistoryActionTypeEnum.DOWN : HistoryActionTypeEnum.UP
  326. )
  327. }
  328. this.updateComponentList(index, swapItem)
  329. this.updateComponentList(swapIndex, targetItem)
  330. loadingFinish()
  331. return
  332. }
  333. } catch(value) {
  334. loadingError()
  335. }
  336. },
  337. // * 图层上移
  338. setUp(isHistory = true) {
  339. this.wrap(false, isHistory)
  340. },
  341. // * 图层下移
  342. setDown(isHistory = true) {
  343. this.wrap(true, isHistory)
  344. },
  345. // * 复制
  346. setCopy(isCut = false) {
  347. try {
  348. loadingStart()
  349. const index:number = this.fetchTargetIndex()
  350. if (index !== -1) {
  351. const copyData:RecordChartType = {
  352. charts :this.getComponentList[index],
  353. type: isCut ? HistoryActionTypeEnum.CUT : HistoryActionTypeEnum.COPY
  354. }
  355. this.setRecordChart(copyData)
  356. window['$message'].success(isCut ? '剪切成功' : '复制成功!')
  357. loadingFinish()
  358. }
  359. } catch(value) {
  360. loadingError()
  361. }
  362. },
  363. // * 剪切
  364. setCut() {
  365. this.setCopy(true)
  366. },
  367. // * 粘贴
  368. setParse() {
  369. try {
  370. loadingStart()
  371. const recordCharts = this.getRecordChart
  372. if (recordCharts === undefined) {
  373. loadingFinish()
  374. return
  375. }
  376. const parseHandle = (e: CreateComponentType) => {
  377. e = cloneDeep(e)
  378. // 生成新 id
  379. e.id = getUUID()
  380. // 偏移位置
  381. e.attr.x = this.getMousePosition.x + 30
  382. e.attr.y = this.getMousePosition.y + 30
  383. return e
  384. }
  385. const isCut = recordCharts.type === HistoryActionTypeEnum.CUT
  386. // 多项
  387. if (Array.isArray(recordCharts.charts)) {
  388. recordCharts.charts.forEach((e: CreateComponentType) => {
  389. this.addComponentList(parseHandle(e), undefined, true)
  390. // 剪切需删除原数据
  391. if (isCut) {
  392. this.setTargetSelectChart(e.id)
  393. this.removeComponentList(true)
  394. }
  395. })
  396. if (isCut) this.setRecordChart(undefined)
  397. loadingFinish()
  398. return
  399. }
  400. // 单项
  401. this.addComponentList(parseHandle(recordCharts.charts), undefined, true)
  402. if (isCut) {
  403. this.setTargetSelectChart(recordCharts.charts.id)
  404. this.removeComponentList()
  405. this.setRecordChart(undefined)
  406. }
  407. loadingFinish()
  408. } catch(value) {
  409. loadingError()
  410. }
  411. },
  412. // * 撤回/前进 目标处理
  413. setBackAndSetForwardHandle(item: HistoryItemType, isForward = false) {
  414. // 处理画布
  415. if (item.targetType === HistoryTargetTypeEnum.CANVAS) {
  416. this.editCanvas = item.historyData as EditCanvasType
  417. return
  418. }
  419. const historyData = item.historyData as CreateComponentType
  420. // 处理新增类型
  421. const isAdd = item.actionType === HistoryActionTypeEnum.ADD
  422. const isDel = item.actionType === HistoryActionTypeEnum.DELETE
  423. this.setTargetSelectChart(historyData.id)
  424. if (isAdd || isDel) {
  425. if ((isAdd && isForward) || (isDel && !isForward)) {
  426. this.addComponentList(historyData)
  427. return
  428. }
  429. this.removeComponentList(false)
  430. return
  431. }
  432. // 处理层级
  433. const isTop = item.actionType === HistoryActionTypeEnum.TOP
  434. const isBottom = item.actionType === HistoryActionTypeEnum.BOTTOM
  435. if (isTop || isBottom) {
  436. if (!isForward) {
  437. // 插入到原有位置
  438. if (isTop) this.getComponentList.pop()
  439. if (isBottom) this.getComponentList.shift()
  440. this.getComponentList.splice(historyData.attr.zIndex, 0, historyData)
  441. return
  442. }
  443. if (isTop) this.setTop(false)
  444. if (isBottom) this.setBottom(false)
  445. }
  446. const isUp = item.actionType === HistoryActionTypeEnum.UP
  447. const isDown = item.actionType === HistoryActionTypeEnum.DOWN
  448. if (isUp || isDown) {
  449. if ((isUp && isForward) || (isDown && !isForward)) {
  450. this.setUp(false)
  451. return
  452. }
  453. this.setDown(false)
  454. return
  455. }
  456. // 处理内容修改
  457. this.getComponentList[this.fetchTargetIndex()] = item.historyData as CreateComponentType
  458. },
  459. // * 撤回
  460. setBack() {
  461. try {
  462. loadingStart()
  463. const targetData = chartHistoryStore.backAction()
  464. if (!targetData) {
  465. loadingFinish()
  466. return
  467. }
  468. if (Array.isArray(targetData)) {
  469. targetData.forEach((e: HistoryItemType) => {
  470. this.setBackAndSetForwardHandle(e)
  471. })
  472. loadingFinish()
  473. return
  474. }
  475. this.setBackAndSetForwardHandle(targetData)
  476. loadingFinish()
  477. } catch(value) {
  478. loadingError()
  479. }
  480. },
  481. // * 前进
  482. setForward() {
  483. try {
  484. loadingStart()
  485. const targetData = chartHistoryStore.forwardAction()
  486. if (!targetData) {
  487. loadingFinish()
  488. return
  489. }
  490. if (Array.isArray(targetData)) {
  491. targetData.forEach((e: HistoryItemType) => {
  492. this.setBackAndSetForwardHandle(e, true)
  493. })
  494. loadingFinish()
  495. return
  496. }
  497. this.setBackAndSetForwardHandle(targetData, true)
  498. loadingFinish()
  499. } catch(value) {
  500. loadingError()
  501. }
  502. },
  503. // * 移动位置
  504. setMove(keyboardValue: MenuEnum) {
  505. const index = this.fetchTargetIndex()
  506. if(index === -1) return
  507. const attr = this.getComponentList[index].attr
  508. const distance = settingStore.getChartMoveDistance
  509. switch (keyboardValue) {
  510. case MenuEnum.ARROW_UP:
  511. attr.y -= distance
  512. break;
  513. case MenuEnum.ARROW_RIGHT:
  514. attr.x += distance
  515. break;
  516. case MenuEnum.ARROW_DOWN:
  517. attr.y += distance
  518. break;
  519. case MenuEnum.ARROW_LEFT:
  520. attr.x -= distance
  521. break;
  522. }
  523. },
  524. // * 页面缩放设置-----------------
  525. // * 设置页面大小
  526. setPageSize(scale: number): void {
  527. this.setPageStyle('height', `${this.editCanvasConfig.height * scale}px`)
  528. this.setPageStyle('width', `${this.editCanvasConfig.width * scale}px`)
  529. },
  530. // * 计算缩放
  531. computedScale() {
  532. if (this.getEditCanvas.editLayoutDom) {
  533. // 现有展示区域
  534. const width =
  535. this.getEditCanvas.editLayoutDom.clientWidth - this.getEditCanvas.offset * 2 - 5
  536. const height =
  537. this.getEditCanvas.editLayoutDom.clientHeight - this.getEditCanvas.offset * 4
  538. // 用户设定大小
  539. const editCanvasWidth = this.editCanvasConfig.width
  540. const editCanvasHeight = this.editCanvasConfig.height
  541. // 需保持的比例
  542. const baseProportion = parseFloat(
  543. (editCanvasWidth / editCanvasHeight).toFixed(5)
  544. )
  545. const currentRate = parseFloat((width / height).toFixed(5))
  546. if (currentRate > baseProportion) {
  547. // 表示更宽
  548. const scaleWidth = parseFloat(
  549. ((height * baseProportion) / editCanvasWidth).toFixed(5)
  550. )
  551. this.setScale( scaleWidth > 1 ? 1 : scaleWidth)
  552. } else {
  553. // 表示更高
  554. const scaleHeight = parseFloat(
  555. (width / baseProportion / editCanvasHeight).toFixed(5)
  556. )
  557. this.setScale(scaleHeight > 1 ? 1 : scaleHeight)
  558. }
  559. } else {
  560. window['$message'].warning('请先创建画布,再进行缩放')
  561. }
  562. },
  563. // * 监听缩放
  564. listenerScale(): Function {
  565. const resize = debounce(this.computedScale, 200)
  566. // 默认执行一次
  567. resize()
  568. // 开始监听
  569. window.addEventListener('resize', resize)
  570. // 销毁函数
  571. const remove = () => {
  572. window.removeEventListener('resize', resize)
  573. }
  574. return remove
  575. },
  576. /**
  577. * * 设置缩放
  578. * @param scale 0~1 number 缩放比例;
  579. * @param force boolean 强制缩放
  580. */
  581. setScale(scale: number, force = false): void {
  582. if (!this.getEditCanvas.lockScale || force) {
  583. this.setPageSize(scale)
  584. this.getEditCanvas.userScale = scale
  585. this.getEditCanvas.scale = scale
  586. }
  587. }
  588. }
  589. })