|
|
@@ -256,14 +256,21 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, watch, nextTick, computed } from 'vue';
|
|
|
+import { ref, reactive, watch, nextTick, computed } from 'vue'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
-import {
|
|
|
const { t } = useI18n()
|
|
|
- Search, FolderOpened, Monitor, Plus, Check,
|
|
|
- Edit, Delete, InfoFilled, Clock
|
|
|
-} from '@element-plus/icons-vue';
|
|
|
-import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
|
|
|
+import {
|
|
|
+ Search,
|
|
|
+ FolderOpened,
|
|
|
+ Monitor,
|
|
|
+ Plus,
|
|
|
+ Check,
|
|
|
+ Edit,
|
|
|
+ Delete,
|
|
|
+ InfoFilled,
|
|
|
+ Clock
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
+import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
|
|
|
|
|
// 属性卡片组件(使用slot)
|
|
|
const PropertyCard = {
|
|
|
@@ -392,42 +399,42 @@ const PropertyCard = {
|
|
|
// 获取数据类型显示名称
|
|
|
getTypeName(type) {
|
|
|
const types = {
|
|
|
- 'integer': '整数',
|
|
|
- 'float': '浮点数',
|
|
|
- 'boolean': '布尔值'
|
|
|
- };
|
|
|
- return types[type] || type;
|
|
|
+ integer: '整数',
|
|
|
+ float: '浮点数',
|
|
|
+ boolean: '布尔值'
|
|
|
+ }
|
|
|
+ return types[type] || type
|
|
|
},
|
|
|
// 获取数据类型标签样式
|
|
|
getTypeTagType(type) {
|
|
|
const types = {
|
|
|
- 'integer': 'primary',
|
|
|
- 'float': 'success',
|
|
|
- 'boolean': 'warning'
|
|
|
- };
|
|
|
- return types[type] || 'info';
|
|
|
+ integer: 'primary',
|
|
|
+ float: 'success',
|
|
|
+ boolean: 'warning'
|
|
|
+ }
|
|
|
+ return types[type] || 'info'
|
|
|
},
|
|
|
// 处理上下限变化
|
|
|
handleLimitChange() {
|
|
|
- this.$emit('update-property', { ...this.property });
|
|
|
+ this.$emit('update-property', { ...this.property })
|
|
|
},
|
|
|
// 处理布尔值变化
|
|
|
handleBooleanChange() {
|
|
|
- this.$emit('update-property', { ...this.property });
|
|
|
+ this.$emit('update-property', { ...this.property })
|
|
|
},
|
|
|
// 格式化日期
|
|
|
formatDate(timestamp) {
|
|
|
- if (!timestamp) return '未更新';
|
|
|
- const date = new Date(timestamp);
|
|
|
- return date.toLocaleString();
|
|
|
+ if (!timestamp) return '未更新'
|
|
|
+ const date = new Date(timestamp)
|
|
|
+ return date.toLocaleString()
|
|
|
}
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 生成唯一ID
|
|
|
const generateId = () => {
|
|
|
- return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
|
|
|
-};
|
|
|
+ return Date.now().toString(36) + Math.random().toString(36).substr(2, 8)
|
|
|
+}
|
|
|
|
|
|
// 设备树形结构数据
|
|
|
const deviceTreeData = ref([
|
|
|
@@ -471,24 +478,24 @@ const deviceTreeData = ref([
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
-]);
|
|
|
+])
|
|
|
|
|
|
// 树形结构配置
|
|
|
const treeProps = {
|
|
|
children: 'children',
|
|
|
label: 'label'
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 状态管理
|
|
|
-const searchQuery = ref('');
|
|
|
-const deviceTree = ref(null);
|
|
|
-const selectedNode = ref(null);
|
|
|
-const properties = ref([]);
|
|
|
-const saveLoading = ref(false);
|
|
|
-const dialogLoading = ref(false);
|
|
|
-const propertyDialogVisible = ref(false);
|
|
|
-const isEditing = ref(false);
|
|
|
-const currentEditIndex = ref(-1);
|
|
|
+const searchQuery = ref('')
|
|
|
+const deviceTree = ref(null)
|
|
|
+const selectedNode = ref(null)
|
|
|
+const properties = ref([])
|
|
|
+const saveLoading = ref(false)
|
|
|
+const dialogLoading = ref(false)
|
|
|
+const propertyDialogVisible = ref(false)
|
|
|
+const isEditing = ref(false)
|
|
|
+const currentEditIndex = ref(-1)
|
|
|
|
|
|
// 属性表单数据
|
|
|
const currentProperty = reactive({
|
|
|
@@ -503,7 +510,7 @@ const currentProperty = reactive({
|
|
|
description: '',
|
|
|
required: false,
|
|
|
updatedAt: null
|
|
|
-});
|
|
|
+})
|
|
|
|
|
|
// 属性表单验证规则
|
|
|
const propertyRules = {
|
|
|
@@ -516,9 +523,7 @@ const propertyRules = {
|
|
|
{ pattern: /^[a-zA-Z0-9_]+$/, message: '标识只能包含字母、数字和下划线', trigger: 'blur' },
|
|
|
{ max: 30, message: '属性标识不能超过30个字符', trigger: 'blur' }
|
|
|
],
|
|
|
- dataType: [
|
|
|
- { required: true, message: '请选择数据类型', trigger: 'change' }
|
|
|
- ],
|
|
|
+ dataType: [{ required: true, message: '请选择数据类型', trigger: 'change' }],
|
|
|
minValue: [
|
|
|
{
|
|
|
required: () => currentProperty.dataType !== 'boolean',
|
|
|
@@ -531,15 +536,15 @@ const propertyRules = {
|
|
|
trigger: 'blur',
|
|
|
validator: (rule, value, callback) => {
|
|
|
if (currentProperty.dataType === 'boolean') {
|
|
|
- return callback();
|
|
|
+ return callback()
|
|
|
}
|
|
|
if (value === null || value === undefined) {
|
|
|
- return callback(new Error('请输入最小值'));
|
|
|
+ return callback(new Error('请输入最小值'))
|
|
|
}
|
|
|
if (currentProperty.maxValue !== null && value > currentProperty.maxValue) {
|
|
|
- return callback(new Error('最小值不能大于最大值'));
|
|
|
+ return callback(new Error('最小值不能大于最大值'))
|
|
|
}
|
|
|
- callback();
|
|
|
+ callback()
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
@@ -555,30 +560,30 @@ const propertyRules = {
|
|
|
trigger: 'blur',
|
|
|
validator: (rule, value, callback) => {
|
|
|
if (currentProperty.dataType === 'boolean') {
|
|
|
- return callback();
|
|
|
+ return callback()
|
|
|
}
|
|
|
if (value === null || value === undefined) {
|
|
|
- return callback(new Error('请输入最大值'));
|
|
|
+ return callback(new Error('请输入最大值'))
|
|
|
}
|
|
|
if (currentProperty.minValue !== null && value < currentProperty.minValue) {
|
|
|
- return callback(new Error('最大值不能小于最小值'));
|
|
|
+ return callback(new Error('最大值不能小于最小值'))
|
|
|
}
|
|
|
- callback();
|
|
|
+ callback()
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 过滤节点方法
|
|
|
const filterNode = (value, data) => {
|
|
|
- if (!value) return true;
|
|
|
- return data.label.toLowerCase().includes(value.toLowerCase());
|
|
|
-};
|
|
|
+ if (!value) return true
|
|
|
+ return data.label.toLowerCase().includes(value.toLowerCase())
|
|
|
+}
|
|
|
|
|
|
// 监听搜索关键词变化
|
|
|
watch(searchQuery, (value) => {
|
|
|
- deviceTree.value?.filter(value);
|
|
|
-});
|
|
|
+ deviceTree.value?.filter(value)
|
|
|
+})
|
|
|
|
|
|
// 处理节点选择
|
|
|
const handleNodeSelect = (data) => {
|
|
|
@@ -592,28 +597,31 @@ const handleNodeSelect = (data) => {
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning'
|
|
|
}
|
|
|
- ).then(() => {
|
|
|
- loadNodeProperties(data);
|
|
|
- }).catch(() => {
|
|
|
- // 取消切换,恢复之前的选择
|
|
|
- nextTick(() => {
|
|
|
- deviceTree.value.setCurrentKey(selectedNode.value?.id);
|
|
|
- });
|
|
|
- });
|
|
|
+ )
|
|
|
+ .then(() => {
|
|
|
+ loadNodeProperties(data)
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 取消切换,恢复之前的选择
|
|
|
+ nextTick(() => {
|
|
|
+ deviceTree.value.setCurrentKey(selectedNode.value?.id)
|
|
|
+ })
|
|
|
+ })
|
|
|
} else {
|
|
|
- loadNodeProperties(data);
|
|
|
+ loadNodeProperties(data)
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 加载节点属性
|
|
|
const loadNodeProperties = (data) => {
|
|
|
- selectedNode.value = data;
|
|
|
+ selectedNode.value = data
|
|
|
// 模拟API加载属性数据
|
|
|
- saveLoading.value = true;
|
|
|
+ saveLoading.value = true
|
|
|
|
|
|
setTimeout(() => {
|
|
|
// 根据不同节点加载不同的属性示例数据
|
|
|
- if (data.id === 'dev11') { // 室内温度计
|
|
|
+ if (data.id === 'dev11') {
|
|
|
+ // 室内温度计
|
|
|
properties.value = [
|
|
|
{
|
|
|
id: generateId(),
|
|
|
@@ -650,8 +658,9 @@ const loadNodeProperties = (data) => {
|
|
|
required: true,
|
|
|
updatedAt: Date.now()
|
|
|
}
|
|
|
- ];
|
|
|
- } else if (data.id === 'cat1') { // 温度设备分类
|
|
|
+ ]
|
|
|
+ } else if (data.id === 'cat1') {
|
|
|
+ // 温度设备分类
|
|
|
properties.value = [
|
|
|
{
|
|
|
id: generateId(),
|
|
|
@@ -677,165 +686,169 @@ const loadNodeProperties = (data) => {
|
|
|
description: '设备存储的环境温度范围',
|
|
|
updatedAt: Date.now() - 86400000
|
|
|
}
|
|
|
- ];
|
|
|
+ ]
|
|
|
} else {
|
|
|
// 其他节点的默认属性
|
|
|
- properties.value = [];
|
|
|
+ properties.value = []
|
|
|
}
|
|
|
|
|
|
- saveLoading.value = false;
|
|
|
+ saveLoading.value = false
|
|
|
ElNotification({
|
|
|
title: '已加载',
|
|
|
message: `已加载 ${data.label} 的 ${properties.value.length} 个属性`,
|
|
|
duration: 1500,
|
|
|
position: 'bottom-right'
|
|
|
- });
|
|
|
- }, 600);
|
|
|
-};
|
|
|
+ })
|
|
|
+ }, 600)
|
|
|
+}
|
|
|
|
|
|
// 检查是否有未保存的更改
|
|
|
-const hasUnsavedChanges = ref(false);
|
|
|
+const hasUnsavedChanges = ref(false)
|
|
|
|
|
|
// 监听属性变化
|
|
|
-watch(properties, (newVal) => {
|
|
|
- hasUnsavedChanges.value = true;
|
|
|
-}, { deep: true });
|
|
|
+watch(
|
|
|
+ properties,
|
|
|
+ (newVal) => {
|
|
|
+ hasUnsavedChanges.value = true
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+)
|
|
|
|
|
|
// 显示添加属性对话框
|
|
|
const showAddPropertyDialog = () => {
|
|
|
- resetPropertyForm();
|
|
|
- isEditing.value = false;
|
|
|
- currentProperty.id = generateId();
|
|
|
- propertyDialogVisible.value = true;
|
|
|
+ resetPropertyForm()
|
|
|
+ isEditing.value = false
|
|
|
+ currentProperty.id = generateId()
|
|
|
+ propertyDialogVisible.value = true
|
|
|
|
|
|
// 自动聚焦到第一个输入框
|
|
|
nextTick(() => {
|
|
|
- const firstInput = document.querySelector('.property-dialog .el-input__inner');
|
|
|
- if (firstInput) firstInput.focus();
|
|
|
- });
|
|
|
-};
|
|
|
+ const firstInput = document.querySelector('.property-dialog .el-input__inner')
|
|
|
+ if (firstInput) firstInput.focus()
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 显示编辑属性对话框
|
|
|
const showEditPropertyDialog = (index, property) => {
|
|
|
- resetPropertyForm();
|
|
|
- isEditing.value = true;
|
|
|
- currentEditIndex.value = index;
|
|
|
+ resetPropertyForm()
|
|
|
+ isEditing.value = true
|
|
|
+ currentEditIndex.value = index
|
|
|
|
|
|
// 复制属性数据到表单
|
|
|
- Object.assign(currentProperty, { ...property });
|
|
|
+ Object.assign(currentProperty, { ...property })
|
|
|
|
|
|
- propertyDialogVisible.value = true;
|
|
|
+ propertyDialogVisible.value = true
|
|
|
nextTick(() => {
|
|
|
- const firstInput = document.querySelector('.property-dialog .el-input__inner');
|
|
|
- if (firstInput) firstInput.focus();
|
|
|
- });
|
|
|
-};
|
|
|
+ const firstInput = document.querySelector('.property-dialog .el-input__inner')
|
|
|
+ if (firstInput) firstInput.focus()
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 重置属性表单
|
|
|
const resetPropertyForm = () => {
|
|
|
- currentProperty.name = '';
|
|
|
- currentProperty.code = '';
|
|
|
- currentProperty.dataType = 'float';
|
|
|
- currentProperty.unit = '';
|
|
|
- currentProperty.minValue = null;
|
|
|
- currentProperty.maxValue = null;
|
|
|
- currentProperty.booleanValue = false;
|
|
|
- currentProperty.description = '';
|
|
|
- currentProperty.required = false;
|
|
|
+ currentProperty.name = ''
|
|
|
+ currentProperty.code = ''
|
|
|
+ currentProperty.dataType = 'float'
|
|
|
+ currentProperty.unit = ''
|
|
|
+ currentProperty.minValue = null
|
|
|
+ currentProperty.maxValue = null
|
|
|
+ currentProperty.booleanValue = false
|
|
|
+ currentProperty.description = ''
|
|
|
+ currentProperty.required = false
|
|
|
|
|
|
// 重置表单验证
|
|
|
nextTick(() => {
|
|
|
- propertyForm.value?.clearValidate();
|
|
|
- });
|
|
|
-};
|
|
|
+ propertyForm.value?.clearValidate()
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 处理数据类型变化
|
|
|
const handleDataTypeChange = (type) => {
|
|
|
// 根据数据类型重置相关值
|
|
|
if (type === 'boolean') {
|
|
|
- currentProperty.minValue = null;
|
|
|
- currentProperty.maxValue = null;
|
|
|
+ currentProperty.minValue = null
|
|
|
+ currentProperty.maxValue = null
|
|
|
} else if (type === 'integer') {
|
|
|
- currentProperty.minValue = currentProperty.minValue !== null ? Math.round(currentProperty.minValue) : 0;
|
|
|
- currentProperty.maxValue = currentProperty.maxValue !== null ? Math.round(currentProperty.maxValue) : 100;
|
|
|
+ currentProperty.minValue =
|
|
|
+ currentProperty.minValue !== null ? Math.round(currentProperty.minValue) : 0
|
|
|
+ currentProperty.maxValue =
|
|
|
+ currentProperty.maxValue !== null ? Math.round(currentProperty.maxValue) : 100
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 确认属性操作(添加或编辑)
|
|
|
-const propertyForm = ref(null);
|
|
|
+const propertyForm = ref(null)
|
|
|
const confirmPropertyAction = () => {
|
|
|
propertyForm.value.validate((valid) => {
|
|
|
if (valid) {
|
|
|
- dialogLoading.value = true;
|
|
|
+ dialogLoading.value = true
|
|
|
|
|
|
// 模拟API请求延迟
|
|
|
setTimeout(() => {
|
|
|
- const newProperty = { ...currentProperty };
|
|
|
- newProperty.updatedAt = Date.now();
|
|
|
+ const newProperty = { ...currentProperty }
|
|
|
+ newProperty.updatedAt = Date.now()
|
|
|
|
|
|
if (isEditing.value) {
|
|
|
// 编辑现有属性
|
|
|
- properties.value.splice(currentEditIndex.value, 1, newProperty);
|
|
|
- ElMessage.success('属性已更新');
|
|
|
+ properties.value.splice(currentEditIndex.value, 1, newProperty)
|
|
|
+ ElMessage.success('属性已更新')
|
|
|
} else {
|
|
|
// 添加新属性
|
|
|
- properties.value.push(newProperty);
|
|
|
+ properties.value.push(newProperty)
|
|
|
// 更新节点的属性计数
|
|
|
if (selectedNode.value) {
|
|
|
- selectedNode.value.propertyCount = (selectedNode.value.propertyCount || 0) + 1;
|
|
|
+ selectedNode.value.propertyCount = (selectedNode.value.propertyCount || 0) + 1
|
|
|
}
|
|
|
- ElMessage.success('新属性已添加');
|
|
|
+ ElMessage.success('新属性已添加')
|
|
|
}
|
|
|
|
|
|
- dialogLoading.value = false;
|
|
|
- propertyDialogVisible.value = false;
|
|
|
- }, 500);
|
|
|
+ dialogLoading.value = false
|
|
|
+ propertyDialogVisible.value = false
|
|
|
+ }, 500)
|
|
|
}
|
|
|
- });
|
|
|
-};
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 处理属性更新
|
|
|
const handlePropertyUpdate = (index, updatedProperty) => {
|
|
|
- updatedProperty.updatedAt = Date.now();
|
|
|
- properties.value.splice(index, 1, updatedProperty);
|
|
|
-};
|
|
|
+ updatedProperty.updatedAt = Date.now()
|
|
|
+ properties.value.splice(index, 1, updatedProperty)
|
|
|
+}
|
|
|
|
|
|
// 处理属性删除
|
|
|
const handlePropertyDelete = (index) => {
|
|
|
- const property = properties.value[index];
|
|
|
- ElMessageBox.confirm(
|
|
|
- `确定要删除属性"${property.name}"吗?此操作不可撤销。`,
|
|
|
- '确认删除',
|
|
|
- {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'danger'
|
|
|
- }
|
|
|
- ).then(() => {
|
|
|
- properties.value.splice(index, 1);
|
|
|
- // 更新节点的属性计数
|
|
|
- if (selectedNode.value) {
|
|
|
- selectedNode.value.propertyCount = Math.max(0, (selectedNode.value.propertyCount || 0) - 1);
|
|
|
- }
|
|
|
- ElMessage.success('属性已删除');
|
|
|
- }).catch(() => {
|
|
|
- // 取消删除
|
|
|
- });
|
|
|
-};
|
|
|
+ const property = properties.value[index]
|
|
|
+ ElMessageBox.confirm(`确定要删除属性"${property.name}"吗?此操作不可撤销。`, '确认删除', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'danger'
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ properties.value.splice(index, 1)
|
|
|
+ // 更新节点的属性计数
|
|
|
+ if (selectedNode.value) {
|
|
|
+ selectedNode.value.propertyCount = Math.max(0, (selectedNode.value.propertyCount || 0) - 1)
|
|
|
+ }
|
|
|
+ ElMessage.success('属性已删除')
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 取消删除
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// 保存所有属性配置
|
|
|
const saveAllProperties = () => {
|
|
|
if (properties.value.length === 0) {
|
|
|
- ElMessage.warning('没有可保存的属性');
|
|
|
- return;
|
|
|
+ ElMessage.warning('没有可保存的属性')
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- saveLoading.value = true;
|
|
|
+ saveLoading.value = true
|
|
|
|
|
|
// 模拟API保存
|
|
|
setTimeout(() => {
|
|
|
- saveLoading.value = false;
|
|
|
- hasUnsavedChanges.value = false;
|
|
|
+ saveLoading.value = false
|
|
|
+ hasUnsavedChanges.value = false
|
|
|
|
|
|
ElNotification({
|
|
|
title: '保存成功',
|
|
|
@@ -843,44 +856,44 @@ const saveAllProperties = () => {
|
|
|
type: 'success',
|
|
|
duration: 2000,
|
|
|
position: 'bottom-right'
|
|
|
- });
|
|
|
- }, 800);
|
|
|
-};
|
|
|
+ })
|
|
|
+ }, 800)
|
|
|
+}
|
|
|
|
|
|
// 返回操作
|
|
|
const handleBack = () => {
|
|
|
if (hasUnsavedChanges.value) {
|
|
|
- ElMessageBox.confirm(
|
|
|
- '当前有未保存的修改,离开页面将丢失这些更改,是否继续?',
|
|
|
- '确认离开',
|
|
|
- {
|
|
|
- confirmButtonText: '离开',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }
|
|
|
- ).then(() => {
|
|
|
+ ElMessageBox.confirm('当前有未保存的修改,离开页面将丢失这些更改,是否继续?', '确认离开', {
|
|
|
+ confirmButtonText: '离开',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
// 实际应用中这里会执行路由跳转
|
|
|
- ElMessage.info('已返回上一页');
|
|
|
- });
|
|
|
+ ElMessage.info('已返回上一页')
|
|
|
+ })
|
|
|
} else {
|
|
|
// 实际应用中这里会执行路由跳转
|
|
|
- ElMessage.info('已返回上一页');
|
|
|
+ ElMessage.info('已返回上一页')
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// 页面加载时默认选中第一个设备
|
|
|
-watch(() => deviceTreeData.value.length, (length) => {
|
|
|
- if (length > 0 && !selectedNode.value) {
|
|
|
- // 尝试选择第一个设备节点
|
|
|
- const firstDevice = deviceTreeData.value[0]?.children?.[0];
|
|
|
- if (firstDevice) {
|
|
|
- nextTick(() => {
|
|
|
- handleNodeSelect(firstDevice);
|
|
|
- deviceTree.value.setCurrentKey(firstDevice.id);
|
|
|
- });
|
|
|
+watch(
|
|
|
+ () => deviceTreeData.value.length,
|
|
|
+ (length) => {
|
|
|
+ if (length > 0 && !selectedNode.value) {
|
|
|
+ // 尝试选择第一个设备节点
|
|
|
+ const firstDevice = deviceTreeData.value[0]?.children?.[0]
|
|
|
+ if (firstDevice) {
|
|
|
+ nextTick(() => {
|
|
|
+ handleNodeSelect(firstDevice)
|
|
|
+ deviceTree.value.setCurrentKey(firstDevice.id)
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-}, { immediate: true });
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|