123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- <template>
- <div class="devicePage">
- <div class="searchBar">
- <a-form layout="inline" @keydown.enter="searchHandler">
- <a-form-item label="设备ID">
- <a-input
- v-model:value.trim="searchState.deviceId"
- placeholder="设备ID"
- :maxlength="12"
- show-count
- allow-clear
- @change="clearHandler"
- />
- </a-form-item>
- <a-form-item label="设备名称">
- <a-input
- v-model:value.trim="searchState.deviceName"
- placeholder="设备名称"
- :maxlength="300"
- show-count
- allow-clear
- @change="clearHandler"
- />
- </a-form-item>
- <a-form-item label="设备状态">
- <a-select
- v-model:value="searchState.deviceStatus"
- style="width: 120px"
- :options="deviceStatusOptions"
- @change="searchHandler"
- ></a-select>
- </a-form-item>
- <a-form-item label="创建日期">
- <range-picker
- v-model:start="searchState.createTimeStart"
- v-model:end="searchState.createTimeEnd"
- @change="searchHandler"
- />
- </a-form-item>
- <a-form-item>
- <a-space>
- <a-button type="primary" @click="searchHandler"> 搜索 </a-button>
- <a-button @click="resetHandler"> 重置 </a-button>
- </a-space>
- </a-form-item>
- </a-form>
- </div>
- <div class="tableCard">
- <div class="tableCard-header">
- <div class="tableCard-header-title">
- <span>设备列表</span>
- <span class="subtitle"
- >设备在线数量:<span style="color: #389e0d">{{ onlineDeviceTotal }}</span> /
- {{ allDeviceTotal }} (台)
- </span>
- </div>
- <div class="tableCard-header-extra">
- <a-space>
- <a-button @click="addDeviceHandler">添加设备</a-button>
- <a-button type="primary" @click="uploadDeviceHandler">批量上传设备</a-button>
- </a-space>
- </div>
- </div>
- <a-table
- :columns="columns"
- :data-source="deviceList"
- :loading="loading"
- :pagination="false"
- :scroll="{ x: 'max-content' }"
- >
- <template #bodyCell="{ column, record }">
- <template v-if="column.key === 'online'">
- <a-tag v-if="record.online === -1" :bordered="false" color="gray">未激活</a-tag>
- <a-tag v-if="record.online === 0" :bordered="false" color="red">离线</a-tag>
- <a-tag v-if="record.online === 1" :bordered="false" color="green">在线</a-tag>
- </template>
- <template v-if="column.key === 'activeState'">
- <a-tag v-if="isToday(record.presenceChangeTime)" :bordered="false" color="green"
- >有</a-tag
- >
- <a-tag v-else :bordered="false" color="#ccc">无</a-tag>
- </template>
- <template v-if="column.key === 'action'">
- <a-button type="link" @click="detailHandler(record.devId, record.clientId)"
- >查看详情</a-button
- >
- <a-button type="link" @click="unbindDeviceHandler(record)">解绑设备</a-button>
- </template>
- </template>
- </a-table>
- <base-pagination
- v-if="deviceTotal > 0"
- v-model:current="current"
- v-model:pageSize="pageSize"
- :total="deviceTotal"
- @change="paginationChange"
- @showSizeChange="paginationSizeChange"
- ></base-pagination>
- </div>
- <add-device-modal
- v-model:open="addDeviceOpen"
- title="添加设备"
- :options="tenantOptions"
- @success="searchHandler"
- ></add-device-modal>
- <upload-device-modal
- v-model:open="uploadDeviceOpen"
- title="批量上传设备"
- @success="searchHandler"
- ></upload-device-modal>
- <baseModal v-model:open="unbindOpen" title="解绑设备">
- <a-descriptions title="" bordered :column="1" size="middle">
- <a-descriptions-item label="设备ID">{{ unbindDeviceData.devId }}</a-descriptions-item>
- <a-descriptions-item label="设备名称">{{ unbindDeviceData.devName }}</a-descriptions-item>
- <a-descriptions-item label="设备状态">
- <a-tag v-if="unbindDeviceData.online === -1" :bordered="false" color="gray">未激活</a-tag>
- <a-tag v-if="unbindDeviceData.online === 0" :bordered="false" color="red">离线</a-tag>
- <a-tag v-if="unbindDeviceData.online === 1" :bordered="false" color="green">在线</a-tag>
- </a-descriptions-item>
- <a-descriptions-item label="绑定用户ID">{{ unbindDeviceData.userId }}</a-descriptions-item>
- <a-descriptions-item label="用户手机号">
- {{ unbindDeviceData.userPhone }}
- </a-descriptions-item>
- <a-descriptions-item label="绑定时间">{{ unbindDeviceData.bindTime }}</a-descriptions-item>
- </a-descriptions>
- <template #footer>
- <a-space class="unbindDevice-btn">
- <a-button @click="unbindOpen = false">取消</a-button>
- <a-popconfirm
- title="确认解绑该设备吗?"
- ok-text="确认"
- cancel-text="取消"
- @confirm="confirmUnbindDevice(unbindDeviceData.devId)"
- >
- <a-button type="primary">解绑</a-button>
- </a-popconfirm>
- </a-space>
- </template>
- </baseModal>
- </div>
- </template>
- <script setup lang="ts">
- import * as deviceAPI from '@/api/device'
- import { ref, onActivated } from 'vue'
- import type { Device } from '@/api/device/types'
- import { columns, deviceStatusOptions } from './const'
- import addDeviceModal from './components/addDevice/index.vue'
- import uploadDeviceModal from './components/uploadDevice/index.vue'
- import { useSearch } from '@/hooks/useSearch'
- import { useRouter } from 'vue-router'
- import * as tenantAPI from '@/api/tenant'
- import type { TenantItem } from '@/api/tenant/types'
- import * as adminAPI from '@/api/admin'
- import * as deviceApi from '@/api/device'
- const router = useRouter()
- interface SearchData {
- deviceId: string // 设备ID
- deviceName: string // 设备名称
- deviceStatus: number | null // 设备状态
- createTimeStart: string // 创建时间开始
- createTimeEnd: string // 创建时间结束
- }
- // 默认搜索条件
- const defaultSearch: SearchData = {
- deviceId: '',
- deviceName: '',
- deviceStatus: 1,
- createTimeStart: '',
- createTimeEnd: '',
- }
- const [searchState, resetHandler] = useSearch(defaultSearch, { afterReset: () => searchHandler() })
- defineOptions({
- name: 'DeviceList',
- })
- const deviceList = ref<Device[]>()
- const deviceTotal = ref<number>(0)
- const current = ref<number>(1)
- const pageSize = ref<number>(10)
- // 分页变化
- const paginationChange = (current: number, pageSize: number) => {
- console.log('change', current, pageSize)
- fetchList()
- }
- // 分页大小变化
- const paginationSizeChange = (current: number, pageSize: number) => {
- console.log('showSizeChange', current, pageSize)
- }
- const allDeviceTotal = ref(0) // 所以设备数量
- const onlineDeviceTotal = ref(0) // 在线设备数量
- const offlineDeviceTotal = ref(0) // 离线设备数量
- const loading = ref(false)
- // 获取设备信息
- const fetchList = async () => {
- try {
- loading.value = true
- const res = await deviceAPI.getDeviceList({
- pageNo: current.value,
- pageSize: pageSize.value,
- clientId: searchState.deviceId,
- devName: searchState.deviceName,
- createTimeStart: searchState.createTimeStart,
- createTimeEnd: searchState.createTimeEnd,
- online: searchState.deviceStatus,
- })
- const allDeviceRes = await deviceAPI.getDeviceList({
- pageNo: current.value,
- pageSize: pageSize.value,
- clientId: searchState.deviceId,
- devName: searchState.deviceName,
- createTimeStart: searchState.createTimeStart,
- createTimeEnd: searchState.createTimeEnd,
- online: null,
- })
- const onlineDeviceRes = await deviceAPI.getDeviceList({
- pageNo: current.value,
- pageSize: pageSize.value,
- clientId: searchState.deviceId,
- devName: searchState.deviceName,
- createTimeStart: searchState.createTimeStart,
- createTimeEnd: searchState.createTimeEnd,
- online: 1,
- })
- allDeviceTotal.value = Number(allDeviceRes.data.total) || 0 // 所以设备数量
- onlineDeviceTotal.value = Number(onlineDeviceRes.data.total) || 0 // 在线设备数量
- offlineDeviceTotal.value = allDeviceTotal.value - onlineDeviceTotal.value // 离线设备数量
- console.log('✅获取到设备信息', res, {
- allDeviceTotal: allDeviceTotal.value,
- onlineDeviceTotal: onlineDeviceTotal.value,
- offlineDeviceTotal: offlineDeviceTotal.value,
- })
- const { rows, total } = res.data
- deviceList.value = rows
- deviceTotal.value = Number(total)
- loading.value = false
- } catch (error) {
- console.error('❌ 获取设备信息失败', error)
- loading.value = false
- }
- }
- const tenantOptions = ref<{ label: string; value: string }[]>([])
- // 获取租户列表
- const fetchTenantList = async () => {
- try {
- const res = await tenantAPI.queryTenant({
- pageNo: 1,
- pageSize: 10000,
- })
- const { rows } = res.data
- tenantOptions.value = rows.map((item: TenantItem) => ({
- label: item.tenantName,
- value: item?.tenantId as string,
- }))
- } catch (err) {
- console.log('❌ 获取数据失败', err)
- }
- }
- onActivated(() => {
- fetchList()
- fetchTenantList()
- })
- // 搜索
- const searchHandler = async () => {
- console.log('searchState', searchState)
- current.value = 1
- pageSize.value = 10
- await fetchList()
- }
- // 搜索条件变化时清空搜索结果
- const clearHandler = (e: InputEvent) => {
- console.log('clearHandler', e, e.target, (e.target as HTMLInputElement)?.value)
- if (!(e.target as HTMLInputElement)?.value) {
- fetchList()
- }
- }
- // 详情
- const detailHandler = (devId: string, clientId: string) => {
- console.log('点击详情', { devId, clientId })
- router.push({
- name: 'deviceDetail',
- query: {
- devId,
- clientId,
- },
- })
- }
- const unbindOpen = ref(false)
- const unbindModalLoading = ref(false)
- const unbindDeviceData = ref<{
- devId: string
- clientId: string
- devName: string
- online: number
- userId: number
- userPhone: string
- bindTime: string
- }>({
- devId: '',
- clientId: '',
- devName: '',
- online: 0,
- userId: 0,
- userPhone: '',
- bindTime: '',
- })
- // 解绑设备
- const unbindDeviceHandler = async (device: Device) => {
- console.log('解绑设备')
- unbindDeviceData.value = {
- devId: device.devId + '',
- clientId: device.clientId,
- devName: device.devName,
- online: device.online,
- userId: device.userId,
- userPhone: '',
- bindTime: '',
- }
- unbindOpen.value = true
- await fetchDeviceBindUser(device.userId)
- await fetchDeviceDetail(device.devId)
- }
- // 确认解绑设备
- const confirmUnbindDevice = async (devId: string) => {
- console.log('确认解绑设备')
- try {
- await adminAPI.unbindUser({ devId: devId })
- unbindOpen.value = false
- } catch (err) {
- console.log('解绑设备失败', err)
- }
- }
- // 获取设备绑定用户信息
- const fetchDeviceBindUser = async (userId: number) => {
- console.log('获取设备绑定用户信息', userId)
- if (!userId) return
- try {
- unbindModalLoading.value = true
- const res = await adminAPI.getBindUserInfo({
- userId,
- })
- console.log('获取设备绑定用户信息成功', res)
- unbindModalLoading.value = false
- const data = res.data
- unbindDeviceData.value.userPhone = data.phone
- } catch (err) {
- console.log('获取设备绑定用户信息失败', err)
- unbindModalLoading.value = false
- }
- }
- // 从设备详情获取设备的激活时间
- const fetchDeviceDetail = async (devId: number) => {
- console.log('fetchDeviceDetail', devId)
- if (!devId) return
- try {
- const res = await deviceApi.getDeviceDetailByDevId({
- devId: String(devId),
- })
- console.log('✅获取到设备详情', res)
- const data = res.data
- unbindDeviceData.value.bindTime = data.activeTime
- } catch (error) {
- console.error('❌获取设备详情失败', error)
- }
- }
- const addDeviceOpen = ref(false)
- // 添加设备
- const addDeviceHandler = () => {
- console.log('添加设备')
- addDeviceOpen.value = true
- }
- const uploadDeviceOpen = ref(false)
- // 批量上传设备
- const uploadDeviceHandler = () => {
- console.log('批量上传设备')
- uploadDeviceOpen.value = true
- }
- // 是否为今天
- function isToday(dateStr: string): boolean {
- const inputDate = new Date(dateStr)
- const now = new Date()
- return (
- inputDate.getFullYear() === now.getFullYear() &&
- inputDate.getMonth() === now.getMonth() &&
- inputDate.getDate() === now.getDate()
- )
- }
- </script>
- <style scoped lang="less">
- .devicePage {
- .searchBar {
- padding: 20px;
- background-color: #fff;
- margin-bottom: 20px;
- display: flex;
- justify-content: space-between;
- .ant-form {
- flex-grow: 1;
- }
- :deep(.ant-form-inline .ant-form-item) {
- margin-bottom: 16px !important;
- }
- }
- .tableCard {
- background-color: #fff;
- &-header {
- display: flex;
- justify-content: space-between;
- padding: 20px;
- &-title {
- font-size: 18px;
- font-weight: 600;
- }
- .subtitle {
- font-size: 14px;
- color: #999;
- margin-left: 10px;
- }
- }
- }
- }
- .unbindDevice-btn {
- margin-top: 12px;
- }
- :deep(.ant-descriptions-item-label) {
- width: 150px;
- }
- </style>
|