Showing
6 changed files
with
85 additions
and
17 deletions
| @@ -187,6 +187,7 @@ export interface DeviceRecord { | @@ -187,6 +187,7 @@ export interface DeviceRecord { | ||
| 187 | brand?: string; | 187 | brand?: string; |
| 188 | deviceProfileId: string; | 188 | deviceProfileId: string; |
| 189 | organizationId: string; | 189 | organizationId: string; |
| 190 | + alarmStatus: number; | ||
| 190 | deviceProfile: { | 191 | deviceProfile: { |
| 191 | default: boolean; | 192 | default: boolean; |
| 192 | name: string; | 193 | name: string; |
| @@ -4,12 +4,12 @@ | @@ -4,12 +4,12 @@ | ||
| 4 | <Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> | 4 | <Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> |
| 5 | <PopConfirmButton v-bind="action"> | 5 | <PopConfirmButton v-bind="action"> |
| 6 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> | 6 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
| 7 | - <template v-if="action.label">{{ action.label }}</template> | 7 | + <template v-if="action.label"><Label :label="action.label" /></template> |
| 8 | </PopConfirmButton> | 8 | </PopConfirmButton> |
| 9 | </Tooltip> | 9 | </Tooltip> |
| 10 | <PopConfirmButton v-else v-bind="action"> | 10 | <PopConfirmButton v-else v-bind="action"> |
| 11 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> | 11 | <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" /> |
| 12 | - <template v-if="action.label">{{ action.label }}</template> | 12 | + <template v-if="action.label"><Label :label="action.label" /> </template> |
| 13 | </PopConfirmButton> | 13 | </PopConfirmButton> |
| 14 | <Divider | 14 | <Divider |
| 15 | type="vertical" | 15 | type="vertical" |
| @@ -36,7 +36,7 @@ | @@ -36,7 +36,7 @@ | ||
| 36 | </div> | 36 | </div> |
| 37 | </template> | 37 | </template> |
| 38 | <script lang="ts"> | 38 | <script lang="ts"> |
| 39 | - import { defineComponent, PropType, computed, toRaw, unref } from 'vue'; | 39 | + import { defineComponent, PropType, computed, toRaw, unref, VNode } from 'vue'; |
| 40 | // import { MoreOutlined } from '@ant-design/icons-vue'; | 40 | // import { MoreOutlined } from '@ant-design/icons-vue'; |
| 41 | import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; | 41 | import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; |
| 42 | import Icon from '/@/components/Icon/index'; | 42 | import Icon from '/@/components/Icon/index'; |
| @@ -52,7 +52,14 @@ | @@ -52,7 +52,14 @@ | ||
| 52 | 52 | ||
| 53 | export default defineComponent({ | 53 | export default defineComponent({ |
| 54 | name: 'TableAction', | 54 | name: 'TableAction', |
| 55 | - components: { Icon, PopConfirmButton, Divider, Dropdown, Tooltip }, | 55 | + components: { |
| 56 | + Icon, | ||
| 57 | + PopConfirmButton, | ||
| 58 | + Divider, | ||
| 59 | + Dropdown, | ||
| 60 | + Tooltip, | ||
| 61 | + Label: (props: { label: VNode | string }) => props.label, | ||
| 62 | + }, | ||
| 56 | props: { | 63 | props: { |
| 57 | actions: { | 64 | actions: { |
| 58 | type: Array as PropType<ActionItem[]>, | 65 | type: Array as PropType<ActionItem[]>, |
| 1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; | 1 | import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; |
| 2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; | 2 | import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; |
| 3 | import { RoleEnum } from '/@/enums/roleEnum'; | 3 | import { RoleEnum } from '/@/enums/roleEnum'; |
| 4 | +import { VNode } from 'vue'; | ||
| 4 | export interface ActionItem extends ButtonProps { | 5 | export interface ActionItem extends ButtonProps { |
| 5 | onClick?: Fn; | 6 | onClick?: Fn; |
| 6 | - label?: string; | 7 | + label?: string | VNode; |
| 7 | color?: 'success' | 'error' | 'warning'; | 8 | color?: 'success' | 'error' | 'warning'; |
| 8 | icon?: string; | 9 | icon?: string; |
| 9 | popConfirm?: PopConfirm; | 10 | popConfirm?: PopConfirm; |
| @@ -70,6 +70,7 @@ export const columns: BasicColumn[] = [ | @@ -70,6 +70,7 @@ export const columns: BasicColumn[] = [ | ||
| 70 | title: '状态', | 70 | title: '状态', |
| 71 | dataIndex: 'deviceState', | 71 | dataIndex: 'deviceState', |
| 72 | width: 110, | 72 | width: 110, |
| 73 | + className: 'device-status', | ||
| 73 | slots: { customRender: 'deviceState' }, | 74 | slots: { customRender: 'deviceState' }, |
| 74 | }, | 75 | }, |
| 75 | { | 76 | { |
| @@ -19,7 +19,20 @@ | @@ -19,7 +19,20 @@ | ||
| 19 | <Tabs.TabPane key="modelOfMatter" tab="物模型数据"> | 19 | <Tabs.TabPane key="modelOfMatter" tab="物模型数据"> |
| 20 | <ModelOfMatter :deviceDetail="deviceDetail" /> | 20 | <ModelOfMatter :deviceDetail="deviceDetail" /> |
| 21 | </Tabs.TabPane> | 21 | </Tabs.TabPane> |
| 22 | - <Tabs.TabPane key="3" tab="告警"> | 22 | + <Tabs.TabPane key="3"> |
| 23 | + <template #tab> | ||
| 24 | + <Badge :offset="[2, -5]" style="color: inherit"> | ||
| 25 | + <span>告警</span> | ||
| 26 | + <template #count> | ||
| 27 | + <div | ||
| 28 | + :style="{ visibility: deviceDetail.alarmStatus ? 'visible' : 'hidden' }" | ||
| 29 | + class="w-3.5 h-3.5 !flex justify-center items-center rounded-1 border-red-400 border" | ||
| 30 | + > | ||
| 31 | + <Icon icon="mdi:bell-warning" color="#f46161" :size="12" class="!mr-0" /> | ||
| 32 | + </div> | ||
| 33 | + </template> | ||
| 34 | + </Badge> | ||
| 35 | + </template> | ||
| 23 | <AlarmLog :device-id="deviceDetail.id" class="bg-gray-100" /> | 36 | <AlarmLog :device-id="deviceDetail.id" class="bg-gray-100" /> |
| 24 | </Tabs.TabPane> | 37 | </Tabs.TabPane> |
| 25 | <Tabs.TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> | 38 | <Tabs.TabPane key="4" tab="子设备" v-if="deviceDetail?.deviceType === 'GATEWAY'"> |
| @@ -51,7 +64,7 @@ | @@ -51,7 +64,7 @@ | ||
| 51 | <script lang="ts" setup> | 64 | <script lang="ts" setup> |
| 52 | import { ref, computed } from 'vue'; | 65 | import { ref, computed } from 'vue'; |
| 53 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | 66 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
| 54 | - import { Tabs } from 'ant-design-vue'; | 67 | + import { Tabs, Badge } from 'ant-design-vue'; |
| 55 | import Detail from '../tabs/Detail.vue'; | 68 | import Detail from '../tabs/Detail.vue'; |
| 56 | import ChildDevice from '../tabs/ChildDevice.vue'; | 69 | import ChildDevice from '../tabs/ChildDevice.vue'; |
| 57 | import TBoxDetail from '../tabs/TBoxDetail.vue'; | 70 | import TBoxDetail from '../tabs/TBoxDetail.vue'; |
| @@ -62,6 +75,7 @@ | @@ -62,6 +75,7 @@ | ||
| 62 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; | 75 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
| 63 | import Task from '../tabs/Task.vue'; | 76 | import Task from '../tabs/Task.vue'; |
| 64 | import AlarmLog from '/@/views/alarm/log/index.vue'; | 77 | import AlarmLog from '/@/views/alarm/log/index.vue'; |
| 78 | + import { Icon } from '/@/components/Icon'; | ||
| 65 | 79 | ||
| 66 | const emit = defineEmits(['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail']); | 80 | const emit = defineEmits(['reload', 'register', 'openTbDeviceDetail', 'openGatewayDeviceDetail']); |
| 67 | 81 |
| @@ -106,13 +106,17 @@ | @@ -106,13 +106,17 @@ | ||
| 106 | </Tag> | 106 | </Tag> |
| 107 | </template> | 107 | </template> |
| 108 | <template #deviceState="{ record }"> | 108 | <template #deviceState="{ record }"> |
| 109 | - <!-- <HeartOutlined v-if="!record.isCollect" class="mr-1" style="color: red" /> --> | ||
| 110 | - <Tooltip> | ||
| 111 | - <template #title> 我的收藏</template> | ||
| 112 | - <HeartTwoTone v-if="record.isCollect" class="mr-1" twoToneColor="#3B82F6" /> | ||
| 113 | - </Tooltip> | 109 | + <div v-if="record.isCollect"> |
| 110 | + <div class="absolute top-0 left-0 device-collect"> </div> | ||
| 111 | + <Icon | ||
| 112 | + icon="ph:star-fill" | ||
| 113 | + class="fill-light-50 absolute top-0.5 left-0.5" | ||
| 114 | + color="#fff" | ||
| 115 | + :size="12" | ||
| 116 | + /> | ||
| 117 | + </div> | ||
| 118 | + | ||
| 114 | <Tag | 119 | <Tag |
| 115 | - :style="{ marginLeft: !record.isCollect ? '17px' : '' }" | ||
| 116 | :color=" | 120 | :color=" |
| 117 | record.deviceState == DeviceState.INACTIVE | 121 | record.deviceState == DeviceState.INACTIVE |
| 118 | ? 'warning' | 122 | ? 'warning' |
| @@ -135,7 +139,7 @@ | @@ -135,7 +139,7 @@ | ||
| 135 | <TableAction | 139 | <TableAction |
| 136 | :actions="[ | 140 | :actions="[ |
| 137 | { | 141 | { |
| 138 | - label: '详情', | 142 | + label: AlarmDetailActionButton({ hasAlarm: !!record.alarmStatus }), |
| 139 | icon: 'ant-design:eye-outlined', | 143 | icon: 'ant-design:eye-outlined', |
| 140 | auth: DeviceListAuthEnum.DETAIL, | 144 | auth: DeviceListAuthEnum.DETAIL, |
| 141 | onClick: handleDetail.bind(null, record), | 145 | onClick: handleDetail.bind(null, record), |
| @@ -232,7 +236,7 @@ | @@ -232,7 +236,7 @@ | ||
| 232 | </div> | 236 | </div> |
| 233 | </template> | 237 | </template> |
| 234 | <script lang="ts" setup> | 238 | <script lang="ts" setup> |
| 235 | - import { reactive, onMounted, ref } from 'vue'; | 239 | + import { reactive, onMounted, ref, h, CSSProperties } from 'vue'; |
| 236 | import { | 240 | import { |
| 237 | DeviceModel, | 241 | DeviceModel, |
| 238 | DeviceRecord, | 242 | DeviceRecord, |
| @@ -241,8 +245,7 @@ | @@ -241,8 +245,7 @@ | ||
| 241 | } from '/@/api/device/model/deviceModel'; | 245 | } from '/@/api/device/model/deviceModel'; |
| 242 | import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table'; | 246 | import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table'; |
| 243 | import { columns, DeviceListAuthEnum, searchFormSchema } from './config/device.data'; | 247 | import { columns, DeviceListAuthEnum, searchFormSchema } from './config/device.data'; |
| 244 | - import { Tag, Popover, Button, Tooltip } from 'ant-design-vue'; | ||
| 245 | - import { HeartTwoTone } from '@ant-design/icons-vue'; | 248 | + import { Tag, Popover, Button, Badge } from 'ant-design-vue'; |
| 246 | import { | 249 | import { |
| 247 | deleteDevice, | 250 | deleteDevice, |
| 248 | devicePage, | 251 | devicePage, |
| @@ -278,6 +281,7 @@ | @@ -278,6 +281,7 @@ | ||
| 278 | } from './cpns/modal/BatchUpdateProductModal'; | 281 | } from './cpns/modal/BatchUpdateProductModal'; |
| 279 | import { DataActionModeEnum } from '/@/enums/toolEnum'; | 282 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 280 | import { AuthDropDown } from '/@/components/Widget'; | 283 | import { AuthDropDown } from '/@/components/Widget'; |
| 284 | + import Icon from '/@/components/Icon'; | ||
| 281 | 285 | ||
| 282 | const { isCustomer } = useAuthDeviceDetail(); | 286 | const { isCustomer } = useAuthDeviceDetail(); |
| 283 | const { createMessage } = useMessage(); | 287 | const { createMessage } = useMessage(); |
| @@ -294,6 +298,32 @@ | @@ -294,6 +298,32 @@ | ||
| 294 | const [registerImportModal, { openModal: openImportModal }] = useModal(); | 298 | const [registerImportModal, { openModal: openImportModal }] = useModal(); |
| 295 | const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal(); | 299 | const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal(); |
| 296 | 300 | ||
| 301 | + const AlarmDetailActionButton = ({ hasAlarm }: { hasAlarm?: boolean }) => | ||
| 302 | + h( | ||
| 303 | + Badge, | ||
| 304 | + { offset: [0, -5] }, | ||
| 305 | + { | ||
| 306 | + default: () => h('span', { style: { color: '#377dff' } }, '详情'), | ||
| 307 | + count: () => | ||
| 308 | + h( | ||
| 309 | + 'div', | ||
| 310 | + { | ||
| 311 | + style: { | ||
| 312 | + visibility: hasAlarm ? 'visible' : 'hidden', | ||
| 313 | + width: '14px', | ||
| 314 | + height: '14px', | ||
| 315 | + display: 'flex', | ||
| 316 | + justifyContent: 'center', | ||
| 317 | + alignItems: 'center', | ||
| 318 | + border: '1px solid #f46161', | ||
| 319 | + borderRadius: '50%', | ||
| 320 | + } as CSSProperties, | ||
| 321 | + }, | ||
| 322 | + h(Icon, { icon: 'mdi:bell-warning', color: '#f46161', size: 12 }) | ||
| 323 | + ), | ||
| 324 | + } | ||
| 325 | + ); | ||
| 326 | + | ||
| 297 | const batchUpdateProductFlag = ref(true); | 327 | const batchUpdateProductFlag = ref(true); |
| 298 | 328 | ||
| 299 | const [ | 329 | const [ |
| @@ -336,6 +366,7 @@ | @@ -336,6 +366,7 @@ | ||
| 336 | rowKey: 'id', | 366 | rowKey: 'id', |
| 337 | searchInfo: searchInfo, | 367 | searchInfo: searchInfo, |
| 338 | clickToRowSelect: false, | 368 | clickToRowSelect: false, |
| 369 | + rowClassName: (record) => ((record as DeviceRecord).alarmStatus ? 'device-alarm-badge' : ''), | ||
| 339 | actionColumn: { | 370 | actionColumn: { |
| 340 | width: 200, | 371 | width: 200, |
| 341 | title: '操作', | 372 | title: '操作', |
| @@ -566,3 +597,16 @@ | @@ -566,3 +597,16 @@ | ||
| 566 | } | 597 | } |
| 567 | } | 598 | } |
| 568 | </style> | 599 | </style> |
| 600 | + | ||
| 601 | +<style lang="less"> | ||
| 602 | + .device-status { | ||
| 603 | + position: relative; | ||
| 604 | + | ||
| 605 | + .device-collect { | ||
| 606 | + width: 0; | ||
| 607 | + height: 0; | ||
| 608 | + border-top: 30px solid #377dff; | ||
| 609 | + border-right: 30px solid transparent; | ||
| 610 | + } | ||
| 611 | + } | ||
| 612 | +</style> |