index.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <template>
  2. <div class="go-chart-search-box">
  3. <div class="chart-search go-transition" :class="{ 'chart-search-focus': isFocus }">
  4. <n-popover
  5. class="chart-search-popover"
  6. :show-arrow="false"
  7. :show="showPopover"
  8. :to="false"
  9. trigger="hover"
  10. placement="bottom-start"
  11. >
  12. <template #trigger>
  13. <n-input-group>
  14. <n-input
  15. size="small"
  16. placeholder="搜索组件"
  17. v-model:value.trim="search"
  18. :loading="loading"
  19. @focus="focusHandle(true)"
  20. @blur="focusHandle(false)"
  21. @update:value="searchHandle"
  22. >
  23. <template #suffix>
  24. <n-icon v-show="!loading" :component="SearchIcon" />
  25. </template>
  26. </n-input>
  27. </n-input-group>
  28. </template>
  29. <div class="search-list-box">
  30. <n-scrollbar style="max-height: 500px">
  31. <n-empty v-show="!searchRes.length" size="small" description="没有找到组件~"></n-empty>
  32. <div
  33. class="list-item go-flex-items-center go-ellipsis-1"
  34. v-for="item in searchRes"
  35. :key="item.key"
  36. :title="item.title"
  37. @click="selectChartHandle(item)"
  38. >
  39. <search-image class="list-item-img" :item="item"></search-image>
  40. <n-text class="list-item-fs" depth="2">{{ item.title }}</n-text>
  41. </div>
  42. </n-scrollbar>
  43. <div class="popover-modal"></div>
  44. </div>
  45. </n-popover>
  46. </div>
  47. <n-button-group class="btn-group go-transition" :class="{ 'btn-group-focus': isFocus }" style="display: flex">
  48. <n-button
  49. ghost
  50. size="small"
  51. :key="index"
  52. :type="chartMode === item.value ? 'primary' : 'tertiary'"
  53. v-for="(item, index) in chartModeList"
  54. @click="changeChartModeType(item.value)"
  55. >
  56. <n-tooltip :show-arrow="false" trigger="hover">
  57. <template #trigger>
  58. <n-icon size="14" :component="item.icon" />
  59. </template>
  60. {{ item.label }}
  61. </n-tooltip>
  62. </n-button>
  63. </n-button-group>
  64. </div>
  65. </template>
  66. <script setup lang="ts">
  67. import { ref, onUnmounted } from 'vue'
  68. import { icon } from '@/plugins'
  69. import { createComponent } from '@/packages'
  70. import { ConfigType, CreateComponentType } from '@/packages/index.d'
  71. import { themeColor, MenuOptionsType } from '../../hooks/useAside.hook'
  72. import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
  73. import { ChartModeEnum, ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
  74. import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
  75. import { isString, addEventListener, removeEventListener } from '@/utils'
  76. import { fetchConfigComponent, fetchChartComponent } from '@/packages/index'
  77. import { componentInstall, loadingStart, loadingFinish, loadingError } from '@/utils'
  78. import SearchImage from './SearchImage.vue'
  79. const props = defineProps({
  80. menuOptions: {
  81. type: Array,
  82. default: () => []
  83. }
  84. })
  85. const chartEditStore = useChartEditStore()
  86. const chartLayoutStore = useChartLayoutStore()
  87. const { SearchIcon, AlbumsIcon, GridIcon } = icon.ionicons5
  88. const isFocus = ref<boolean>(false)
  89. const showPopover = ref<boolean>(false)
  90. const loading = ref<boolean | undefined>(undefined)
  91. const search = ref<string | null>(null)
  92. const searchRes = ref<ConfigType[]>([])
  93. const chartMode = ref<ChartModeEnum>(chartLayoutStore.getChartType)
  94. const chartModeList = [
  95. { label: '单列', icon: AlbumsIcon, value: ChartModeEnum.SINGLE },
  96. { label: '双列', icon: GridIcon, value: ChartModeEnum.DOUBLE }
  97. ]
  98. // 组件数组提取
  99. const listFormatHandle = (options: any[]) => {
  100. const arr = []
  101. for (const item of options) {
  102. arr.push(...item.list)
  103. }
  104. return arr
  105. }
  106. // 组件列表
  107. const List = listFormatHandle(props.menuOptions as unknown as ConfigType[])
  108. // 关闭处理
  109. const closeHandle = () => {
  110. loading.value = undefined
  111. showPopover.value = false
  112. search.value = null
  113. searchRes.value = []
  114. }
  115. // 搜索处理
  116. const searchHandle = (key: string | null) => {
  117. if (!isString(key) || !key.length) {
  118. closeHandle()
  119. return
  120. }
  121. loading.value = true
  122. showPopover.value = true
  123. searchRes.value = List.filter((e: ConfigType) => !key || e.title.toLowerCase().includes(key.toLowerCase()))
  124. setTimeout(() => {
  125. loading.value = undefined
  126. }, 500)
  127. }
  128. // 关闭处理
  129. const listenerCloseHandle = (e: Event) => {
  130. if (!showPopover.value) return
  131. if (!e.target) return
  132. if (!(e.target as any).closest('.go-chart-search')) {
  133. closeHandle()
  134. }
  135. }
  136. // 选择处理
  137. const selectChartHandle = async (item: ConfigType) => {
  138. try {
  139. loadingStart()
  140. // 动态注册图表组件
  141. componentInstall(item.chartKey, fetchChartComponent(item))
  142. componentInstall(item.conKey, fetchConfigComponent(item))
  143. // 创建新图表组件
  144. let newComponent: CreateComponentType = await createComponent(item)
  145. // 添加
  146. chartEditStore.addComponentList(newComponent, false, true)
  147. // 选中
  148. chartEditStore.setTargetSelectChart(newComponent.id)
  149. // 清空搜索
  150. closeHandle()
  151. loadingFinish()
  152. } catch (error) {
  153. loadingError()
  154. window['$message'].warning(`图表正在研发中, 敬请期待...`)
  155. }
  156. }
  157. // 聚焦设置
  158. const focusHandle = (value: boolean) => {
  159. isFocus.value = value
  160. }
  161. // 修改图表展示方式
  162. const changeChartModeType = (value: ChartModeEnum) => {
  163. chartMode.value = value
  164. chartLayoutStore.setItem(ChartLayoutStoreEnum.Chart_TYPE, value)
  165. }
  166. addEventListener(document, 'click', (e: Event) => {
  167. listenerCloseHandle(e)
  168. })
  169. onUnmounted(() => {
  170. removeEventListener(document, 'click', listenerCloseHandle)
  171. })
  172. </script>
  173. <style lang="scss" scoped>
  174. $width: 98px;
  175. $searchWidth: 176px;
  176. @include go('chart-search-box') {
  177. display: flex;
  178. .chart-search {
  179. width: $width;
  180. margin-right: 10px;
  181. &.chart-search-focus {
  182. width: $searchWidth;
  183. &.chart-search {
  184. margin-right: 0;
  185. }
  186. }
  187. @include deep() {
  188. .chart-search-popover {
  189. padding-left: 5px;
  190. padding-right: 8px;
  191. }
  192. }
  193. .chart-search-popover {
  194. .search-list-box {
  195. width: 163px;
  196. .list-item {
  197. z-index: 2;
  198. position: relative;
  199. cursor: pointer;
  200. padding: 2px;
  201. padding-left: 6px;
  202. margin-bottom: 5px;
  203. &-fs {
  204. font-size: 12px;
  205. }
  206. &-img {
  207. height: 28px;
  208. margin-right: 5px;
  209. border-radius: 5px;
  210. }
  211. &:hover {
  212. &::before {
  213. content: '';
  214. position: absolute;
  215. width: 3px;
  216. height: 100%;
  217. left: 0;
  218. top: 0;
  219. border-radius: 2px;
  220. background-color: v-bind('themeColor');
  221. }
  222. &::after {
  223. z-index: -1;
  224. content: '';
  225. position: absolute;
  226. width: 100%;
  227. height: 100%;
  228. opacity: 0.1;
  229. left: 0;
  230. top: 0;
  231. border-radius: 5px;
  232. background-color: v-bind('themeColor');
  233. }
  234. }
  235. }
  236. }
  237. }
  238. }
  239. .btn-group {
  240. width: 68px;
  241. overflow: hidden;
  242. &.btn-group-focus {
  243. width: 0px;
  244. }
  245. }
  246. }
  247. </style>