index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. <template>
  2. <div class="page page-full">
  3. <el-row :gutter="15" class="h-full">
  4. <el-col :span="6" class="h-full">
  5. <el-card shadow="nover" class="h-full">
  6. <el-scrollbar v-loading="treeLoading">
  7. <el-input :prefix-icon="Search" v-model="searchVal" placeholder="请输入设备树名称" clearable style="width: 100%;" />
  8. <el-button v-if="!treeLoading && !treeData.length" type="primary" v-auth="'add'" class="mt-2" @click="operateCmd('add', {})" style="width: 100%">新建节点</el-button>
  9. <el-tree ref="zlTreeSearchRef" class="mt-4" v-if="!treeLoading" :data="treeData" :props="{
  10. children: 'children',
  11. label: 'name'
  12. }" :filter-node-method="filterNode" :default-expand-all="true" :node-key="'id'" highlight-current @node-click="nodeClick">
  13. <template #default="{ node, data }">
  14. <div class="custom-tree-node">
  15. <span class="tree-label">
  16. <!-- <i class="iconfont icon-wenjianjia icon-wjj mr8" /> -->
  17. {{ node.label }}
  18. </span>
  19. <span class="tree-options" @click.stop v-auth="'edit'">
  20. <slot name="operate" :data="data">
  21. <el-dropdown @command="command => operateCmd(command, data)">
  22. <el-icon>
  23. <More></More>
  24. </el-icon>
  25. <template #dropdown>
  26. <el-dropdown-menu>
  27. <el-dropdown-item command="add">
  28. <el-icon>
  29. <Plus></Plus>
  30. </el-icon>
  31. </el-dropdown-item>
  32. <el-dropdown-item command="edit">
  33. <el-icon>
  34. <Edit></Edit>
  35. </el-icon>
  36. </el-dropdown-item>
  37. <el-dropdown-item command="delete">
  38. <el-icon>
  39. <Delete></Delete>
  40. </el-icon>
  41. </el-dropdown-item>
  42. </el-dropdown-menu>
  43. </template>
  44. </el-dropdown>
  45. </slot>
  46. </span>
  47. </div>
  48. </template>
  49. </el-tree>
  50. </el-scrollbar>
  51. </el-card>
  52. </el-col>
  53. <el-col :span="18" class="h-full">
  54. <el-card shadow="nover" class="h-full" v-if="treeDetail.name">
  55. <el-form :model="ruleForm" ref="formRef" label-width="80px">
  56. <el-tabs v-model="tabName" @tab-click="onTabClick">
  57. <el-tab-pane label="设备树信息" name="1">
  58. <el-descriptions class="margin-top" :column="3" border>
  59. <el-descriptions-item label="名称">{{ treeDetail.name }}</el-descriptions-item>
  60. <el-descriptions-item label="设备标识">{{ treeDetail.deviceKey }}</el-descriptions-item>
  61. <el-descriptions-item label="联系人">{{ treeDetail.contact }}</el-descriptions-item>
  62. <el-descriptions-item label="经纬度">{{ treeDetail.lng }}, {{ treeDetail.lat }}</el-descriptions-item>
  63. <el-descriptions-item label="联系电话">{{ treeDetail.phone }}</el-descriptions-item>
  64. <el-descriptions-item label="类型">{{ treeDetail.types }}</el-descriptions-item>
  65. <el-descriptions-item label="服务周期" span="2">{{ treeDetail.startDate }} - {{ treeDetail.endDate }}</el-descriptions-item>
  66. <el-descriptions-item label="图片">
  67. <el-image :src="treeDetail.image" :previewSrcList="[treeDetail.image]" style="width: 60px;height: 60px;">
  68. <template #error>
  69. <div class="image-slot">
  70. <ele-Picture style="width: 30px;" />
  71. 加载失败
  72. </div>
  73. </template>
  74. </el-image>
  75. </el-descriptions-item>
  76. <el-descriptions-item label="地址">{{ treeDetail.address }}</el-descriptions-item>
  77. <el-descriptions-item label="所属公司">{{ treeDetail.company }}</el-descriptions-item>
  78. <el-descriptions-item label="设备所属区域">{{ treeDetail.area }}</el-descriptions-item>
  79. </el-descriptions>
  80. <!-- <table>
  81. <tbody>
  82. <tr class="ant-descriptions-row">
  83. <th class="ant-descriptions-item-label ant-descriptions-item-colon">名称</th>
  84. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.name }}</td>
  85. <th class="ant-descriptions-item-label ant-descriptions-item-colon">地址</th>
  86. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.address }}</td>
  87. <th class="ant-descriptions-item-label ant-descriptions-item-colon">经度</th>
  88. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.lng }}</td>
  89. </tr>
  90. <tr class="ant-descriptions-row">
  91. <th class="ant-descriptions-item-label ant-descriptions-item-colon">纬度</th>
  92. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.lat }}</td>
  93. <th class="ant-descriptions-item-label ant-descriptions-item-colon">联系人</th>
  94. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.contact }}</td>
  95. <th class="ant-descriptions-item-label ant-descriptions-item-colon">联系电话</th>
  96. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.phone }}</td>
  97. </tr>
  98. <tr class="ant-descriptions-row">
  99. <th class="ant-descriptions-item-label ant-descriptions-item-colon">服务周期:开始日期</th>
  100. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.startDate }}</td>
  101. <th class="ant-descriptions-item-label ant-descriptions-item-colon">服务周期:截止日期</th>
  102. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.endDate }}</td>
  103. <th class="ant-descriptions-item-label ant-descriptions-item-colon">图片</th>
  104. <td class="ant-descriptions-item-content" colspan="1">
  105. <el-image :src="treeDetail.image" v-if="treeDetail.image" />
  106. </td>
  107. </tr>
  108. <tr class="ant-descriptions-row">
  109. <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备标识</th>
  110. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.deviceKey }}</td>
  111. <th class="ant-descriptions-item-label ant-descriptions-item-colon">设备所属区域</th>
  112. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.area }}</td>
  113. <th class="ant-descriptions-item-label ant-descriptions-item-colon">所属公司</th>
  114. <td class="ant-descriptions-item-content" colspan="1">{{ treeDetail.company }}</td>
  115. </tr>
  116. <tr class="ant-descriptions-row">
  117. <th class="ant-descriptions-item-label ant-descriptions-item-colon">类型</th>
  118. <td class="ant-descriptions-item-content" colspan="5">{{ treeDetail.types }}</td>
  119. </tr>
  120. </tbody>
  121. </table> -->
  122. </el-tab-pane>
  123. <el-tab-pane label="时间周期" name="2">
  124. <el-form-item label="开始日期" prop="startDate">
  125. <el-date-picker v-model="ruleForm.startDate" type="date" placeholder="选择开始日期" class="w-35" :size="'default'" />
  126. </el-form-item>
  127. <el-form-item label="结束日期" prop="endDate">
  128. <el-date-picker v-model="ruleForm.endDate" type="date" placeholder="选择结束日期" class="w-35" :size="'default'" />
  129. </el-form-item>
  130. <!-- <el-form-item label="类型" prop="template">
  131. <el-select v-model="ruleForm.template" filterable clearable placeholder="请选择类型" class="w-35">
  132. <el-option v-for="dict in tree_types" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
  133. </el-select>
  134. </el-form-item>
  135. <el-form-item label="分类" prop="category">
  136. <el-select v-model="ruleForm.category" filterable clearable placeholder="请选择分类" class="w-35">
  137. <el-option v-for="dict in tree_category" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
  138. </el-select>
  139. </el-form-item> -->
  140. <el-form-item label=" " prop="category">
  141. <el-button type="primary" v-auth="'save'" @click="onSaveTime">保存</el-button>
  142. </el-form-item>
  143. </el-tab-pane>
  144. <el-tab-pane label="绑定实际设备" name="3">
  145. <el-form-item label="选择设备" prop="deviceKey">
  146. <el-select v-model="ruleForm.deviceKey" filterable placeholder="请选择设备">
  147. <el-option v-for="item in deviceList" :key="item.key" :label="item.name" :value="item.key">
  148. <span style="float: left">{{ item.name }}</span>
  149. <!-- <span style="float: right; font-size: 13px">{{ item.key }}</span> -->
  150. </el-option>
  151. </el-select>
  152. </el-form-item>
  153. <el-form-item label=" " prop="category">
  154. <el-button type="primary" v-auth="'save'" @click="onSaveTime">保存</el-button>
  155. </el-form-item>
  156. </el-tab-pane>
  157. </el-tabs>
  158. </el-form>
  159. </el-card>
  160. </el-col>
  161. </el-row>
  162. <AddOrUpdate ref="addOrUpdateRef" @finish="onFinish" />
  163. </div>
  164. </template>
  165. <script lang="ts">
  166. import { toRefs, reactive, onMounted, ref, defineComponent, watch } from 'vue';
  167. import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
  168. import AddOrUpdate from './component/edit.vue';
  169. import api from '/@/api/device';
  170. import { More, Plus, Edit, Delete, Search } from '@element-plus/icons-vue'
  171. // 定义接口来定义对象的类型
  172. interface TableDataRow {
  173. id: number;
  174. name: string;
  175. deviceType: string;
  176. status: number;
  177. desc: string;
  178. createBy: string;
  179. }
  180. interface TableDataState {
  181. ids: number[];
  182. tableData: {
  183. data: Array<TableDataRow>;
  184. total: number;
  185. loading: boolean;
  186. param: {
  187. pageNum: number;
  188. pageSize: number;
  189. name: string;
  190. status: string;
  191. };
  192. };
  193. treeData: any[]
  194. deviceList: any[]
  195. treeLoading: boolean
  196. tabName: string
  197. searchVal: string
  198. treeDetail: any
  199. unitData: any
  200. currentTree: any
  201. }
  202. export default defineComponent({
  203. name: 'deviceTree',
  204. components: { AddOrUpdate, More, Plus, Edit, Delete },
  205. setup() {
  206. const addOrUpdateRef = ref();
  207. const queryRef = ref();
  208. const state = reactive<TableDataState>({
  209. ids: [],
  210. tableData: {
  211. data: [],
  212. total: 0,
  213. loading: false,
  214. param: {
  215. pageNum: 1,
  216. pageSize: 20,
  217. status: '',
  218. name: ''
  219. },
  220. },
  221. treeData: [],
  222. treeLoading: false,
  223. tabName: '1',
  224. searchVal: '',
  225. treeDetail: {},
  226. deviceList: [],
  227. unitData: [
  228. { label: '秒', value: 1 },
  229. { label: '分', value: 2 },
  230. { label: '时', value: 3 },
  231. { label: '天', value: 4 },
  232. ],
  233. currentTree: {}
  234. });
  235. let ruleForm = ref({
  236. startDate: '',
  237. endDate: '',
  238. deviceKey: ''
  239. })
  240. let zlTreeSearchRef = ref()
  241. watch(() => state.searchVal, (val) => {
  242. zlTreeSearchRef.value!.filter(val);
  243. });
  244. const filterNode = (value: string, data: any) => {
  245. if (!value) return true;
  246. return data.name.includes(value);
  247. };
  248. // 初始化表格数据
  249. const initTableData = () => {
  250. getTreeList();
  251. getDeviceList()
  252. };
  253. const getTreeList = () => {
  254. state.treeLoading = true;
  255. api.tree.getList({}).then((res: any) => {
  256. state.treeData = res.list;
  257. }).finally(() => (state.treeLoading = false));
  258. }
  259. const getDeviceList = () => {
  260. api.device.allList({}).then((res: any) => {
  261. state.deviceList = res.device || [];
  262. })
  263. }
  264. const typeList = () => {
  265. };
  266. // 页面加载时
  267. onMounted(() => {
  268. initTableData();
  269. });
  270. /** 重置按钮操作 */
  271. const resetQuery = (formEl: FormInstance | undefined) => {
  272. if (!formEl) return;
  273. formEl.resetFields();
  274. typeList();
  275. };
  276. // 多选框选中数据
  277. const handleSelectionChange = (selection: TableDataRow[]) => {
  278. state.ids = selection.map((item) => item.id);
  279. };
  280. const nodeClick = (data: any) => {
  281. state.currentTree = data
  282. getTreeDetail(data)
  283. }
  284. const getTreeDetail = (data: any) => {
  285. api.tree.detail({ infoId: data.infoId })
  286. .then((res: any) => {
  287. state.treeDetail = res.data || {}
  288. ruleForm.value.startDate = state.treeDetail.startDate
  289. ruleForm.value.endDate = state.treeDetail.endDate
  290. ruleForm.value.deviceKey = state.treeDetail.deviceKey
  291. })
  292. }
  293. const onFinish = () => {
  294. getTreeList()
  295. getTreeDetail(state.currentTree)
  296. }
  297. const onTabClick = () => {
  298. // ruleForm.value.startDate = ''
  299. // ruleForm.value.endDate = ''
  300. // ruleForm.value.deviceKey = ''
  301. }
  302. const onSaveTime = () => {
  303. if (!state.treeDetail.id) {
  304. ElMessage.warning('请选择节点树')
  305. return
  306. }
  307. if (!(new Date(ruleForm.value.endDate) >= new Date(ruleForm.value.startDate))) {
  308. ElMessage.warning('开始时间不能大于结束时间')
  309. return
  310. }
  311. //修改
  312. api.tree.edit({
  313. ...state.treeDetail,
  314. endDate: ruleForm.value.endDate,
  315. startDate: ruleForm.value.startDate,
  316. deviceKey: ruleForm.value.deviceKey,
  317. }).then(() => {
  318. ElMessage.success('修改成功');
  319. });
  320. }
  321. const operateCmd = (type: string, data: any) => {
  322. switch (type) {
  323. case 'add':
  324. addOrUpdateRef.value.openDialog(type, data)
  325. break
  326. case 'edit':
  327. addOrUpdateRef.value.openDialog(type, data)
  328. break
  329. case 'delete':
  330. ElMessageBox.confirm('是否删除该设备树?', '提示', {
  331. confirmButtonText: '确认',
  332. cancelButtonText: '取消',
  333. type: 'warning',
  334. })
  335. .then(() => {
  336. api.tree.delete({ id: data.infoId }).then(() => {
  337. ElMessage.success('删除成功');
  338. state.searchVal = ''
  339. state.treeDetail = {}
  340. getTreeList();
  341. });
  342. })
  343. .catch(() => { });
  344. break
  345. }
  346. }
  347. return {
  348. addOrUpdateRef,
  349. queryRef,
  350. zlTreeSearchRef,
  351. typeList,
  352. resetQuery,
  353. handleSelectionChange,
  354. operateCmd,
  355. getTreeList,
  356. nodeClick,
  357. ...toRefs(state),
  358. ruleForm,
  359. Search,
  360. onSaveTime,
  361. filterNode,
  362. onFinish,
  363. onTabClick
  364. };
  365. },
  366. });
  367. </script>
  368. <style scoped lang="scss">
  369. .el-card :deep(.el-card__body) {
  370. height: 100%;
  371. }
  372. .el-tree :deep(.el-tree-node__label) {
  373. width: 100%;
  374. overflow: hidden;
  375. display: block;
  376. }
  377. // :deep(.el-card__body) {
  378. // padding: 0;
  379. // }
  380. .zl-tree-search {
  381. margin: 0 12px 0 0;
  382. border-radius: 4px;
  383. &__filter {
  384. padding: 4px 10px;
  385. border-bottom: 1px solid #EAEAEA;
  386. }
  387. }
  388. .zl-tree-search__filter :deep .el-input__wrapper {
  389. box-shadow: none;
  390. }
  391. .custom-tree-node {
  392. width: 100%;
  393. display: flex;
  394. align-items: center;
  395. justify-content: space-between;
  396. font-size: 14px;
  397. padding-right: 8px;
  398. overflow: hidden;
  399. .tree-label {
  400. width: 100%;
  401. overflow: hidden;
  402. text-overflow: ellipsis;
  403. white-space: nowrap;
  404. margin-right: 10px;
  405. }
  406. &:hover {
  407. .tree-options {
  408. display: block;
  409. }
  410. }
  411. }
  412. :deep(.column-right) {
  413. padding: 0;
  414. background-color: transparent;
  415. }
  416. .icon-wjj {
  417. font-size: 12px;
  418. color: #C4C6CF;
  419. }
  420. table {
  421. width: 100%;
  422. border-collapse: collapse;
  423. text-indent: initial;
  424. border-spacing: 2px;
  425. border: 1px solid #e8e8e8;
  426. }
  427. tbody {
  428. box-sizing: border-box;
  429. display: table-row-group;
  430. vertical-align: middle;
  431. border-color: inherit;
  432. }
  433. tr {
  434. display: table-row;
  435. vertical-align: inherit;
  436. border-color: inherit;
  437. }
  438. .ant-descriptions-view {
  439. width: 100%;
  440. overflow: hidden;
  441. border-radius: 4px;
  442. }
  443. .ant-descriptions-view {
  444. border: 1px solid #e8e8e8;
  445. }
  446. .ant-descriptions-view table {
  447. width: 100%;
  448. table-layout: fixed;
  449. }
  450. .ant-descriptions-view>table {
  451. table-layout: auto;
  452. }
  453. .ant-descriptions-row {
  454. border-bottom: 1px solid #e8e8e8;
  455. }
  456. .ant-descriptions-item-label {
  457. color: rgba(0, 0, 0, 0.85);
  458. font-weight: 400;
  459. font-size: 14px;
  460. line-height: 1.5;
  461. }
  462. .ant-descriptions-item-label {
  463. padding: 16px;
  464. border-right: 1px solid #e8e8e8;
  465. }
  466. .ant-descriptions-item-label {
  467. background-color: #fafafa;
  468. }
  469. .ant-descriptions-item-content {
  470. padding: 16px;
  471. border-right: 1px solid #e8e8e8;
  472. display: table-cell;
  473. color: rgba(0, 0, 0, 0.65);
  474. font-size: 14px;
  475. line-height: 1.5;
  476. }
  477. </style>