index.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <template>
  2. <div
  3. v-loading="loading"
  4. class="l-flex--col c-device-tree u-color--black u-font-size--sm u-relative"
  5. :class="{ 'support-shrink': shrink, shrink: shrinkState }"
  6. >
  7. <div class="l-flex__fill l-flex--col c-device-tree__content">
  8. <el-tabs
  9. v-if="exact"
  10. v-model="active"
  11. class="c-tabs sm has-bottom-padding--sm"
  12. >
  13. <el-tab-pane
  14. label="按部门"
  15. name="group"
  16. />
  17. <el-tab-pane
  18. label="按分辨率"
  19. name="ratio"
  20. />
  21. <!-- <el-tab-pane
  22. label="按宽高比"
  23. name="productType"
  24. /> -->
  25. </el-tabs>
  26. <warning
  27. v-if="error"
  28. @click="getDevices"
  29. />
  30. <template v-else>
  31. <div class="l-flex__none l-flex--row c-sibling-item--v u-overflow--hidden">
  32. <div class="l-flex__auto">
  33. <div
  34. v-if="canReset"
  35. class="o-button"
  36. @click="onReset"
  37. >
  38. 重置
  39. </div>
  40. </div>
  41. <search-input
  42. v-model.trim="deviceName"
  43. class="l-flex__none"
  44. size="small"
  45. placeholder="设备名称"
  46. @search="onSearch"
  47. />
  48. </div>
  49. <c-tree
  50. class="l-flex__fill c-sibling-item--v"
  51. :root-option="rootOption"
  52. />
  53. </template>
  54. </div>
  55. <i
  56. v-if="shrink"
  57. class="o-shrink-icon u-color--black u-font-size--lg has-active"
  58. :class="iconClass"
  59. @click="onToggleShrink"
  60. />
  61. </div>
  62. </template>
  63. <script>
  64. import {
  65. getDepartmentDeviceTree,
  66. getProduct
  67. } from '@/api/device'
  68. import treeMixin from '../tree.js'
  69. export default {
  70. name: 'DeviceTree',
  71. mixins: [treeMixin],
  72. props: {
  73. shrink: {
  74. type: [Boolean, String],
  75. default: false
  76. },
  77. exact: {
  78. type: [Boolean, String],
  79. default: false
  80. },
  81. filter: {
  82. type: Function,
  83. default: null
  84. },
  85. customTransform: {
  86. type: Function,
  87. default: null
  88. }
  89. },
  90. data () {
  91. return {
  92. shrinkState: false,
  93. loading: false,
  94. error: false,
  95. active: 'group',
  96. deviceName: ''
  97. }
  98. },
  99. computed: {
  100. isGroupTab () {
  101. return this.active === 'group'
  102. },
  103. iconClass () {
  104. return this.shrinkState ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'
  105. },
  106. canReset () {
  107. return this.checkbox && !this.rootOption.disabled
  108. }
  109. },
  110. watch: {
  111. active: {
  112. handler () {
  113. this.getDevices()
  114. },
  115. immediate: true
  116. }
  117. },
  118. methods: {
  119. onToggleShrink () {
  120. this.shrinkState = !this.shrinkState
  121. },
  122. getDevices () {
  123. this.loading = true
  124. this.error = false
  125. this.getDevicesByActive().then(
  126. data => {
  127. this.$nodes = data
  128. this.initData()
  129. },
  130. () => {
  131. this.error = true
  132. }
  133. ).finally(() => {
  134. this.loading = false
  135. })
  136. },
  137. getDevicesByActive () {
  138. if (this.$productCache) {
  139. return this.initTabCache()
  140. }
  141. const cache = this[`$${this.active}Cache`]
  142. if (cache) {
  143. return Promise.resolve(cache)
  144. }
  145. return getDepartmentDeviceTree().then(({ data }) => {
  146. const productCacheOptions = {
  147. key: 'productId',
  148. ids: {},
  149. map: {},
  150. cache: []
  151. }
  152. this.$groupCache = this.iterateNode(data, productCacheOptions).children
  153. if (this.exact) {
  154. this.$productCache = productCacheOptions.cache
  155. }
  156. this.$emit('loaded', data)
  157. return this.getDevicesByActive()
  158. })
  159. },
  160. async initTabCache () {
  161. for (let i = 0; i < this.$productCache.length; i++) {
  162. if (!this.$productCache[i].resolutionRatio) {
  163. const { data: { resolutionRatio, productTypeName } } = await getProduct(this.$productCache[i].id)
  164. this.$productCache[i].resolutionRatio = resolutionRatio
  165. this.$productCache[i].productTypeName = productTypeName
  166. this.$productCache[i].children.forEach(device => {
  167. device.resolutionRatio = resolutionRatio
  168. })
  169. }
  170. }
  171. const ratioMap = {}
  172. // const productTypeMap = {}
  173. this.$productCache.forEach(({ children, resolutionRatio/* , productTypeName */ }) => {
  174. if (!ratioMap[resolutionRatio]) {
  175. ratioMap[resolutionRatio] = []
  176. }
  177. ratioMap[resolutionRatio] = [...ratioMap[resolutionRatio], ...children]
  178. // if (!productTypeMap[productTypeName]) {
  179. // productTypeMap[productTypeName] = []
  180. // }
  181. // productTypeMap[productTypeName] = [...productTypeMap[productTypeName], ...children]
  182. })
  183. this.$ratioCache = Object.keys(ratioMap).map(key => {
  184. return {
  185. id: key,
  186. name: key,
  187. children: ratioMap[key]
  188. }
  189. })
  190. // this.$productTypeCache = Object.keys(productTypeMap).map(key => {
  191. // return {
  192. // id: key,
  193. // name: key,
  194. // children: productTypeMap[key]
  195. // }
  196. // })
  197. this.$productCache = null
  198. return this.getDevicesByActive()
  199. },
  200. iterateNode ({ id = 'root', name = '我的部门', children, devices }, productCacheOptions) {
  201. const deviceArr = []
  202. devices.forEach(device => {
  203. const item = this.transform(device)
  204. this.setCache(item, productCacheOptions)
  205. deviceArr.push(item)
  206. })
  207. return {
  208. id: `dir_${id}`,
  209. name,
  210. children: [
  211. ...children.map(child => this.iterateNode(child, productCacheOptions)),
  212. ...deviceArr
  213. ]
  214. }
  215. },
  216. transform (device) {
  217. const { id, name, productId } = device
  218. if (this.customTransform) {
  219. return {
  220. id, name, productId,
  221. ...this.customTransform(device)
  222. }
  223. }
  224. return {
  225. id,
  226. name,
  227. productId
  228. }
  229. },
  230. setCache (device, { key, ids, map, cache }) {
  231. const id = device.id
  232. if (!ids[id]) {
  233. const value = device[key]
  234. if (!map[value]) {
  235. map[value] = []
  236. cache.push({
  237. id: value,
  238. children: map[value]
  239. })
  240. }
  241. ids[id] = 1
  242. map[value].push(device)
  243. }
  244. },
  245. initData () {
  246. const rootOption = this.createRootNode()
  247. this.setNodes(rootOption, this.onFilter(rootOption, this.$nodes, this.deviceName ? new RegExp(this.deviceName) : null))
  248. this.rootOption = rootOption
  249. this.customExpand = false
  250. const diff = this.$presetNodeMap ? Object.keys(this.$presetNodeMap) : []
  251. this.$presetNodeMap = null
  252. return diff.length
  253. },
  254. onSearch () {
  255. this.filterNode(this.rootOption, this.deviceName ? new RegExp(this.deviceName) : null)
  256. this.customExpand = !!this.deviceName
  257. },
  258. filterNode (node, regx) {
  259. const { disbaled, checked, indeterminate } = node
  260. let change = false
  261. node.children.forEach(subnode => {
  262. if (subnode.children) {
  263. change = this.filterNode(subnode, regx) || change
  264. } else {
  265. const isDisabled = this.isDisabledNode(subnode, regx)
  266. if (subnode.disabled !== isDisabled) {
  267. change = true
  268. subnode.disabled = isDisabled
  269. this.addOrRemoveNodeCache(subnode)
  270. }
  271. }
  272. })
  273. if (change) {
  274. this.checkNode(node)
  275. return disbaled !== node.disbaled || checked !== node.checked || indeterminate !== node.indeterminate
  276. }
  277. return change
  278. },
  279. onFilter (parent, nodes, regx) {
  280. const childNodes = nodes.map(({ children, ...node }) => {
  281. if (!children) {
  282. const checked = this.$presetNodeMap ? this.$presetNodeMap[node.id] : false
  283. if (checked) {
  284. delete this.$presetNodeMap[node.id]
  285. }
  286. return this.createLeafNode(parent, {
  287. ...node,
  288. checked,
  289. disabled: this.isDisabledNode(node)
  290. })
  291. }
  292. const subNode = this.createNode(node, parent)
  293. this.setNodes(subNode, this.onFilter(subNode, children, regx))
  294. return subNode
  295. })
  296. return childNodes
  297. },
  298. isDisabledNode (node, regx) {
  299. return (regx ? !regx.test(node.name) : false) || (this.filter ? !this.filter(node) : false)
  300. },
  301. onReset () {
  302. this.deviceName = ''
  303. this.initData()
  304. },
  305. reset (deviceIds) {
  306. if (deviceIds?.length) {
  307. const map = {}
  308. deviceIds.forEach(id => {
  309. map[id] = true
  310. })
  311. this.$presetNodeMap = map
  312. this.deviceName = ''
  313. if (this.$nodes) {
  314. return this.initData()
  315. }
  316. return -1
  317. }
  318. return this.initData()
  319. }
  320. }
  321. }
  322. </script>
  323. <style lang="scss" scoped>
  324. .c-device-tree {
  325. &.support-shrink {
  326. padding-right: $spacing--lg !important;
  327. &.shrink {
  328. box-sizing: border-box;
  329. width: $spacing--lg !important;
  330. .c-device-tree__content {
  331. width: 0;
  332. overflow: hidden;
  333. }
  334. }
  335. }
  336. }
  337. .o-shrink-icon {
  338. position: absolute;
  339. top: 50%;
  340. right: calc(($spacing--lg - $spacing) / 2);
  341. transform: translateY(-50%);
  342. }
  343. </style>