index.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. mergeConfig(props.chartConfig.option)
  106. if(type=="preview"){
  107. calcCapsuleLengthAndLabelData(data)
  108. }else{
  109. calcCapsuleLengthAndLabelData(state.mergedConfig.dataset)
  110. }
  111. }
  112. const mergeConfig = (data: any) => {
  113. state.mergedConfig = cloneDeep(data || {})
  114. }
  115. // 数据解析
  116. const calcCapsuleLengthAndLabelData = (dataset:any) => {
  117. const { source } = dataset
  118. if (!source.length) return
  119. state.capsuleItemHeight = numberSizeHandle(state.mergedConfig.itemHeight)
  120. const capsuleValue = source.map((item: DataProps) => item[state.mergedConfig.dataset.dimensions[1]])
  121. const maxValue = Math.max(...capsuleValue)
  122. state.capsuleValue = capsuleValue
  123. state.capsuleLength = capsuleValue.map((v: any) => (maxValue ? v / maxValue : 0))
  124. const oneFifth = maxValue / 5
  125. const labelData = Array.from(new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth))))
  126. state.labelData = labelData
  127. }
  128. const numberSizeHandle = (val: string | number) => {
  129. return val + 'px'
  130. }
  131. onMounted(() => {
  132. calcData(props.chartConfig.option)
  133. })
  134. // 预览
  135. useChartDataFetch(props.chartConfig, useChartEditStore, (newData: any) => {
  136. calcData(newData,"preview")
  137. })
  138. </script>
  139. <style lang="scss" scoped>
  140. @include go('dv-capsule-chart') {
  141. position: relative;
  142. display: flex;
  143. flex-direction: row;
  144. box-sizing: border-box;
  145. padding: 20px;
  146. padding-right: 50px;
  147. color: #b9b8cc;
  148. .label-column {
  149. display: flex;
  150. flex-direction: column;
  151. justify-content: space-between;
  152. box-sizing: border-box;
  153. padding-right: 10px;
  154. text-align: right;
  155. > div:not(:last-child) {
  156. margin: 5px 0;
  157. }
  158. }
  159. .capsule-container {
  160. flex: 1;
  161. display: flex;
  162. flex-direction: column;
  163. justify-content: space-between;
  164. }
  165. .capsule-item {
  166. box-shadow: 0 0 3px #999;
  167. height: 10px;
  168. margin: 5px 0px;
  169. border-radius: 5px;
  170. .capsule-item-column {
  171. position: relative;
  172. height: 8px;
  173. margin-top: 1px;
  174. border-radius: 5px;
  175. transition: all 0.3s;
  176. display: flex;
  177. justify-content: flex-end;
  178. align-items: center;
  179. .capsule-item-value {
  180. padding-left: 10px;
  181. transform: translateX(100%);
  182. }
  183. }
  184. }
  185. .unit-label {
  186. height: 20px;
  187. position: relative;
  188. display: flex;
  189. justify-content: space-between;
  190. align-items: center;
  191. }
  192. .unit-text {
  193. text-align: right;
  194. display: flex;
  195. align-items: flex-end;
  196. line-height: 20px;
  197. margin-left: 10px;
  198. }
  199. }
  200. </style>