设备卡片调整
This commit is contained in:
parent
780240d96a
commit
67dce31b56
284
src/views/devices/DevFrom.vue
Normal file
284
src/views/devices/DevFrom.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="设备ID" prop="deviceId">
|
||||
<el-input v-model="formData.deviceId" placeholder="请输入设备ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属机构" prop="orgId">
|
||||
<el-select v-model="formData.orgId" placeholder="请选择所属机构" @change="handleProductChange">
|
||||
<el-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="formData.deviceType" placeholder="请选择设备类型">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_DEVICE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备位置" prop="address">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-cascader
|
||||
v-model="selectedOptions"
|
||||
:options="options"
|
||||
@change="handleAddressChange"
|
||||
placeholder="请选择省/市/区"
|
||||
clearable
|
||||
class="w-[450px]"
|
||||
/>
|
||||
<el-input
|
||||
v-model="formData.detailAddress"
|
||||
placeholder="请输入详细地址"
|
||||
class="w-[450px]"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备状态" prop="state">
|
||||
<el-select v-model="formData.state" placeholder="请选择设备状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_DEVICE_STATE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
placeholder="请输入设备描述"
|
||||
:rows="3"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as DeviceApi from '@/api/iot/device/device'
|
||||
import * as ProductApi from '@/api/iot/product/product'
|
||||
import * as DeviceGroupApi from '@/api/iot/device/group'
|
||||
import { DeviceStateEnum } from '@/api/iot/device/device'
|
||||
import { FormRules } from 'element-plus'
|
||||
import { regionData, codeToText } from 'element-china-area-data'
|
||||
|
||||
interface CascaderOption {
|
||||
value: string
|
||||
label: string
|
||||
children?: CascaderOption[]
|
||||
}
|
||||
|
||||
defineOptions({ name: 'DeviceForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<DeviceApi.DeviceVO>({
|
||||
id: undefined,
|
||||
deviceKey: undefined,
|
||||
deviceName: undefined,
|
||||
productId: undefined,
|
||||
productKey: undefined,
|
||||
deviceType: undefined,
|
||||
nickname: undefined,
|
||||
gatewayId: undefined,
|
||||
state: DeviceStateEnum.INACTIVE,
|
||||
onlineTime: undefined,
|
||||
offlineTime: undefined,
|
||||
activeTime: undefined,
|
||||
createTime: undefined,
|
||||
ip: undefined,
|
||||
firmwareVersion: undefined,
|
||||
deviceSecret: undefined,
|
||||
mqttClientId: undefined,
|
||||
mqttUsername: undefined,
|
||||
mqttPassword: undefined,
|
||||
authType: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
areaId: undefined,
|
||||
address: undefined,
|
||||
serialNumber: undefined,
|
||||
config: undefined,
|
||||
groupIds: []
|
||||
})
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }],
|
||||
orgId: [{ required: true, message: '所属机构不能为空', trigger: 'blur' }],
|
||||
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'blur' }],
|
||||
address: [{ required: true, message: '设备位置不能为空', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
const formRef = ref() // 表单 Ref
|
||||
const productList = ref<ProductApi.ProductVO[]>([]) // 产品列表
|
||||
const groupList = ref<DeviceGroupApi.DeviceGroupVO[]>([]) // 分组列表
|
||||
const selectedOptions = ref<string[]>([]) // 添加级联选择器的值
|
||||
const options = ref<CascaderOption[]>(regionData as unknown as CascaderOption[])
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await DeviceApi.getDevice(id)
|
||||
// 处理地址数据
|
||||
if (formData.value.address) {
|
||||
const addressParts = formData.value.address.split('/')
|
||||
if (addressParts.length >= 3) {
|
||||
// 获取省市区编码
|
||||
const provinceCode = Object.keys(codeToText).find(key => codeToText[key] === addressParts[0])
|
||||
let cityCode = Object.keys(codeToText).find(key => codeToText[key] === addressParts[1])
|
||||
if(provinceCode == '12'){
|
||||
cityCode = '1201'
|
||||
}
|
||||
const areaCode = Object.keys(codeToText).find(key => codeToText[key] === addressParts[2])
|
||||
if (provinceCode && cityCode && areaCode) {
|
||||
selectedOptions.value = [provinceCode, cityCode, areaCode]
|
||||
}
|
||||
|
||||
// 设置详细地址
|
||||
if (addressParts.length > 3) {
|
||||
formData.value.detailAddress = addressParts.slice(3).join('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 获得产品列表
|
||||
productList.value = await ProductApi.getSimpleProductList()
|
||||
// 获得分组列表
|
||||
groupList.value = await DeviceGroupApi.getSimpleDeviceGroupList()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value
|
||||
if (formType.value === 'create') {
|
||||
await DeviceApi.createDevice(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await DeviceApi.updateDevice(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
deviceKey: undefined,
|
||||
deviceName: undefined,
|
||||
productId: undefined,
|
||||
productKey: undefined,
|
||||
deviceType: undefined,
|
||||
nickname: undefined,
|
||||
gatewayId: undefined,
|
||||
state: DeviceStateEnum.INACTIVE,
|
||||
onlineTime: undefined,
|
||||
offlineTime: undefined,
|
||||
activeTime: undefined,
|
||||
createTime: undefined,
|
||||
ip: undefined,
|
||||
firmwareVersion: undefined,
|
||||
deviceSecret: undefined,
|
||||
mqttClientId: undefined,
|
||||
mqttUsername: undefined,
|
||||
mqttPassword: undefined,
|
||||
authType: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
areaId: undefined,
|
||||
address: undefined,
|
||||
serialNumber: undefined,
|
||||
config: undefined,
|
||||
groupIds: []
|
||||
}
|
||||
selectedOptions.value = []
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 检查设备标识 */
|
||||
const checkDeviceKey = async () => {
|
||||
// TODO: 实现设备标识检查逻辑
|
||||
message.warning('设备标识检查功能待实现')
|
||||
}
|
||||
|
||||
/** 处理产品变更 */
|
||||
const handleProductChange = (productId: number) => {
|
||||
const product = productList.value.find(item => item.id === productId)
|
||||
if (product) {
|
||||
formData.value.productKey = product.productKey
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理地址变化 */
|
||||
const handleAddressChange = (value: any) => {
|
||||
if (value && value.length === 3) {
|
||||
const address = codeToText[value[0]] + '/' + codeToText[value[1]] + '/' + codeToText[value[2]]
|
||||
if (formData.value.detailAddress) {
|
||||
formData.value.address = address + '/' + formData.value.detailAddress
|
||||
} else {
|
||||
formData.value.address = address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听详细地址变化
|
||||
watch(() => formData.value.detailAddress, (newValue) => {
|
||||
if (selectedOptions.value && selectedOptions.value.length === 3) {
|
||||
const address = codeToText[selectedOptions.value[0]] + '/' + codeToText[selectedOptions.value[1]] + '/' + codeToText[selectedOptions.value[2]]
|
||||
if (newValue) {
|
||||
formData.value.address = address + '/' + newValue
|
||||
} else {
|
||||
formData.value.address = address
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
300
src/views/devices/devices_cards.vue
Normal file
300
src/views/devices/devices_cards.vue
Normal file
@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<el-card class="device-card" :body-style="{ padding: '20px' }" :data-status="deviceInfo.status">
|
||||
<div class="card-content">
|
||||
<div class="device-info">
|
||||
<div class="name-section">
|
||||
<h3 class="device-name">{{ deviceInfo.name }}</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="detail-btn"
|
||||
text
|
||||
@click="handleAction('details')"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="device-status" :class="deviceInfo.status">
|
||||
{{ deviceInfo.statusText }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="device-details">
|
||||
<p class="detail-item">
|
||||
<span class="label">设备ID:</span>
|
||||
<span class="value">{{ deviceInfo.id }}</span>
|
||||
</p>
|
||||
<p class="detail-item">
|
||||
<span class="label">类型:</span>
|
||||
<span class="value">{{ deviceInfo.type }}</span>
|
||||
</p>
|
||||
<p class="detail-item">
|
||||
<span class="label">位置:</span>
|
||||
<span class="value">{{ deviceInfo.location }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="deviceInfo.status === 'normal'"
|
||||
@click="handleAction('enable')"
|
||||
>
|
||||
启用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
:disabled="deviceInfo.status === 'error'"
|
||||
@click="handleAction('disable')"
|
||||
>
|
||||
停用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleAction('settings')"
|
||||
>
|
||||
设置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
size="small"
|
||||
@click="handleAction('delete')"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
import { Delete } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
deviceInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
location: '',
|
||||
status: 'normal',
|
||||
statusText: '正常'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
const handleAction = (action) => {
|
||||
emit('action', {
|
||||
action,
|
||||
deviceId: props.deviceInfo.id
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-card {
|
||||
width: 300px;
|
||||
margin: 10px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(145deg, #ffffff, #f5f7fa);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.device-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.name-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.device-card:hover .device-name {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.device-status.normal {
|
||||
background-color: #67C23A;
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3);
|
||||
}
|
||||
|
||||
.device-status.warning {
|
||||
background-color: #F7BA2A;
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(247, 186, 42, 0.3);
|
||||
}
|
||||
|
||||
.device-status.error {
|
||||
background-color: #909399;
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(144, 147, 153, 0.3);
|
||||
}
|
||||
|
||||
.device-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.device-card:hover .detail-item {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #b7b7bb, transparent);
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.action-buttons .el-button {
|
||||
flex: 1;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-buttons .el-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
padding: 0 !important;
|
||||
background-color: #f38787 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #f38787 !important;
|
||||
}
|
||||
|
||||
.delete-btn .el-icon {
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
height: auto;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.detail-btn:hover {
|
||||
color: #79bbff;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 在线状态卡片样式 */
|
||||
.device-card[data-status="normal"] {
|
||||
background: linear-gradient(145deg, #e6f3ff, #fafafa);
|
||||
border: 1px solid rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 在线状态卡片悬停效果 */
|
||||
.device-card[data-status="normal"]:hover {
|
||||
background: linear-gradient(145deg, #d9edff, #fafafa);
|
||||
box-shadow: 0 8px 30px rgba(64, 158, 255, 0.15);
|
||||
}
|
||||
|
||||
/* 待激活状态卡片样式 */
|
||||
.device-card[data-status="warning"] {
|
||||
/* 淡粉色渐变背景,从淡粉色过渡到纯白色 */
|
||||
background: linear-gradient(145deg, #f7ecec, #fafafa);
|
||||
/* 淡粉色边框 */
|
||||
border: 1px solid rgba(247, 186, 42, 0.2);
|
||||
}
|
||||
|
||||
/* 待激活状态卡片悬停效果 */
|
||||
.device-card[data-status="warning"]:hover {
|
||||
/* 悬停时加深粉色渐变效果 */
|
||||
background: linear-gradient(145deg, #f7ecec, #fafafa);
|
||||
/* 添加淡粉色阴影效果 */
|
||||
box-shadow: 0 8px 30px rgba(247, 186, 42, 0.15);
|
||||
}
|
||||
|
||||
/* 离线状态卡片样式 */
|
||||
.device-card[data-status="error"] {
|
||||
background: linear-gradient(145deg, #e4e4e4, #fafafa);
|
||||
border: 1px solid rgba(247, 186, 42, 0.2);
|
||||
}
|
||||
|
||||
/* 离线状态卡片悬停效果 */
|
||||
.device-card[data-status="error"]:hover {
|
||||
background: linear-gradient(145deg, #e4e4e4, #fafafa);
|
||||
box-shadow: 0 8px 30px rgba(247, 186, 42, 0.15);
|
||||
}
|
||||
</style>
|
173
src/views/devices/index.vue
Normal file
173
src/views/devices/index.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="设备名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入设备名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择设备状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 设备类型 -->
|
||||
<el-form-item label="设备类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择设备类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 设备卡片展示区域 -->
|
||||
<ContentWrap>
|
||||
<div class="device-cards-container">
|
||||
<device-card
|
||||
v-for="device in deviceList"
|
||||
:key="device.id"
|
||||
:device-info="device"
|
||||
@action="handleDeviceAction"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 设备表单 -->
|
||||
<DeviceForm ref="deviceFormRef" @success="handleQuery" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElButton } from 'element-plus'
|
||||
import { getIntDictOptions } from '@/utils/dict'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import DeviceCard from '../devices/devices_cards.vue'
|
||||
import DeviceForm from './DevFrom.vue'
|
||||
|
||||
// 查询参数
|
||||
interface QueryParams {
|
||||
name: string
|
||||
status: number | undefined
|
||||
type: number | undefined
|
||||
}
|
||||
|
||||
const queryParams = reactive<QueryParams>({
|
||||
name: '',
|
||||
status: undefined,
|
||||
type: undefined
|
||||
})
|
||||
|
||||
// 设备列表数据
|
||||
const deviceList = ref([
|
||||
{
|
||||
id: 'Blood001',
|
||||
name: '血压仪',
|
||||
type: '血压仪',
|
||||
location: '天津市/西青区/精武镇',
|
||||
status: 'normal',
|
||||
statusText: '在线'
|
||||
},
|
||||
{
|
||||
id: 'Glucose001',
|
||||
name: '血糖仪',
|
||||
type: '血糖仪',
|
||||
location: '河北省/廊坊市/安次区',
|
||||
status: 'warning',
|
||||
statusText: '待激活'
|
||||
},
|
||||
{
|
||||
id: 'Oxygen001',
|
||||
name: '血氧仪',
|
||||
type: '血氧仪',
|
||||
location: '四川省/绵阳市/涪城区',
|
||||
status: 'error',
|
||||
statusText: '离线'
|
||||
}
|
||||
])
|
||||
|
||||
// 查询表单引用
|
||||
const queryFormRef = ref()
|
||||
// 设备表单引用
|
||||
const deviceFormRef = ref()
|
||||
|
||||
// 查询方法
|
||||
const handleQuery = () => {
|
||||
// TODO: 实现查询逻辑
|
||||
console.log('查询参数:', queryParams)
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 打开表单
|
||||
const openForm = (type: string, id?: number) => {
|
||||
deviceFormRef.value?.open(type, id)
|
||||
}
|
||||
|
||||
// 处理设备操作
|
||||
const handleDeviceAction = (action: any) => {
|
||||
if (action.type === 'detail') {
|
||||
openForm('update', action.device.id)
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
// TODO: 实现获取设备列表数据
|
||||
// 这里可以添加实际的API调用
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-cards-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
@ -1,172 +0,0 @@
|
||||
<template>
|
||||
<el-card class="device-card" :body-style="{ padding: '20px' }">
|
||||
<div class="card-content">
|
||||
<div class="device-info">
|
||||
<h3 class="device-name">{{ deviceInfo.name }}</h3>
|
||||
<p class="device-status" :class="deviceInfo.status">
|
||||
{{ deviceInfo.statusText }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="device-details">
|
||||
<p class="detail-item">
|
||||
<span class="label">设备ID:</span>
|
||||
<span class="value">{{ deviceInfo.id }}</span>
|
||||
</p>
|
||||
<p class="detail-item">
|
||||
<span class="label">类型:</span>
|
||||
<span class="value">{{ deviceInfo.type }}</span>
|
||||
</p>
|
||||
<p class="detail-item">
|
||||
<span class="label">位置:</span>
|
||||
<span class="value">{{ deviceInfo.location }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="deviceInfo.status === 'normal'"
|
||||
@click="handleAction('enable')"
|
||||
>
|
||||
启用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
:disabled="deviceInfo.status === 'error'"
|
||||
@click="handleAction('disable')"
|
||||
>
|
||||
停用
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleAction('settings')"
|
||||
>
|
||||
设置
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
size="small"
|
||||
@click="handleAction('details')"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
deviceInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
location: '',
|
||||
status: 'normal',
|
||||
statusText: '正常'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['action'])
|
||||
|
||||
const handleAction = (action) => {
|
||||
emit('action', {
|
||||
action,
|
||||
deviceId: props.deviceInfo.id
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-card {
|
||||
width: 300px;
|
||||
margin: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.device-status.normal {
|
||||
background-color: #67C23A;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.device-status.warning {
|
||||
background-color: #E6A23C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.device-status.error {
|
||||
background-color: #F56C6C;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.device-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: #b7b7bb;
|
||||
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-buttons .el-button {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
@ -100,18 +100,6 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 设备卡片展示区域 -->
|
||||
<!-- <ContentWrap>
|
||||
<div class="device-cards-container">
|
||||
<device-card
|
||||
v-for="device in deviceList"
|
||||
:key="device.id"
|
||||
:device-info="device"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap> -->
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DeptForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
@ -122,7 +110,6 @@ import { handleTree } from '@/utils/tree'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import DeptForm from './DeptForm.vue'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import DeviceCard from '../cards/cards.vue'
|
||||
|
||||
defineOptions({ name: 'SystemDept' })
|
||||
|
||||
@ -142,34 +129,6 @@ const isExpandAll = ref(true) // 是否展开,默认全部展开
|
||||
const refreshTable = ref(true) // 重新渲染表格状态
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
// 测试设备数据
|
||||
// const deviceList = ref([
|
||||
// {
|
||||
// id: 'Blood001',
|
||||
// name: '血压仪',
|
||||
// type: '血压仪',
|
||||
// location: '天津市/西青区/精武镇',
|
||||
// status: 'normal',
|
||||
// statusText: '正常'
|
||||
// },
|
||||
// {
|
||||
// id: 'Glucose001',
|
||||
// name: '血糖仪',
|
||||
// type: '血糖仪',
|
||||
// location: '河北省/廊坊市/安次区',
|
||||
// status: 'warning',
|
||||
// statusText: '警告'
|
||||
// },
|
||||
// {
|
||||
// id: 'Oxygen001',
|
||||
// name: '血氧仪',
|
||||
// type: '血氧仪',
|
||||
// location: '四川省/绵阳市/涪城区',
|
||||
// status: 'error',
|
||||
// statusText: '故障'
|
||||
// }
|
||||
// ])
|
||||
|
||||
/** 查询机构列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
@ -228,12 +187,3 @@ onMounted(async () => {
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.device-cards-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,7 +1,4 @@
|
||||
<template>
|
||||
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
|
||||
<doc-alert title="菜单路由" url="https://doc.iocoder.cn/vue3/route/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
|
Loading…
Reference in New Issue
Block a user