Device.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. <template>
  2. <schema-table
  3. ref="table"
  4. row-key="id"
  5. :schema="schema"
  6. >
  7. <confirm-dialog
  8. ref="editDialog"
  9. :title="dialogTitle"
  10. @confirm="onSave"
  11. >
  12. <template #default>
  13. <div class="c-grid-form u-align-self--center">
  14. <span class="c-grid-form__label u-required">名称</span>
  15. <el-input
  16. v-model.trim="currObj.name"
  17. placeholder="最多30个字符"
  18. maxlength="30"
  19. clearable
  20. />
  21. <span class="c-grid-form__label u-required">配置</span>
  22. <schema-select
  23. v-model="currObj.productId"
  24. class="u-width"
  25. :schema="productSelectSchema"
  26. placeholder="请选择配置"
  27. />
  28. <span class="c-grid-form__label u-required">型号</span>
  29. <el-autocomplete
  30. v-model="currObj.remark"
  31. class="u-width"
  32. :fetch-suggestions="querySearch"
  33. placeholder="播控器的系列或型号"
  34. />
  35. <span class="c-grid-form__label u-required">序列号</span>
  36. <el-input
  37. v-model.trim="currObj.serialNumber"
  38. placeholder="最多50个字符"
  39. maxlength="50"
  40. clearable
  41. />
  42. <span class="c-grid-form__label u-required">MAC</span>
  43. <el-input
  44. v-model.trim="currObj.mac"
  45. class="u-width"
  46. placeholder="ff:ff:ff:ff:ff:ff"
  47. maxlength="17"
  48. clearable
  49. />
  50. <div class="c-grid-form__label u-required">开关机时间</div>
  51. <el-time-picker
  52. v-model="currObj.range"
  53. class="u-width u-pointer"
  54. is-range
  55. value-format="HH:mm:ss"
  56. :clearable="false"
  57. />
  58. <span class="c-grid-form__label u-required">地址</span>
  59. <el-input
  60. v-model.trim="currObj.address"
  61. type="textarea"
  62. placeholder="最多100个字符"
  63. maxlength="100"
  64. :rows="2"
  65. show-word-limit
  66. />
  67. <span class="c-grid-form__label">坐标</span>
  68. <div class="l-flex--row c-grid-form__option">
  69. <span class="c-sibling-item">{{ currObj.longitude }},{{ currObj.latitude }}</span>
  70. <i
  71. class="c-sibling-item el-icon-edit u-color--blue has-active"
  72. @click="onEditCoordinate"
  73. />
  74. </div>
  75. </div>
  76. </template>
  77. </confirm-dialog>
  78. <coordinate-dialog
  79. ref="coordinateDialog"
  80. @confirm="onChangeCoordinate"
  81. />
  82. <mesh-dialog ref="meshDialog" />
  83. <confirm-dialog
  84. ref="replaceDialog"
  85. title="设备替换"
  86. @confirm="onConfirmReplace"
  87. >
  88. <template #default>
  89. <div class="c-grid-form u-align-self--center">
  90. <span class="c-grid-form__label u-required">序列号</span>
  91. <el-input
  92. v-model.trim="currObj.serialNumber"
  93. placeholder="最多50个字符"
  94. maxlength="50"
  95. clearable
  96. />
  97. <span class="c-grid-form__label u-required">MAC</span>
  98. <el-input
  99. v-model.trim="currObj.mac"
  100. class="u-width"
  101. placeholder="ff:ff:ff:ff:ff:ff"
  102. maxlength="17"
  103. clearable
  104. />
  105. <span class="c-grid-form__label">配置</span>
  106. <schema-select
  107. ref="productSelect"
  108. v-model="currObj.productId"
  109. class="u-width"
  110. :schema="replaceProductSelectSchema"
  111. />
  112. </div>
  113. </template>
  114. </confirm-dialog>
  115. </schema-table>
  116. </template>
  117. <script>
  118. import { BoxModels } from '@/constant'
  119. import {
  120. validMAC,
  121. validLongitude,
  122. validLatitude
  123. } from '@/utils/validate'
  124. import {
  125. getDevicesByTenant,
  126. addDevice,
  127. deleteDevice,
  128. getSubDevices,
  129. addSubDevice,
  130. activateDevice,
  131. deactivateDevice,
  132. getProducts,
  133. updateDevice,
  134. replaceDevice
  135. } from '@/api/device'
  136. import MeshDialog from '../../components/MeshDialog.vue'
  137. export default {
  138. name: 'Device',
  139. components: {
  140. MeshDialog
  141. },
  142. props: {
  143. tenant: {
  144. type: String,
  145. required: true
  146. }
  147. },
  148. data () {
  149. const productSelectSchema = {
  150. remote: this.getProducts,
  151. pagination: true,
  152. value: 'id',
  153. label: 'name'
  154. }
  155. return {
  156. currObj: {},
  157. isSub: false,
  158. productSelectSchema,
  159. replaceProductSelectSchema: {
  160. ...productSelectSchema,
  161. placeholder: '不选择时使用原配置'
  162. },
  163. schema: {
  164. keepalive: true,
  165. props: {
  166. size: 'small'
  167. },
  168. // listeners: {
  169. // 'row-click': __SUB_DEVICE__ ? this.onRowClick : null
  170. // },
  171. list: this.getDevicesByTenant,
  172. transform: this.transform,
  173. // transformData: __SUB_DEVICE__ && this.transformTableData,
  174. buttons: [
  175. { type: 'add', on: this.onAddDevice }
  176. ],
  177. filters: [
  178. { key: 'productId', type: 'select', placeholder: '配置', ...productSelectSchema },
  179. { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
  180. { value: 1, label: '已使用' },
  181. { value: 0, label: '未使用' }
  182. ] },
  183. { key: 'serialNumber', type: 'search', placeholder: '序列号' },
  184. { key: 'mac', type: 'search', placeholder: 'MAC' },
  185. { key: 'name', type: 'search', placeholder: '设备名称' },
  186. { type: 'refresh' }
  187. ],
  188. cols: [
  189. // { type: 'refresh', render: __SUB_DEVICE__
  190. // ? (data, h) => data.isMaster
  191. // ? h('i', {
  192. // staticClass: `o-expand-icon u-pointer ${data.loading ? 'el-icon-loading' : 'el-icon-arrow-right'}`,
  193. // class: { expand: data.expand }
  194. // })
  195. // : null
  196. // : null },
  197. // __SUB_DEVICE__
  198. // ? { label: '设备名称', render: (data, h) => data.empty ? h('span', { staticClass: 'u-color--info' }, '暂无备份设备') : data.name, 'min-width': 120 }
  199. // : { prop: 'name', label: '设备名称', 'min-width': 120 },
  200. { type: 'refresh' },
  201. { prop: 'name', label: '设备名称', 'min-width': 120 },
  202. { label: '型号', render: (data, h) => data.empty
  203. ? ''
  204. : h('edit-input', {
  205. props: {
  206. value: data.remark,
  207. placeholder: '-'
  208. },
  209. on: { edit: val => this.onEditRemark(data, val) }
  210. }), 'class-name': 'c-edit-column' },
  211. { prop: 'productName', label: '配置' },
  212. { prop: 'serialNumber', label: '序列号', 'min-width': 160 },
  213. { prop: 'mac', label: 'MAC', 'min-width': 120 },
  214. { type: 'tag', render: ({ empty, activate, onlineStatus }) => empty
  215. ? null
  216. : activate
  217. ? onlineStatus === 0
  218. ? { type: 'primary', label: '待接入' }
  219. : onlineStatus === 1
  220. ? { type: 'success', label: '在线' }
  221. : { type: 'danger', label: '离线' }
  222. : { type: 'warning', label: '未激活' },
  223. 'size': 'sm', width: 80, on: this.onTagClick },
  224. { label: '使用情况', type: 'tag', render: ({ empty, isMaster, bound }) => empty || !isMaster
  225. ? null
  226. : bound
  227. ? { type: 'success', label: '已使用' }
  228. : { type: 'primary', label: '未使用', ignore: true }, 'size': 'sm', width: 80, on: this.onViewMesh },
  229. { type: 'invoke', render: [
  230. { label: '配置', render: ({ isMaster }) => isMaster, on: this.onSettingDevice },
  231. { label: '替换', render: ({ isMaster }) => isMaster, on: this.onReplace },
  232. // __SUB_DEVICE__ && { label: '备机', render: ({ isMaster }) => isMaster, on: this.onAddSubDevice },
  233. { label: '删除', render: ({ empty }) => !empty, on: this.onDelDevice }
  234. ], width: 140 }
  235. ]
  236. }
  237. }
  238. },
  239. computed: {
  240. dialogTitle () {
  241. return this.isSub ? '新增备份设备' : '新增设备'
  242. }
  243. },
  244. methods: {
  245. querySearch (val, cb) {
  246. cb(val ? [] : [...BoxModels])
  247. },
  248. getProducts (params) {
  249. return getProducts({
  250. tenant: this.tenant,
  251. ...params
  252. })
  253. },
  254. getDevicesByTenant (params) {
  255. return getDevicesByTenant(this.tenant, params)
  256. },
  257. transform (data) {
  258. return {
  259. ...data,
  260. loaded: false,
  261. loading: false,
  262. expand: false,
  263. subs: [],
  264. isMaster: true
  265. }
  266. },
  267. transformTableData (data) {
  268. const arr = []
  269. data.forEach(item => {
  270. arr.push(item)
  271. if (item.loaded && item.expand) {
  272. arr.push(...item.subs)
  273. }
  274. })
  275. return arr
  276. },
  277. onAddDevice () {
  278. this.isSub = false
  279. this.$master = null
  280. this.productSelectSchema.option = null
  281. this.onAdd()
  282. },
  283. onAddSubDevice (item) {
  284. this.isSub = true
  285. this.$master = item
  286. this.productSelectSchema.option = { value: item.productId, label: item.productName }
  287. this.onAdd()
  288. },
  289. onAdd () {
  290. if (this.isSub) {
  291. const { productId, openTime, closeTime, address, longitude, latitude } = this.$master
  292. this.currObj = {
  293. name: '',
  294. remark: '',
  295. productId,
  296. serialNumber: '',
  297. mac: '',
  298. range: [openTime, closeTime],
  299. address,
  300. longitude,
  301. latitude,
  302. tenant: this.tenant
  303. }
  304. } else {
  305. this.currObj = {
  306. name: '',
  307. remark: '',
  308. productId: '',
  309. serialNumber: '',
  310. mac: '',
  311. range: ['08:00:00', '22:00:00'],
  312. address: '',
  313. longitude: '',
  314. latitude: '',
  315. tenant: this.tenant
  316. }
  317. }
  318. this.$refs.editDialog.show()
  319. },
  320. onEditCoordinate () {
  321. const { longitude, latitude, address } = this.currObj
  322. this.$refs.coordinateDialog.show({
  323. longitude,
  324. latitude,
  325. address
  326. })
  327. },
  328. onChangeCoordinate ({ value: { longitude, latitude, address }, done }) {
  329. this.currObj.longitude = longitude
  330. this.currObj.latitude = latitude
  331. this.currObj.address = address
  332. done()
  333. },
  334. onSave (done) {
  335. if (this.check(this.currObj)) {
  336. const { mac, range, ...data } = this.currObj
  337. if (this.isSub) {
  338. addSubDevice(this.$master, {
  339. openTime: range[0],
  340. closeTime: range[1],
  341. mac: mac.toLowerCase(),
  342. ...data
  343. }).then(() => {
  344. done()
  345. this.reloadSubDevices(this.$master)
  346. })
  347. } else {
  348. addDevice({
  349. openTime: range[0],
  350. closeTime: range[1],
  351. mac: mac.toLowerCase(),
  352. ...data
  353. }).then(() => {
  354. done()
  355. this.$refs.table.resetCondition({
  356. boundFlag: 0,
  357. name: this.currObj.name,
  358. productId: this.currObj.productId,
  359. serialNumber: this.currObj.serialNumber,
  360. mac: mac.toLowerCase()
  361. })
  362. })
  363. }
  364. }
  365. },
  366. check (item) {
  367. if (!item.name) {
  368. this.$message({
  369. type: 'warning',
  370. message: '名称不能为空'
  371. })
  372. return false
  373. }
  374. if (!item.productId) {
  375. this.$message({
  376. type: 'warning',
  377. message: '请选择配置'
  378. })
  379. return false
  380. }
  381. if (!item.serialNumber) {
  382. this.$message({
  383. type: 'warning',
  384. message: '序列号不能为空'
  385. })
  386. return false
  387. }
  388. if (!item.mac) {
  389. this.$message({
  390. type: 'warning',
  391. message: 'MAC不能为空'
  392. })
  393. return false
  394. }
  395. if (!validMAC(item.mac)) {
  396. this.$message({
  397. type: 'warning',
  398. message: 'MAC格式不正确,例 ff:ff:ff:ff:ff:ff'
  399. })
  400. return false
  401. }
  402. if (!item.range || !item.range[0] || !item.range[1]) {
  403. this.$message({
  404. type: 'warning',
  405. message: '请选择开关机时间'
  406. })
  407. return false
  408. }
  409. if (item.range[0] >= item.range[1]) {
  410. this.$message({
  411. type: 'warning',
  412. message: '开机时间必须小于关机时间'
  413. })
  414. return false
  415. }
  416. if (!item.address) {
  417. this.$message({
  418. type: 'warning',
  419. message: '地址不能为空'
  420. })
  421. return false
  422. }
  423. if (item.longitude && !validLongitude(item.longitude)) {
  424. this.$message({
  425. type: 'warning',
  426. message: '经度格式错误,-180 ~ +180'
  427. })
  428. return false
  429. }
  430. if (item.latitude && !validLatitude(item.latitude)) {
  431. this.$message({
  432. type: 'warning',
  433. message: '纬度格式错误,-90 ~ +90'
  434. })
  435. return false
  436. }
  437. return true
  438. },
  439. onViewDevice (item) {
  440. this.$router.push({
  441. name: 'device-management-detail',
  442. params: {
  443. id: item.id
  444. }
  445. })
  446. },
  447. onDelDevice (item) {
  448. deleteDevice(item).then(() => {
  449. if (item.isMaster) {
  450. this.$refs.table.decrease(1)
  451. } else {
  452. this.reloadSubDevices(item.parent)
  453. }
  454. })
  455. },
  456. reloadSubDevices (item) {
  457. item.loaded = false
  458. item.children = []
  459. this.getSubDevices(item)
  460. },
  461. getSubDevices (item, pageSize = 999) {
  462. item.loading = true
  463. getSubDevices({
  464. id: item.id,
  465. pageNum: 1,
  466. pageSize
  467. }).then(({ data, totalCount }) => {
  468. if (totalCount > pageSize) {
  469. this.getSubDevices(item, totalCount)
  470. } else {
  471. item.loading = false
  472. item.loaded = true
  473. item.expand = true
  474. if (data.length === 0) {
  475. item.subs = [{ id: `${Math.random()}`, empty: true }]
  476. } else {
  477. item.subs = data.map(device => {
  478. return {
  479. ...device,
  480. isMaster: false,
  481. empty: false,
  482. parent: item
  483. }
  484. })
  485. }
  486. }
  487. }, () => {
  488. item.loading = false
  489. })
  490. },
  491. onRowClick (data) {
  492. if (__SUB_DEVICE__ && data.isMaster) {
  493. if (data.loaded) {
  494. data.expand = !data.expand
  495. } else if (!data.loading) {
  496. this.getSubDevices(data)
  497. }
  498. }
  499. },
  500. onTagClick (data) {
  501. (data.activate ? deactivateDevice : activateDevice)(data).then(() => {
  502. if (data.isMaster) {
  503. this.$refs.table.pageTo()
  504. } else {
  505. this.reloadSubDevices(data.parent)
  506. }
  507. })
  508. },
  509. onSettingDevice ({ id }) {
  510. this.$router.push({
  511. name: 'box-settings',
  512. params: { id }
  513. })
  514. },
  515. onViewMesh ({ id, bound }) {
  516. if (bound) {
  517. this.$refs.meshDialog.show(id)
  518. }
  519. },
  520. onEditRemark (device, { newVal, oldVal }) {
  521. if (newVal === oldVal) {
  522. return
  523. }
  524. device.remark = newVal
  525. updateDevice({
  526. id: device.id,
  527. remark: newVal
  528. }).catch(() => {
  529. device.remark = oldVal
  530. })
  531. },
  532. onReplace (device) {
  533. this.$device = device
  534. this.currObj = {
  535. serialNumber: '',
  536. mac: '',
  537. productId: ''
  538. }
  539. this.$refs.replaceDialog.show()
  540. },
  541. onConfirmReplace (done) {
  542. const { serialNumber, mac, productId } = this.currObj
  543. if (!serialNumber) {
  544. this.$message({
  545. type: 'warning',
  546. message: '序列号不能为空'
  547. })
  548. return
  549. }
  550. if (!mac) {
  551. this.$message({
  552. type: 'warning',
  553. message: 'MAC不能为空'
  554. })
  555. return
  556. }
  557. if (!validMAC(mac)) {
  558. this.$message({
  559. type: 'warning',
  560. message: 'MAC格式不正确,例 ff:ff:ff:ff:ff:ff'
  561. })
  562. return
  563. }
  564. const realMac = mac.toLowerCase()
  565. if (serialNumber === this.$device.serialNumber && realMac === this.$device.mac) {
  566. this.$message({
  567. type: 'warning',
  568. message: '序列号与MAC不能均与原数据一致'
  569. })
  570. return
  571. }
  572. const productName = productId ? this.$refs.productSelect.getOptions().find(({ value }) => value === productId).label : ''
  573. replaceDevice(this.$device.id, { serialNumber, mac: realMac, productId }).then(() => {
  574. this.$device.serialNumber = serialNumber
  575. this.$device.mac = realMac
  576. if (productId) {
  577. this.$device.productId = productId
  578. this.$device.productName = productName
  579. }
  580. this.$device = null
  581. done()
  582. })
  583. }
  584. }
  585. }
  586. </script>