chartEditStore.ts 19 KB

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