| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- <template>
- <div>
- <!-- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">-->
- <!-- <div class="search" v-show="showSearch">-->
- <!-- <el-form ref="queryFormRef" :model="state.queryParams" :inline="true" label-width="68px">-->
- <!-- <el-form-item label="部门名称" prop="menuName">-->
- <!-- <el-input v-model="state.queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />-->
- <!-- </el-form-item>-->
- <!-- <el-form-item label="状态" prop="status">-->
- <!-- <el-select v-model="state.queryParams.status" placeholder="部门状态" clearable>-->
- <!-- <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />-->
- <!-- </el-select>-->
- <!-- </el-form-item>-->
- <!-- <el-form-item>-->
- <!-- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>-->
- <!-- <el-button icon="Refresh" @click="resetQuery">重置</el-button>-->
- <!-- </el-form-item>-->
- <!-- </el-form>-->
- <!-- </div>-->
- <!-- </transition>-->
- <yt-crud
- v-bind="options"
- ref="crudRef"
- @onLoad="getData"
- :fun-props="{
- exportBtn: false,
- delBtn: layoutType !== 'card',
- }"
- :table-props="{
- selection: true,
- delBtn: false,
- viewBtn: false,
- editBtn: true,
- customTable: layoutType === 'card',
- menuSlot: true,
- menuWidth: 300,
- }"
- :loading="state.loading"
- :total="state.total"
- v-model:page="state.page"
- v-model:query="state.query"
- @delFun="handleDelete"
- @saveFun="onSave"
- @openBeforeFun="openBeforeFun"
- :addBtn = "hasPermission('iot:device:add')"
- >
- <template #rightToolbar>
- <el-radio-group v-model="layoutType">
- <el-radio-button label="table">
- <svg-icon icon-class="table2" />
- </el-radio-button>
- <el-radio-button label="card">
- <svg-icon icon-class="card" />
- </el-radio-button>
- </el-radio-group>
- </template>
- <template #customTable>
- <el-row class="card-list flex">
- <el-col class="card-item" v-for="(item, index) in data" :key="index" :class="item.state.online ? 'success-box' : 'error-box'">
- <div class="text-box">
- <div class="title flex align-center">
- <div class="title-l">
- <div class="icon">
- <svg-icon icon-class="card2" />
- </div>
- {{ item.deviceName }}
- </div>
- <div class="title-r">
- <status-tag :type="item.state.online ? 'success' : 'danger'" :text="item.state.online ? '在线' : '离线'" />
- </div>
- </div>
- <div class="text flex">
- <div class="txt">
- <div class="txt-item">
- <div class="label">所属产品</div>
- <div class="value active">{{ getProductName(item.productKey) }}</div>
- </div>
- <div class="txt-item">
- <div class="label">设备类型</div>
- <div class="value">{{ getNodeTypeName(item.productKey) }}</div>
- </div>
- <div class="txt-item">
- <div class="copy-tag" v-copyText="item.deviceId" v-copyText:callback="copyIdSuccess">
- <svg-icon icon-class="copy" />
- 设备ID
- </div>
- </div>
- </div>
- <div class="img">
- <img :src="defaultImg" alt="" />
- </div>
- </div>
- </div>
- <div class="btn-group">
- <el-button
- v-if="item.productKey === 'openiitagateway01'"
- class="cu-btn"
- type="success"
- icon="Box"
- plain
- @click="showChidrenDevices(item)"
- >子设备</el-button
- >
- <el-button class="cu-btn" type="primary" icon="EditPen" plain @click="crudRef.handleUpdate(item)" >编辑</el-button>
- <el-button class="cu-btn" type="warning" icon="View" plain @click="handleView(item)">详情</el-button>
- <el-divider direction="vertical" />
- <el-popconfirm title="是否确认删除?" @confirm="handleDelete(item)">
- <template #reference>
- <el-button type="danger" icon="Delete" plain />
- </template>
- </el-popconfirm>
- </div>
- </el-col>
- </el-row>
- </template>
- <template #state="scope">
- <el-tag v-if="scope.row.state.online" type="success" size="small">在线</el-tag>
- <el-tag v-else type="danger" size="small">离线</el-tag>
- </template>
- <template #menuSlot="scope">
- <!-- TODO: 没接口,nodeType无法获取,得改成 != 0 -->
- <el-tooltip class="box-item" effect="dark" content="子设备" placement="top">
- <el-button link icon="Box" :disabled="scope.row.nodeType == 0" @click="showChidrenDevices(scope.row)" />
- </el-tooltip>
- <el-divider direction="vertical" />
- <el-tooltip class="box-item" effect="dark" content="详情" placement="top">
- <el-button link type="primary" icon="View" @click="handleView(scope.row)" />
- </el-tooltip>
- <el-divider direction="vertical" />
- <el-tooltip class="box-item" effect="dark" content="删除" placement="top">
- <el-popconfirm title="是否确认删除该数据" @confirm="handleDelete(scope.row)">
- <template #reference>
- <el-button link type="danger" icon="Delete" />
- </template>
- </el-popconfirm>
- </el-tooltip>
- </template>
- <template #type="{ row }">{{ getNodeTypeName(row.productKey) }}</template>
- <template #deviceMapFormItem="{ row }">
- <div v-if="state.showDeviceMap">
- <Map :clickMap="true" @locateChange="(lnglat) => locateChange(lnglat, row)" :isWrite="true" v-model:center="state.mapLnglat" />
- </div>
- </template>
- </yt-crud>
- <children-dialog ref="childrenDialogRef" />
- </div>
- </template>
- <script lang="ts" setup>
- import defaultImg from '@/assets/images/pic_device.png'
- import { IColumn } from '@/components/common/types/tableCommon'
- import { ComponentInternalInstance } from 'vue'
- import { getDevicesList, deleteDevices, saveDevices,getParentDevices, deleteBatchDevices } from '../api/devices.api'
- import { getProductsList,IProductsVO } from '../api/products.api'
- import Map from '@/components/Map/index.vue'
- import ChildrenDialog from './modules/childrenDialog.vue'
- import YtCrud from '@/components/common/yt-crud.vue'
- import { ElPopconfirm } from 'element-plus'
- import StatusTag from '@/components/StatusTag/index.vue'
- import { hasPermission } from '@/utils/auth'
- import { listDept} from '@/api/system/dept'
- const { proxy } = getCurrentInstance() as ComponentInternalInstance
- interface DeptOptionsType {
- id: number | string
- deptName: string
- children: DeptOptionsType[]
- }
- const state = reactive({
- queryParams: {
- pageNum: 1,
- pageSize: 10,
- deptName: undefined,
- status: undefined,
- },
- page: {
- pageSize: 12,
- pageNum: 1,
- },
- total: 0,
- loading: false,
- showDeviceMap: false,
- mapLnglat: '' as any,
- query: {},
- })
- const layoutType = ref('card')
- // 查看详情
- const router = useRouter()
- const handleView = (row: any) => {
- if (!row.id) return
- let showMap=false
- productOptions.value.forEach((p) => {
- if (p.productKey == row.productKey ) {
- showMap=p.isOpenLocate
- }
- })
- router.push(`devicesDetail/${row.id}?showMap=${showMap}`)
- }
- const nodeTypeOptions = [
- {
- value: 0,
- label: '网关设备',
- }, {
- value: 1,
- label: '网关子设备',
- }, {
- value: 2,
- label: '直连设备',
- },
- ]
- // const { proxy } = getCurrentInstance() as ComponentInternalInstance
- // 打开子设备
- const childrenDialogRef = ref()
- const showChidrenDevices = (row: any) => {
- childrenDialogRef.value.openDialog(row)
- }
- // 复制ID
- const copyIdSuccess = () => {
- proxy?.$modal.msgSuccess('复制成功')
- }
- // 产品字典
- const productOptions = ref<IProductsVO[]>([])
- const deptOptions = ref<DeptOptionsType[]>([])
- // 组列表
- const groupOptions = [
- {
- 'id': 'g3',
- 'name': '组3',
- 'uid': 'fa1c5eaa-de6e-48b6-805e-8f091c7bb831',
- 'remark': '2223333',
- 'deviceQty': 17,
- 'createAt': 1659872082792
- },
- {
- 'id': 'g2',
- 'name': '组2',
- 'uid': 'fa1c5eaa-de6e-48b6-805e-8f091c7bb831',
- 'remark': '222',
- 'deviceQty': 21,
- 'createAt': 1659872082803
- },
- {
- 'id': 'g1',
- 'name': '分组1',
- 'uid': 'fa1c5eaa-de6e-48b6-805e-8f091c7bb831',
- 'remark': '1111',
- 'deviceQty': 10,
- 'createAt': 1659872082805
- }
- ]
- const column = ref<IColumn[]>([{
- label: '设备ID',
- key: 'deviceId',
- formHide: true,
- rules: [{ required: true, message: 'ProductKey不能为空' }],
- }, {
- label: '产品',
- key: 'productKey',
- type: 'select',
- search: true,
- colSpan:12,
- tableWidth: 120,
- editDisabled: true,
- componentProps: {
- labelAlias: 'name',
- valueAlias: 'productKey',
- options: [],
- },
- rules: [{ required: true, message: '产品名称不能为空' }],
- formWatch: (scope) => {
- scope.column.forEach((f: IColumn) => {
- if (['parentId', 'longitude', 'latitude'].includes(f.key)) {
- if (!scope.value) {
- f.formHide = true
- state.showDeviceMap = false
- return
- }
- productOptions.value.forEach((p)=>{
- if (p.productKey == scope.value ) {
- if (f.key === 'parentId') {
- f.formHide = p.nodeType !== 1
- } else {
- const flag = p.isOpenLocate && 'manual' == p.locateUpdateType
- state.showDeviceMap = flag
- f.formHide = !flag
- }
- }
- })
- }
- })
- column.value = scope.column
- }
- }, {
- label: '设备类型',
- key: 'type',
- slot: true,
- formHide: true,
- }, {
- label: '网关设备',
- key: 'parentId',
- type: 'select',
- colSpan: 12,
- tableWidth: 120,
- hide: true,
- formHide: true,
- componentProps: {
- labelAlias: 'deviceName',
- valueAlias: 'id',
- options: [],
- placeholder: '子设备可选择父设备'
- },
- }, {
- label: '设备DN',
- key: 'deviceName',
- tableWidth: 240,
- componentProps: {
- placeholder: '一般为设备mac'
- },
- rules: [{ required: true, message: '设备DN不能为空' }],
- },
- {
- label: '设备经度',
- key: 'longitude',
- hide: true,
- formHide: true,
- colSpan: 12,
- },
- {
- label: '设备纬度',
- key: 'latitude',
- hide: true,
- formHide: true,
- colSpan: 12,
- },
- {
- label: '设备地图',
- key: 'deviceMap',
- hide: true,
- formItemSlot: true,
- },
- // , {
- // label: '分组',
- // key: 'group',
- // type: 'select',
- // componentProps: {
- // labelAlias: 'name',
- // valueAlias: 'id',
- // options: groupOptions,
- // },
- // }
- {
- label: '状态',
- key: 'state',
- type: 'select',
- componentProps: {
- options: [
- {
- label: '在线',
- value: 'online',
- },
- {
- label: '离线',
- value: 'offline',
- }
- ]
- },
- search: true,
- formHide: true,
- tableWidth: 80,
- slot: true,
- }, {
- label: '关键字',
- key: 'keyword',
- search: true,
- hide: true,
- formHide: true,
- },
- {
- label: '所属部门',
- key: 'createDept',
- search: true,
- type: 'select',
- hide: true,
- formHide: true,
- componentProps: {
- labelAlias: 'deptName',
- valueAlias: 'id',
- options: [],
- }
- },
- {
- label: '创建时间',
- key: 'createAt',
- tableWidth: 180,
- sortable: true,
- type: 'date',
- formHide: true,
- }])
- const crudRef = ref()
- const data = ref<any[]>([])
- const getData = () => {
- state.loading = true
- getDevicesList({
- ...state.page,
- ...state.query,
- }).then((res) => {
- data.value = res.data.rows
- state.total = res.data.total
- }).finally(() => {
- state.loading = false
- })
- }
- const getDict = () => {
- getProductsList({
- pageNum: 1,
- pageSize: 99999,
- }).then(res => {
- productOptions.value = res.data.rows || []
- column.value.forEach(item => {
- if (item.key === 'productKey') {
- item.componentProps.options = productOptions.value
- }
- })
- })
- }
- getDict()
- const getDept = () => {
- listDept().then((res) => {
- // const data = proxy?.handleTree<DeptOptionsType>(res.data, 'id')
- deptOptions.value = res.data || []
- // console.log("################"+JSON.stringify(deptOptions.value))
- column.value.forEach(item => {
- if (item.key === 'createDept') {
- item.componentProps.options = deptOptions.value
- }
- })
- // const data = proxy?.handleTree<DeptOptionsType>(res.data, 'id')
- // console.log("####################"+JSON.stringify(data))
- // if (data) {
- // deptOptions.value = data
- // }
- })
- }
- getDept()
- const getProductName = (key: string) => {
- return productOptions.value.find(f => f.productKey === key)?.name || ''
- }
- const getNodeTypeName = (key) => {
- const type = productOptions.value.find(f => f.productKey === key)?.nodeType
- return nodeTypeOptions.find(f => f.value === type)?.label || ''
- }
- // 保存数据
- const onSave = async ({type, data, cancel}: any) => {
- state.loading = true
- await saveDevices(toRaw(data))
- state.loading = false
- cancel()
- getData()
- }
- // 弹窗前置操作
- const openBeforeFun = ({type, data}) => {
- if (type === 'add') {
- state.mapLnglat=''
- } else if (type === 'update') {
- const latitude = data?.locate?.latitude || ''
- const longitude = data?.locate?.longitude || ''
- state.mapLnglat = longitude + ',' + latitude
- }
- }
- const parentDevices = async () => {
- let data = await getParentDevices()
- column.value.forEach(item => {
- if (item.key === 'parentId') {
- item.componentProps.options = data.data
- }
- })
- }
- parentDevices()
- // 删除
- const handleDelete = async (row: any) => {
- state.loading = true
- if (row instanceof Array) {
- await deleteBatchDevices(row.map(m => m.id))
- } else {
- await deleteDevices(row.id)
- }
- ElMessage.success('删除成功!')
- state.loading = false
- getData()
- }
- const locateChange=(e, row)=> {
- if (!e) return
- row.longitude = e[0] || ''
- row.latitude = e[1] || ''
- }
- const options = reactive({
- ref: 'crudRef',
- data,
- column,
- })
- </script>
- <style lang="scss" scoped>
- ::v-deep(.el-radio-button__inner) {
- padding: 8px;
- }
- ::v-deep(.el-radio-button__original-radio:checked+.el-radio-button__inner) {
- border: 1px solid #0070ffff;
- background: #0070ff1a;
- box-shadow: none;
- svg {
- path {
- fill: #0070ffff;
- }
- }
- }
- .card-list {
- .card-item {
- border: 1px solid #d8dee5;
- border-radius: 3px;
- margin-right: 16px;
- margin-bottom: 16px;
- flex: 0 0 calc(25% - 12px);
- width: calc(25% - 12px);
- &.success-box {
- .text-box {
- background: linear-gradient(141.6deg, rgb(238, 250, 255) 0%, rgba(255, 255, 255, 0) 80%);
- }
- }
- &.error-box {
- .text-box {
- background: linear-gradient(141.6deg, rgb(255, 241, 241) 0%, rgba(255, 255, 255, 0) 80%);
- }
- }
- &:nth-child(4n) {
- margin-right: 0;
- }
- .text-box {
- padding: 16px;
- .title {
- font-size: 16px;
- font-weight: 600;
- align-items: center;
- margin-bottom: 12px;
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .title-l {
- display: flex;
- align-items: center;
- }
- .icon {
- margin-right: 10px;
- display: flex;
- align-items: center;
- }
- }
- .text {
- align-items: center;
- font-size: 14px;
- .txt {
- flex: 1;
- .txt-item {
- margin-bottom: 10px;
- &:last-child {
- margin-bottom: 0;
- }
- .copy-tag {
- padding: 4px 8px;
- background-color: #FFF7EF;
- color: #FF7D00;
- display: inline-flex;
- align-items: center;
- transition: 0.3s ease;
- cursor: pointer;
- &:hover {
- opacity: 0.8;
- transform: translateY(-2px);
- }
- svg {
- margin-right: 8px;
- }
- }
- }
- border-radius: 2px;
- .label {
- display: inline-block;
- margin-right: 10px;
- color: #717C8E;
- }
- .value {
- display: inline-block;
- color: #0B1D30;
- &.active {
- color: #0070FF;
- }
- }
- }
- .img {
- width: 100px;
- height: 100px;
- img {
- width: 100%;
- height: auto;
- }
- }
- }
- }
- .btn-group {
- padding: 12px 16px;
- border-top: 1px solid #DCDFE1;
- .cu-btn {
- width: calc((100% - 73px) / 3);
- }
- .el-button {
- padding: 8px;
- }
- display: flex;
- justify-content: flex-end;
- align-items: center;
- }
- }
- }
- @media screen and (max-width: 1560px) {
- .card-list .card-item .btn-group {
- padding: 12px;
- .el-button {
- font-size: 12px;
- }
- .cu-btn {
- width: calc((100% - 59px) / 3);
- }
- .el-button+.el-button {
- margin-left: 6px;
- }
- }
- }
- @media screen and (max-width: 1400px) {
- .card-list .card-item {
- width: calc(100% / 3 - 8px);
- flex: 0 0 calc(100% / 3 - 8px);
- margin-right: 12px;
- &:nth-child(4n) {
- margin-right: 12px;
- }
- &:nth-child(3n) {
- margin-right: 0;
- }
- }
- }
- </style>
|