iot-gateway_fontend/src/views/iot/deviceCard/components/device-edit.vue
Kven a45b8985b6 feat(iot): 新增设备管理卡片展示功能
- 添加设备列表页面,支持查询、重置功能
- 实现设备详情页面,展示设备信息和扩展属性
- 新增设备编辑组件,支持创建和修改设备
- 重构公告卡片组件,改为设备卡片样式
2025-01-29 13:33:40 +08:00

416 lines
10 KiB
Vue

<template>
<!-- <a-button v-permission="['iot:device:create']" v-if="props.isCreate" type="primary" @click="handleClick">-->
<!-- <template #icon>-->
<!-- <icon-plus />-->
<!-- </template>-->
<!-- 新建-->
<!-- </a-button>-->
<a-col
v-permission="['iot:device:create']"
v-if="props.isCreate"
@click="handleClick"
:xs="12"
:sm="12"
:md="12"
:lg="6"
:xl="6"
:xxl="6"
class="list-col"
>
<div class="card-wrap empty-wrap">
<a-card :bordered="false" hoverable>
<a-result :status="null" title="点击创建设备">
<template #icon>
<icon-plus style="font-size: 20px" />
</template>
</a-result>
</a-card>
</div>
</a-col>
<a-button
v-permission="['iot:device:update']"
v-if="!props.isCreate"
type="outline"
size="small"
@click="handleClick"
>
<!-- <template #icon><icon-edit /></template>-->
修改
</a-button>
<a-modal
width="900px"
height="500px"
:visible="visible"
@cancel="handleCancel"
>
<template #title>{{ modalTitle }}</template>
<a-form
ref="CreateRef"
:model="formData"
:style="{ width: '800px', height: '420px' }"
>
<!-- 产品id -->
<a-form-item
field="productName"
label="产品名称"
:rules="[{ required: true, message: '产品名称不能为空' }]"
:validate-trigger="['change']"
>
<a-select
v-model="formData.productName"
placeholder="请选择产品名称"
:loading="loading"
:filter-option="false"
allow-search
@search="handleSearch"
>
<a-option v-for="item of options" :key="item.id" :value="item.name">{{
item.name
}}</a-option>
</a-select>
</a-form-item>
<!-- 设备名称 -->
<a-form-item
field="name"
label="设备名称"
:rules="[{ required: true, message: '设备名称不能为空' }]"
:validate-trigger="['change']"
>
<a-input v-model="formData.name" placeholder="请输入设备名称" />
</a-form-item>
<!-- 硬件版本 -->
<a-form-item
field="hardwareVersion"
label="硬件版本"
:rules="[{ required: true, message: '硬件版本不能为空' }]"
:validate-trigger="['change']"
>
<a-input
v-model="formData.hardwareVersion"
placeholder="请输入硬件版本"
/>
</a-form-item>
<!-- 固件版本 -->
<a-form-item
field="firmwareVersion"
label="固件版本"
:rules="[{ required: true, message: '固件版本不能为空' }]"
:validate-trigger="['change']"
>
<a-input
v-model="formData.firmwareVersion"
placeholder="请输入固件版本"
/>
</a-form-item>
<a-form-item
field="remark"
label="备注"
>
<a-textarea
v-model="formData.remark"
placeholder="请输入描述"
/>
</a-form-item>
<!-- 扩展属性 -->
<a-form-item field="extendParams" label="扩展属性">
<!-- <a-input-->
<!-- v-model="formData.extendParams"-->
<!-- placeholder='请输入扩展属性'-->
<!-- />-->
<div style="width: 100%">
<div style="width: 100%; margin-bottom: 5px">
<a-space
v-for="(param, index) in paramsData"
:key="index"
style="margin-bottom: 5px"
>
<a-input v-model="param.name" placeholder="名称" allow-clear />
<a-input
v-model="param.identifier"
placeholder="标识"
allow-clear
/>
<a-select
v-model="param.dataType"
:options="dataTypeOptions"
allow-search
placeholder="数据类型"
style="width: 140px"
/>
<a-select
v-model="param.type"
:options="typeOptions"
allow-search
placeholder="类型"
style="width: 140px"
/>
<a-button type="text" @click="handleDeleteParams(index)"
><icon-minus-circle
/></a-button>
</a-space>
</div>
<a-button type="outline" @click="handleAddParams" style="width: 100%">
<icon-plus />
</a-button>
</div>
</a-form-item>
</a-form>
<template #footer>
<a-button class="editor-button" @click="handleCancel">取消</a-button>
<a-button class="editor-button" type="primary" @click="handleSubmit"
>确定</a-button
>
</template>
</a-modal>
</template>
<script lang="ts" setup>
import useVisible from '@/hooks/visible';
import {
computed,
defineEmits,
PropType,
ref,
shallowRef,
onBeforeUnmount,
} from 'vue';
import { CreateRecord } from '@/api/user';
import { FormInstance } from '@arco-design/web-vue/es/form';
import { Message } from '@arco-design/web-vue';
import '@wangeditor/editor/dist/css/style.css';
import { createDevice, queryDeviceByName, queryDeviceDetail, updateDevice } from '@/api/device';
import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface';
import { queryProductDetail } from '@/api/product';
const props = defineProps({
id: {
type: Number,
default: -1,
},
isCreate: Boolean,
});
const paramsData = ref([
{
name: '',
identifier: '',
dataType: '',
type: '',
},
]);
const dataTypeOptions = computed<SelectOptionData[]>(() => [
{
label: '整型',
value: 'INT',
},
{
label: '单精度浮点型',
value: 'FLOAT',
},
{
label: '双精度浮点型',
value: 'DOUBLE',
},
{
label: '布尔型',
value: 'BOOLEAN',
},
{
label: '字符串',
value: 'STRING',
},
{
label: '日期型',
value: 'DATE',
},
{
label: '透传',
value: 'RAW',
},
]);
const typeOptions = computed<SelectOptionData[]>(() => [
{
label: '输入',
value: 'INPUT',
},
{
label: '输出',
value: 'OUTPUT',
},
{
label: '读写属性',
value: 'RW',
},
]);
const emit = defineEmits(['refresh']);
const modalTitle = computed(() => {
return props.isCreate ? '创建设备' : '编辑设备';
});
const { visible, setVisible } = useVisible(false);
const checkKeys = ref<number[]>([]);
const CreateRef = ref<FormInstance>();
const formData = ref<any>({});
const editorRef = shallowRef();
const options = ref();
const loading = ref(false);
// 搜索
const handleSearch = (value: any) => {
if (value) {
loading.value = true;
options.value = [];
window.setTimeout(async () => {
const res = await queryDeviceByName({
name: value,
page:1,
size: 10,
});
options.value = res.data.records.map((item: any) => {
return {
id: item.id,
name: item.name,
};
});
loading.value = false;
}, 1000);
} else {
options.value = [];
}
};
// 点击添加参数
const handleAddParams = () => {
// paramsVisible.value = true;
paramsData.value.push({
name: '',
identifier: '',
dataType: '',
type: '',
});
};
// 删除参数
const handleDeleteParams = (record: number) => {
if (record !== -1) {
paramsData.value.splice(record, 1);
}
};
// 组件被点击
const handleClick = async () => {
setVisible(true);
if (!props.isCreate) {
queryDeviceDetail(props.id).then((res) => {
formData.value = res.data;
paramsData.value = res.data.extendParams;
console.log('formData:', formData.value);
const ID = formData.value.productId;
return queryProductDetail(ID);
}).then((res) => {
formData.value.productName = res.data.name;
}).catch((error) => {
console.error('Error fetching data:', error);
});
}
};
// 提交
const handleSubmit = async () => {
const valid = await CreateRef.value?.validate();
// formData.value.productId = formData.value.productId.id;
if (!valid) {
// 新增
formData.value.extendParams = paramsData.value;
const productId = await queryDeviceByName(formData.value.productName);
formData.value.productId = productId.data.records[0].id;
if (props.isCreate) {
// formData.value.username = formData.value.email;
const res = await createDevice(formData.value);
if (res.status === 200) {
Message.success({
content: '新建成功',
duration: 5 * 1000,
});
emit('refresh');
setVisible(false);
}
CreateRef.value?.resetFields();
} else {
// 编辑
const res = await updateDevice(formData.value);
if (res.status === 200) {
Message.success({
content: '修改成功',
duration: 5 * 1000,
});
emit('refresh');
setVisible(false);
}
}
}
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
// 关闭
const handleCancel = async () => {
checkKeys.value = [];
setVisible(false);
};
</script>
<style scoped lang="less">
.editor-button {
position: static;
}
.card-wrap {
height: 100%;
transition: all 0.3s;
border: 1px solid var(--color-neutral-3);
&:hover {
transform: translateY(-4px);
}
:deep(.arco-card-meta-description) {
color: rgb(var(--gray-6));
.arco-descriptions-item-label-inline {
font-weight: normal;
font-size: 12px;
color: rgb(var(--gray-6));
}
.arco-descriptions-item-value-inline {
color: rgb(var(--gray-8));
}
}
}
.empty-wrap {
height: 210px;
border-radius: 4px;
:deep(.arco-card) {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
.arco-result-title {
color: rgb(var(--gray-6));
}
}
}
:deep(.list-wrap) {
.list-row {
align-items: stretch;
.list-col {
margin-bottom: 16px;
}
}
}
</style>