index.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <template>
  2. <div
  3. v-if="state.mergedConfig"
  4. class="go-dv-capsule-chart"
  5. :style="{
  6. fontSize: numberSizeHandle(state.mergedConfig.valueFontSize),
  7. paddingLeft: numberSizeHandle(state.mergedConfig.paddingLeft),
  8. paddingRight: numberSizeHandle(state.mergedConfig.paddingRight)
  9. }"
  10. >
  11. <div class="label-column">
  12. <div
  13. v-for="item in state.mergedConfig.dataset.source"
  14. :key="item[state.mergedConfig.dataset.dimensions[0]]"
  15. :style="{ height: state.capsuleItemHeight, lineHeight: state.capsuleItemHeight }"
  16. >
  17. {{ item[state.mergedConfig.dataset.dimensions[0]] }}
  18. </div>
  19. <div class="laset">&nbsp;</div>
  20. </div>
  21. <div class="capsule-container">
  22. <div
  23. v-for="(capsule, index) in state.capsuleLength"
  24. :key="index"
  25. class="capsule-item"
  26. :style="{ height: state.capsuleItemHeight }"
  27. >
  28. <div
  29. class="capsule-item-column"
  30. :style="`width: ${capsule * 100}%; background-color: ${
  31. state.mergedConfig.colors[index % state.mergedConfig.colors.length]
  32. };height:calc(100% - ${2}px);`"
  33. >
  34. <div v-if="state.mergedConfig.showValue" class="capsule-item-value">
  35. {{ state.capsuleValue[index] }}
  36. </div>
  37. </div>
  38. </div>
  39. <div class="unit-label">
  40. <div v-for="(label, index) in state.labelData" :key="label + index">
  41. {{ label }}
  42. </div>
  43. </div>
  44. </div>
  45. <div v-if="state.mergedConfig.unit" class="unit-text">
  46. {{ state.mergedConfig.unit }}
  47. </div>
  48. </div>
  49. </template>
  50. <script setup lang="ts">
  51. import { onMounted, watch, reactive, PropType } from 'vue'
  52. import { useChartDataFetch } from '@/hooks'
  53. import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  54. import config, { option } from './config'
  55. import cloneDeep from 'lodash/cloneDeep'
  56. type DataProps = {
  57. name: string | number
  58. value: string | number
  59. [key: string]: string | number
  60. }
  61. interface StateProps {
  62. defaultConfig: {
  63. dataset: {
  64. dimensions: Array<string>
  65. source: Array<DataProps>
  66. }
  67. colors: Array<string>
  68. unit: string
  69. showValue: boolean
  70. itemHeight: number
  71. valueFontSize: number
  72. paddingLeft: number
  73. paddingRight: number
  74. }
  75. mergedConfig: any
  76. capsuleLength: Array<number>
  77. capsuleValue: Array<string | Object>
  78. labelData: Array<number>
  79. capsuleItemHeight: string
  80. }
  81. const props = defineProps({
  82. chartConfig: {
  83. type: Object as PropType<config>,
  84. default: () => ({})
  85. }
  86. })
  87. const state = reactive<StateProps>({
  88. defaultConfig: option,
  89. mergedConfig: null,
  90. capsuleLength: [],
  91. capsuleValue: [],
  92. labelData: [],
  93. capsuleItemHeight: ''
  94. })
  95. watch(
  96. () => props.chartConfig.option,
  97. newVal => {
  98. calcData(newVal)
  99. },
  100. {
  101. deep: true
  102. }
  103. )
  104. const calcData = (data: any, type?: string) => {
  105. let cloneConfig = cloneDeep(props.chartConfig.option || {})
  106. state.mergedConfig = cloneConfig
  107. if (type == 'preview') {
  108. cloneConfig.dataset = data
  109. }
  110. calcCapsuleLengthAndLabelData(state.mergedConfig.dataset)
  111. }
  112. // 数据解析
  113. const calcCapsuleLengthAndLabelData = (dataset: any) => {
  114. const { source } = dataset
  115. if (!source.length) return
  116. state.capsuleItemHeight = numberSizeHandle(state.mergedConfig.itemHeight)
  117. const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]])
  118. const maxValue = Math.max(...capsuleValue)
  119. state.capsuleValue = capsuleValue
  120. state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0))
  121. const oneFifth = maxValue / 5
  122. const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth))))
  123. state.labelData = labelData
  124. }
  125. const numberSizeHandle = (val: string | number) => {
  126. return val + 'px'
  127. }
  128. onMounted(() => {
  129. calcData(props.chartConfig.option)
  130. })
  131. // 预览
  132. useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
  133. calcData(newData, 'preview')
  134. })
  135. </script>
  136. <style lang="scss" scoped>
  137. @include go('dv-capsule-chart') {
  138. position: relative;
  139. display: flex;
  140. flex-direction: row;
  141. box-sizing: border-box;
  142. padding: 20px;
  143. padding-right: 50px;
  144. color: #b9b8cc;
  145. .label-column {
  146. display: flex;
  147. flex-direction: column;
  148. justify-content: space-between;
  149. box-sizing: border-box;
  150. padding-right: 10px;
  151. text-align: right;
  152. > div:not(:last-child) {
  153. margin: 5px 0;
  154. }
  155. }
  156. .capsule-container {
  157. flex: 1;
  158. display: flex;
  159. flex-direction: column;
  160. justify-content: space-between;
  161. }
  162. .capsule-item {
  163. box-shadow: 0 0 3px #999;
  164. height: 10px;
  165. margin: 5px 0px;
  166. border-radius: 5px;
  167. .capsule-item-column {
  168. position: relative;
  169. height: 8px;
  170. margin-top: 1px;
  171. border-radius: 5px;
  172. transition: all 0.3s;
  173. display: flex;
  174. justify-content: flex-end;
  175. align-items: center;
  176. .capsule-item-value {
  177. padding-left: 10px;
  178. transform: translateX(100%);
  179. }
  180. }
  181. }
  182. .unit-label {
  183. height: 20px;
  184. position: relative;
  185. display: flex;
  186. justify-content: space-between;
  187. align-items: center;
  188. }
  189. .unit-text {
  190. text-align: right;
  191. display: flex;
  192. align-items: flex-end;
  193. line-height: 20px;
  194. margin-left: 10px;
  195. }
  196. }
  197. </style>