Device.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <template>
  2. <schema-table
  3. ref="table"
  4. row-key="id"
  5. :schema="schema"
  6. @row-click="onRowClick"
  7. >
  8. <confirm-dialog
  9. ref="editDialog"
  10. :title="dialogTitle"
  11. @confirm="onSave"
  12. >
  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-input
  30. v-model.trim="currObj.serialNumber"
  31. placeholder="最多50个字符"
  32. maxlength="50"
  33. clearable
  34. />
  35. <span class="c-grid-form__label u-required">MAC</span>
  36. <el-input
  37. v-model.trim="currObj.mac"
  38. class="u-width--sm"
  39. placeholder="ff:ff:ff:ff:ff:ff"
  40. maxlength="17"
  41. clearable
  42. />
  43. <div class="c-grid-form__label u-required">开关机时间</div>
  44. <el-time-picker
  45. v-model="currObj.range"
  46. class="u-width u-pointer"
  47. is-range
  48. value-format="HH:mm:ss"
  49. :clearable="false"
  50. />
  51. <span class="c-grid-form__label u-required">地址</span>
  52. <el-input
  53. v-model.trim="currObj.address"
  54. type="textarea"
  55. placeholder="最多100个字符"
  56. maxlength="100"
  57. :rows="2"
  58. show-word-limit
  59. />
  60. <span class="c-grid-form__label">坐标</span>
  61. <div class="l-flex--row c-grid-form__option">
  62. <span class="c-sibling-item">{{ currObj.longitude }},{{ currObj.latitude }}</span>
  63. <i
  64. class="c-sibling-item el-icon-edit u-color--blue has-active"
  65. @click="onEditCoordinate"
  66. />
  67. </div>
  68. </div>
  69. </confirm-dialog>
  70. <coordinate-dialog
  71. ref="coordinateDialog"
  72. @confirm="onChangeCoordinate"
  73. />
  74. <mesh-dialog ref="meshDialog" />
  75. </schema-table>
  76. </template>
  77. <script>
  78. import {
  79. validMAC,
  80. validLongitude,
  81. validLatitude
  82. } from '@/utils/validate'
  83. import {
  84. getDevicesByTenant,
  85. addDevice,
  86. deleteDevice,
  87. getSubDevices,
  88. addSubDevice,
  89. activateDevice,
  90. deactivateDevice,
  91. getProducts
  92. } from '@/api/device'
  93. import MeshDialog from '../../components/MeshDialog.vue'
  94. export default {
  95. name: 'Device',
  96. components: {
  97. MeshDialog
  98. },
  99. props: {
  100. tenant: {
  101. type: String,
  102. required: true
  103. }
  104. },
  105. data () {
  106. const productSelectSchema = {
  107. remote: this.getProducts,
  108. pagination: true,
  109. value: 'id',
  110. label: 'name'
  111. }
  112. return {
  113. currObj: {},
  114. isSub: false,
  115. ratio: null,
  116. productSelectSchema,
  117. schema: {
  118. keepalive: true,
  119. list: this.getDevicesByTenant,
  120. transform: this.transform,
  121. transformData: __SUB_DEVICE__ && this.transformTableData,
  122. buttons: [
  123. { type: 'add', on: this.onAddDevice }
  124. ],
  125. filters: [
  126. { key: 'productId', type: 'select', placeholder: '配置', ...productSelectSchema },
  127. { key: 'boundFlag', type: 'select', placeholder: '使用情况', options: [
  128. { value: 1, label: '已使用' },
  129. { value: 0, label: '未使用' }
  130. ] },
  131. { key: 'serialNumber', type: 'search', placeholder: '序列号' },
  132. { key: 'mac', type: 'search', placeholder: 'MAC' },
  133. { key: 'name', type: 'search', placeholder: '设备名称' }
  134. ],
  135. cols: [
  136. { type: 'refresh', render: __SUB_DEVICE__
  137. ? (data, h) => data.isMaster
  138. ? h('i', {
  139. staticClass: `o-expand-icon u-pointer ${data.loading ? 'el-icon-loading' : 'el-icon-arrow-right'}`,
  140. class: { expand: data.expand }
  141. })
  142. : null
  143. : null },
  144. __SUB_DEVICE__
  145. ? { label: '设备名称', render: (data, h) => data.empty ? h('span', { staticClass: 'u-color--info' }, '暂无备份设备') : data.name, 'min-width': 120 }
  146. : { prop: 'name', label: '设备名称', 'min-width': 120 },
  147. { prop: 'productName', label: '配置' },
  148. { prop: 'serialNumber', label: '序列号', 'min-width': 140 },
  149. { prop: 'mac', label: 'MAC', 'min-width': 140 },
  150. { type: 'tag', render: ({ empty, activate, onlineStatus }) => empty
  151. ? null
  152. : activate
  153. ? onlineStatus === 0
  154. ? { type: 'primary', label: '待接入' }
  155. : onlineStatus === 1
  156. ? { type: 'success', label: '在线' }
  157. : { type: 'danger', label: '离线' }
  158. : { type: 'warning', label: '未激活' },
  159. on: this.onTagClick },
  160. { label: '使用情况', type: 'tag', render: ({ bound }) => bound
  161. ? { type: 'success', label: '已使用' }
  162. : { type: 'primary', label: '未使用' } },
  163. { type: 'invoke', render: [
  164. { label: '配置', render: ({ isMaster }) => isMaster, on: this.onSettingDevice },
  165. __SUB_DEVICE__ && { label: '添加备份', render: ({ isMaster }) => isMaster, on: this.onAddSubDevice },
  166. { label: '所属网点', render: ({ isMaster }) => isMaster, allow: ({ bound }) => bound, on: this.onViewMesh },
  167. { label: '删除', render: ({ empty }) => !empty, on: this.onDelDevice }
  168. ].filter(Boolean), width: __SUB_DEVICE__ ? 240 : 180 }
  169. ]
  170. }
  171. }
  172. },
  173. computed: {
  174. dialogTitle () {
  175. return this.isSub ? '新增备份设备' : '新增设备'
  176. }
  177. },
  178. methods: {
  179. getProducts (params) {
  180. return getProducts({
  181. tenant: this.tenant,
  182. ...params
  183. })
  184. },
  185. getDevicesByTenant (params) {
  186. return getDevicesByTenant(this.tenant, params)
  187. },
  188. transform (data) {
  189. return {
  190. ...data,
  191. loaded: false,
  192. loading: false,
  193. expand: false,
  194. subs: [],
  195. isMaster: true
  196. }
  197. },
  198. transformTableData (data) {
  199. const arr = []
  200. data.forEach(item => {
  201. arr.push(item)
  202. if (item.loaded && item.expand) {
  203. arr.push(...item.subs)
  204. }
  205. })
  206. return arr
  207. },
  208. onAddDevice () {
  209. this.isSub = false
  210. this.$master = null
  211. this.productSelectSchema.option = null
  212. this.onAdd()
  213. },
  214. onAddSubDevice (item) {
  215. this.isSub = true
  216. this.$master = item
  217. this.productSelectSchema.option = { value: item.productId, label: item.productName }
  218. this.onAdd()
  219. },
  220. onAdd () {
  221. if (this.isSub) {
  222. const { productId, openTime, closeTime, address, longitude, latitude } = this.$master
  223. this.currObj = {
  224. name: '',
  225. productId,
  226. serialNumber: '',
  227. mac: '',
  228. range: [openTime, closeTime],
  229. address,
  230. longitude,
  231. latitude,
  232. tenant: this.tenant
  233. }
  234. } else {
  235. this.currObj = {
  236. name: '',
  237. productId: '',
  238. serialNumber: '',
  239. mac: '',
  240. range: ['00:00:00', '23:59:59'],
  241. address: '',
  242. longitude: '',
  243. latitude: '',
  244. tenant: this.tenant
  245. }
  246. }
  247. this.$refs.editDialog.show()
  248. },
  249. onEditCoordinate () {
  250. const { longitude, latitude, address } = this.currObj
  251. this.$refs.coordinateDialog.show({
  252. longitude,
  253. latitude,
  254. address
  255. })
  256. },
  257. onChangeCoordinate ({ value: { longitude, latitude, address }, done }) {
  258. this.currObj.longitude = longitude
  259. this.currObj.latitude = latitude
  260. this.currObj.address = address
  261. done()
  262. },
  263. onSave (done) {
  264. if (this.check(this.currObj)) {
  265. const { range, ...data } = this.currObj
  266. if (this.isSub) {
  267. addSubDevice(this.$master, {
  268. openTime: range[0],
  269. closeTime: range[1],
  270. ...data
  271. }).then(() => {
  272. done()
  273. this.reloadSubDevices(this.$master)
  274. })
  275. } else {
  276. addDevice({
  277. openTime: range[0],
  278. closeTime: range[1],
  279. ...data
  280. }).then(() => {
  281. done()
  282. this.$refs.table.resetCondition({
  283. boundFlag: 0,
  284. name: this.currObj.name,
  285. productId: this.currObj.productId,
  286. serialNumber: this.currObj.serialNumber,
  287. mac: this.currObj.mac
  288. })
  289. })
  290. }
  291. }
  292. },
  293. check (item) {
  294. if (!item.name) {
  295. this.$message({
  296. type: 'warning',
  297. message: '名称不能为空'
  298. })
  299. return false
  300. }
  301. if (!item.productId) {
  302. this.$message({
  303. type: 'warning',
  304. message: '请选择配置'
  305. })
  306. return false
  307. }
  308. if (!item.serialNumber) {
  309. this.$message({
  310. type: 'warning',
  311. message: '序列号不能为空'
  312. })
  313. return false
  314. }
  315. if (!item.mac) {
  316. this.$message({
  317. type: 'warning',
  318. message: 'MAC不能为空'
  319. })
  320. return false
  321. }
  322. if (!validMAC(item.mac)) {
  323. this.$message({
  324. type: 'warning',
  325. message: 'MAC格式不正确,例 ff:ff:ff:ff:ff:ff'
  326. })
  327. return false
  328. }
  329. item.mac = item.mac.toLowerCase()
  330. if (!item.range || !item.range[0] || !item.range[1]) {
  331. this.$message({
  332. type: 'warning',
  333. message: '请选择开关机时间'
  334. })
  335. return false
  336. }
  337. if (item.range[0] >= item.range[1]) {
  338. this.$message({
  339. type: 'warning',
  340. message: '开机时间必须小于关机时间'
  341. })
  342. return false
  343. }
  344. if (!item.address) {
  345. this.$message({
  346. type: 'warning',
  347. message: '地址不能为空'
  348. })
  349. return false
  350. }
  351. if (item.longitude && !validLongitude(item.longitude)) {
  352. this.$message({
  353. type: 'warning',
  354. message: '经度格式错误,-180 ~ +180'
  355. })
  356. return false
  357. }
  358. if (item.latitude && !validLatitude(item.latitude)) {
  359. this.$message({
  360. type: 'warning',
  361. message: '纬度格式错误,-90 ~ +90'
  362. })
  363. return false
  364. }
  365. return true
  366. },
  367. onViewDevice (item) {
  368. this.$router.push({
  369. name: 'device-management-detail',
  370. params: {
  371. id: item.id
  372. }
  373. })
  374. },
  375. onDelDevice (item) {
  376. deleteDevice(item).then(() => {
  377. if (item.isMaster) {
  378. this.$refs.table.decrease(1)
  379. } else {
  380. this.reloadSubDevices(item.parent)
  381. }
  382. })
  383. },
  384. reloadSubDevices (item) {
  385. item.loaded = false
  386. item.children = []
  387. this.getSubDevices(item)
  388. },
  389. getSubDevices (item, pageSize = 999) {
  390. item.loading = true
  391. getSubDevices({
  392. id: item.id,
  393. pageNum: 1,
  394. pageSize
  395. }).then(({ data, totalCount }) => {
  396. if (totalCount > pageSize) {
  397. this.getSubDevices(item, totalCount)
  398. } else {
  399. item.loading = false
  400. item.loaded = true
  401. item.expand = true
  402. if (data.length === 0) {
  403. item.subs = [{ id: `${Math.random()}`, empty: true }]
  404. } else {
  405. item.subs = data.map(device => {
  406. return {
  407. ...device,
  408. isMaster: false,
  409. empty: false,
  410. parent: item
  411. }
  412. })
  413. }
  414. }
  415. }, () => {
  416. item.loading = false
  417. })
  418. },
  419. onRowClick (data) {
  420. if (__SUB_DEVICE__ && data.isMaster) {
  421. if (data.loaded) {
  422. data.expand = !data.expand
  423. } else if (!data.loading) {
  424. this.getSubDevices(data)
  425. }
  426. }
  427. },
  428. onTagClick (data) {
  429. (data.activate ? deactivateDevice : activateDevice)(data).then(() => {
  430. if (data.isMaster) {
  431. this.$refs.table.pageTo()
  432. } else {
  433. this.reloadSubDevices(data.parent)
  434. }
  435. })
  436. },
  437. onSettingDevice ({ id }) {
  438. this.$router.push({
  439. name: 'box-settings',
  440. params: { id }
  441. })
  442. },
  443. onViewMesh ({ id }) {
  444. this.$refs.meshDialog.show(id)
  445. }
  446. }
  447. }
  448. </script>