1494 lines
40 KiB
Vue
1494 lines
40 KiB
Vue
<template>
|
||
<div class="devinfo-page">
|
||
<el-breadcrumb separator="/" style="margin-bottom: 16px">
|
||
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
|
||
<el-breadcrumb-item>业务管理</el-breadcrumb-item>
|
||
<el-breadcrumb-item>设备管理</el-breadcrumb-item>
|
||
</el-breadcrumb>
|
||
<!-- 搜索栏 -->
|
||
<el-card class="search-card" shadow="never">
|
||
<div class="search-bar">
|
||
<el-form :inline="true" :model="queryForm" label-width="100px">
|
||
<el-form-item label="设备名称">
|
||
<el-input
|
||
v-model="queryForm.devName"
|
||
placeholder="输入设备名称"
|
||
clearable
|
||
style="width: 200px"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="设备状态">
|
||
<el-select
|
||
v-model="queryForm.status"
|
||
placeholder="请选择设备状态"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option label="空闲" :value="0" />
|
||
<el-option label="运行" :value="1" />
|
||
<el-option label="故障" :value="4" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="所属功能岛">
|
||
<el-select
|
||
v-model="queryForm.islandId"
|
||
placeholder="请选择功能岛"
|
||
clearable
|
||
style="width: 200px"
|
||
>
|
||
<el-option
|
||
v-for="island in islandList"
|
||
:key="island.id"
|
||
:label="island.islandName || island.name"
|
||
:value="island.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
<el-button @click="resetSearch">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div class="toolbar">
|
||
<el-button type="primary" @click="handleAdd">
|
||
<el-icon><Plus /></el-icon>
|
||
新增设备
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 设备列表 -->
|
||
<el-card shadow="never">
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="deviceList"
|
||
border
|
||
stripe
|
||
style="width: 100%"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" :index="getIndex" />
|
||
<el-table-column prop="devName" label="设备名称" min-width="120" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.devName || '暂无' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="devModel" label="设备型号" min-width="120" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.devModel || '暂无' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getStatusType(row.status)" size="small">
|
||
{{ getStatusText(row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="islandId" label="所属功能岛" width="150">
|
||
<template #default="{ row }">
|
||
{{ getIslandName(row.islandId) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="devDesc" label="描述" min-width="150" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.devDesc || '暂无' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
{{ row.remark || '暂无' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="220" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
type="success"
|
||
size="small"
|
||
link
|
||
@click="handleConfigParams(row)"
|
||
>
|
||
<el-icon><Setting /></el-icon>
|
||
配置参数
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="handleEdit(row)"
|
||
>
|
||
<el-icon><Edit /></el-icon>
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
link
|
||
@click="handleDelete(row)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination">
|
||
<el-pagination
|
||
v-model:current-page="pagination.pageNum"
|
||
v-model:page-size="pagination.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="total"
|
||
background
|
||
@current-change="handleCurrentChange"
|
||
@size-change="handleSizeChange"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 新增/编辑抽屉 -->
|
||
<el-drawer
|
||
v-model="drawerVisible"
|
||
:title="drawerTitle"
|
||
direction="rtl"
|
||
size="500px"
|
||
:before-close="handleDrawerClose"
|
||
>
|
||
<el-form
|
||
ref="formRef"
|
||
:model="formData"
|
||
:rules="formRules"
|
||
label-width="100px"
|
||
label-position="left"
|
||
>
|
||
<el-form-item label="设备名称" prop="devName">
|
||
<el-input
|
||
v-model="formData.devName"
|
||
placeholder="请输入设备名称"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="设备型号" prop="devModel">
|
||
<el-input
|
||
v-model="formData.devModel"
|
||
placeholder="请输入设备型号"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="描述" prop="devDesc">
|
||
<el-input
|
||
v-model="formData.devDesc"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入描述"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input
|
||
v-model="formData.remark"
|
||
type="textarea"
|
||
:rows="3"
|
||
placeholder="请输入备注"
|
||
clearable
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="drawer-footer">
|
||
<el-button @click="handleDrawerClose">取消</el-button>
|
||
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
||
确定
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-drawer>
|
||
|
||
<!-- 下拉框选项配置对话框 -->
|
||
<el-dialog
|
||
v-model="selectOptionsDialogVisible"
|
||
:title="`[${currentSelectParam?.paramName || ''}]下拉框选项列表配置`"
|
||
width="600px"
|
||
:before-close="handleSelectOptionsDialogClose"
|
||
>
|
||
<div class="select-options-container">
|
||
<!-- 输入区域 -->
|
||
<div class="select-options-input-area">
|
||
<span class="input-label">选项数据:</span>
|
||
<el-input
|
||
v-model="selectOptionInput"
|
||
placeholder="请输入选项数据"
|
||
clearable
|
||
style="flex: 1; margin-right: 8px"
|
||
@keyup.enter="handleAddSelectOption"
|
||
/>
|
||
<el-button type="primary" @click="handleAddSelectOption">添加</el-button>
|
||
</div>
|
||
|
||
<!-- 选项卡片列表 -->
|
||
<div class="select-options-cards">
|
||
<div
|
||
v-for="option in selectOptionsList"
|
||
:key="option.id"
|
||
class="select-option-card"
|
||
>
|
||
<span class="option-text">{{ option.dicValue }}</span>
|
||
<el-icon
|
||
class="option-delete-icon"
|
||
@click="handleDeleteSelectOption(option)"
|
||
>
|
||
<Close />
|
||
</el-icon>
|
||
</div>
|
||
<div v-if="selectOptionsList.length === 0" class="empty-tip">
|
||
暂无选项数据,请添加
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="handleSelectOptionsDialogClose">取消</el-button>
|
||
<el-button type="primary" @click="handleSaveSelectOptions">保存</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 参数配置对话框 -->
|
||
<el-dialog
|
||
v-model="paramDialogVisible"
|
||
:title="`设备参数配置 - ${currentDevice?.devName || ''}`"
|
||
width="1200px"
|
||
:before-close="handleParamDialogClose"
|
||
>
|
||
<div class="param-config-container">
|
||
<!-- 工具栏 -->
|
||
<div class="param-toolbar">
|
||
<el-button type="primary" @click="handleAddParamRow">
|
||
<el-icon><Plus /></el-icon>
|
||
添加一行
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 参数列表 -->
|
||
<el-table
|
||
v-loading="paramLoading"
|
||
:data="paramList"
|
||
border
|
||
stripe
|
||
style="width: 100%"
|
||
:empty-text="paramEmptyText"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" :index="getParamIndex" />
|
||
|
||
<!-- 参数名称 -->
|
||
<el-table-column label="参数名称" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="row.isEditing"
|
||
v-model="row.paramName"
|
||
placeholder="请输入参数名称"
|
||
size="small"
|
||
/>
|
||
<span v-else>{{ row.paramName || '暂无' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 参数类型 -->
|
||
<el-table-column label="参数类型" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="row.isEditing"
|
||
v-model="row.paramType"
|
||
placeholder="请选择参数类型"
|
||
size="small"
|
||
style="width: 100%"
|
||
>
|
||
<el-option
|
||
v-for="type in paramTypeOptions"
|
||
:key="type.value"
|
||
:label="type.label"
|
||
:value="type.value"
|
||
/>
|
||
</el-select>
|
||
<span v-else>{{ row.paramType || '暂无' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 参数单位 -->
|
||
<el-table-column label="参数单位" min-width="100">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="row.isEditing"
|
||
v-model="row.paramUnit"
|
||
placeholder="请输入参数单位"
|
||
size="small"
|
||
/>
|
||
<span v-else>{{ row.paramUnit || '暂无' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 表单控件 -->
|
||
<el-table-column label="表单控件" min-width="120" align="left">
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="row.isEditing"
|
||
v-model="row.formType"
|
||
placeholder="请选择表单控件"
|
||
size="small"
|
||
style="width: 100%"
|
||
>
|
||
<el-option
|
||
v-for="form in formTypeOptions"
|
||
:key="form.value"
|
||
:label="form.label"
|
||
:value="form.value"
|
||
/>
|
||
</el-select>
|
||
<el-button
|
||
v-else-if="row.formType === 'select'"
|
||
type="success"
|
||
size="small"
|
||
link
|
||
class="form-type-select-btn"
|
||
@click="handleConfigSelectOptions(row)"
|
||
>
|
||
下拉框
|
||
</el-button>
|
||
<span v-else>{{ getFormTypeLabel(row.formType) || '暂无' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<!-- 操作列 -->
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
v-if="row.isEditing"
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="handleSaveParam(row)"
|
||
>
|
||
<el-icon><Check /></el-icon>
|
||
保存
|
||
</el-button>
|
||
<el-button
|
||
v-else
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="handleEditParam(row)"
|
||
>
|
||
<el-icon><Edit /></el-icon>
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
link
|
||
@click="handleDeleteParam(row)"
|
||
>
|
||
<el-icon><Delete /></el-icon>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="param-pagination">
|
||
<el-pagination
|
||
v-model:current-page="paramPagination.pageNum"
|
||
v-model:page-size="paramPagination.pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="paramTotal"
|
||
background
|
||
@current-change="handleParamCurrentChange"
|
||
@size-change="handleParamSizeChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus, Edit, Delete, Setting, Check, Close } from '@element-plus/icons-vue'
|
||
import {
|
||
devInfoadd,
|
||
devInfodel,
|
||
devInfoupd,
|
||
devInfolist,
|
||
devInfobyid,
|
||
} from '@/api/tb/devinfo'
|
||
import { islandInfobyid, islandInfolist } from '@/api/tb/islandinfo'
|
||
import {
|
||
devparamadd,
|
||
devparamdel,
|
||
devparamupd,
|
||
devparamlist,
|
||
devparambyid,
|
||
} from '@/api/tb/devparam'
|
||
import { dictypeadd, dictypelist } from '@/api/system/dictype'
|
||
import { dicdataadd, dicdatadel, dicdatabydicid } from '@/api/system/dicdata'
|
||
|
||
// 加载状态
|
||
const loading = ref(false)
|
||
const submitting = ref(false)
|
||
|
||
// 设备列表
|
||
const deviceList = ref<any[]>([])
|
||
const total = ref(0)
|
||
|
||
// 功能岛列表和映射
|
||
const islandList = ref<any[]>([])
|
||
const islandMap = ref<Record<number, string>>({})
|
||
|
||
// 查询表单
|
||
const queryForm = reactive({
|
||
devName: '',
|
||
status: undefined as number | undefined,
|
||
islandId: undefined as number | undefined,
|
||
})
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
})
|
||
|
||
// 抽屉相关
|
||
const drawerVisible = ref(false)
|
||
const drawerTitle = ref('新增设备')
|
||
const isEdit = ref(false)
|
||
const formRef = ref()
|
||
|
||
// 表单数据
|
||
const formData = reactive({
|
||
id: undefined,
|
||
devName: '',
|
||
devModel: '',
|
||
devDesc: '',
|
||
remark: '',
|
||
status: 0, // 状态自动设置为0
|
||
})
|
||
|
||
// 表单验证规则
|
||
const formRules = {
|
||
devName: [
|
||
{ required: true, message: '请输入设备名称', trigger: 'blur' },
|
||
],
|
||
}
|
||
|
||
// 获取序号
|
||
const getIndex = (index: number) => {
|
||
return (pagination.pageNum - 1) * pagination.pageSize + index + 1
|
||
}
|
||
|
||
// 获取状态类型
|
||
const getStatusType = (status: number | null | undefined) => {
|
||
if (status === 0) return 'success' // 空闲 - 绿色
|
||
if (status === 1) return 'warning' // 运行 - 黄色
|
||
if (status === 4) return 'danger' // 故障 - 红色
|
||
return 'info'
|
||
}
|
||
|
||
// 获取状态文本
|
||
const getStatusText = (status: number | null | undefined) => {
|
||
if (status === 0) return '空闲'
|
||
if (status === 1) return '运行'
|
||
if (status === 4) return '故障'
|
||
return '未知'
|
||
}
|
||
|
||
// 获取功能岛列表
|
||
const getIslandList = async () => {
|
||
try {
|
||
const res: any = await islandInfolist({
|
||
pageNum: 1,
|
||
pageSize: 1000, // 获取所有功能岛
|
||
})
|
||
|
||
const data = res?.data ?? res ?? {}
|
||
const recordsFromData =
|
||
data.records ||
|
||
data.list ||
|
||
data.rows ||
|
||
data.items ||
|
||
(Array.isArray(data.data) ? data.data : undefined)
|
||
|
||
const records = Array.isArray(recordsFromData)
|
||
? recordsFromData
|
||
: Array.isArray(data)
|
||
? data
|
||
: []
|
||
|
||
islandList.value = records
|
||
|
||
// 建立ID到名称的映射
|
||
const map: Record<number, string> = {}
|
||
records.forEach((island: any) => {
|
||
if (island.id !== null && island.id !== undefined) {
|
||
map[island.id] = island.islandName || island.name || '未命名功能岛'
|
||
}
|
||
})
|
||
islandMap.value = map
|
||
} catch (error) {
|
||
console.error('获取功能岛列表失败:', error)
|
||
}
|
||
}
|
||
|
||
// 获取功能岛名称
|
||
const getIslandName = (islandId: number | null | undefined) => {
|
||
if (islandId === null || islandId === undefined || islandId === 0) {
|
||
return '暂未绑定功能岛'
|
||
}
|
||
return islandMap.value[islandId] || `功能岛${islandId}`
|
||
}
|
||
|
||
// 获取设备列表
|
||
const getDeviceList = async () => {
|
||
try {
|
||
loading.value = true
|
||
const params: any = {
|
||
pageNum: pagination.pageNum,
|
||
pageSize: pagination.pageSize,
|
||
}
|
||
|
||
// 设备名称模糊查询
|
||
if (queryForm.devName) {
|
||
params.devName = queryForm.devName
|
||
}
|
||
// 设备状态精确查询
|
||
if (queryForm.status !== undefined && queryForm.status !== null) {
|
||
params.status = queryForm.status
|
||
}
|
||
// 所属功能岛精确查询
|
||
if (queryForm.islandId !== undefined && queryForm.islandId !== null) {
|
||
params.islandId = queryForm.islandId
|
||
}
|
||
// 排除 devModel 为 PLC 的设备
|
||
params.devModelNot = 'PLC'
|
||
|
||
const res: any = await devInfolist(params)
|
||
|
||
const data = res?.data ?? res ?? {}
|
||
|
||
// 兼容多种返回格式
|
||
const recordsFromData =
|
||
data.records ||
|
||
data.list ||
|
||
data.rows ||
|
||
data.items ||
|
||
(Array.isArray(data.data) ? data.data : undefined)
|
||
|
||
const records = Array.isArray(recordsFromData)
|
||
? recordsFromData
|
||
: Array.isArray(data)
|
||
? data
|
||
: []
|
||
|
||
// 过滤掉 devModel 为 PLC 的设备(前端过滤作为备用)
|
||
const filteredRecords = records.filter((item: any) => {
|
||
const devModel = item.devModel || ''
|
||
return devModel.toUpperCase() !== 'PLC'
|
||
})
|
||
|
||
const totalValue =
|
||
data.total ?? data.count ?? data.totalCount ?? filteredRecords.length ?? 0
|
||
|
||
deviceList.value = filteredRecords
|
||
total.value = Number(totalValue) || 0
|
||
} catch (error) {
|
||
console.error('获取设备列表失败:', error)
|
||
ElMessage.error('获取设备列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 查询
|
||
const handleSearch = () => {
|
||
pagination.pageNum = 1
|
||
getDeviceList()
|
||
}
|
||
|
||
// 重置查询
|
||
const resetSearch = () => {
|
||
queryForm.devName = ''
|
||
queryForm.status = undefined
|
||
queryForm.islandId = undefined
|
||
handleSearch()
|
||
}
|
||
|
||
// 分页大小改变
|
||
const handleSizeChange = (size: number) => {
|
||
pagination.pageSize = size
|
||
getDeviceList()
|
||
}
|
||
|
||
// 当前页改变
|
||
const handleCurrentChange = (page: number) => {
|
||
pagination.pageNum = page
|
||
getDeviceList()
|
||
}
|
||
|
||
// 打开新增抽屉
|
||
const handleAdd = () => {
|
||
isEdit.value = false
|
||
drawerTitle.value = '新增设备'
|
||
resetForm()
|
||
drawerVisible.value = true
|
||
}
|
||
|
||
// 重置表单
|
||
const resetForm = () => {
|
||
formData.id = undefined
|
||
formData.devName = ''
|
||
formData.devModel = ''
|
||
formData.devDesc = ''
|
||
formData.remark = ''
|
||
formData.status = 0
|
||
formRef.value?.clearValidate()
|
||
}
|
||
|
||
// 编辑设备
|
||
const handleEdit = async (item: any) => {
|
||
try {
|
||
loading.value = true
|
||
const res: any = await devInfobyid(item.id)
|
||
|
||
const data = res?.data ?? res ?? {}
|
||
|
||
formData.id = data.id ?? item.id
|
||
formData.devName = data.devName ?? ''
|
||
formData.devModel = data.devModel ?? ''
|
||
formData.devDesc = data.devDesc ?? ''
|
||
formData.remark = data.remark ?? ''
|
||
formData.status = data.status ?? 0
|
||
|
||
isEdit.value = true
|
||
drawerTitle.value = '编辑设备'
|
||
drawerVisible.value = true
|
||
} catch (error) {
|
||
console.error('获取设备信息失败:', error)
|
||
ElMessage.error('获取设备信息失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 删除设备
|
||
const handleDelete = async (item: any) => {
|
||
try {
|
||
// 检查设备是否绑定了功能岛
|
||
if (item.islandId && item.islandId !== 0 && item.islandId !== '0') {
|
||
// 查询功能岛名称
|
||
let islandName = '功能岛'
|
||
try {
|
||
const islandRes: any = await islandInfobyid(item.islandId)
|
||
if (islandRes?.code === '0' || islandRes?.code === 0) {
|
||
const islandData = islandRes.data || islandRes || {}
|
||
islandName = islandData.islandName || islandData.name || '功能岛'
|
||
}
|
||
} catch (error) {
|
||
console.error('获取功能岛信息失败:', error)
|
||
}
|
||
|
||
await ElMessageBox.alert(
|
||
`该设备已绑定${islandName},请先完成解绑`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
return
|
||
}
|
||
|
||
await ElMessageBox.confirm(
|
||
`确定要删除设备"${item.devName || '未命名'}"吗?`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
loading.value = true
|
||
const res: any = await devInfodel(item.id)
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
ElMessage.success('删除成功')
|
||
getDeviceList()
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || '删除失败')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
console.error('删除设备失败:', error)
|
||
ElMessage.error('删除设备失败')
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
try {
|
||
await formRef.value.validate()
|
||
submitting.value = true
|
||
|
||
// 构建提交数据,只包含非空字段(根据后端要求:非空则入库/更新)
|
||
const submitData: any = {
|
||
devName: formData.devName,
|
||
}
|
||
|
||
// 添加其他字段(如果非空)
|
||
if (formData.devModel) {
|
||
submitData.devModel = formData.devModel
|
||
}
|
||
if (formData.devDesc) {
|
||
submitData.devDesc = formData.devDesc
|
||
}
|
||
if (formData.remark) {
|
||
submitData.remark = formData.remark
|
||
}
|
||
|
||
let res: any
|
||
if (isEdit.value && formData.id) {
|
||
// 编辑 - 需要传入ID,其他字段可选(非空则更新)
|
||
res = await devInfoupd({
|
||
id: formData.id,
|
||
...submitData,
|
||
})
|
||
} else {
|
||
// 新增 - 状态自动设置为0
|
||
res = await devInfoadd({
|
||
...submitData,
|
||
status: 0,
|
||
})
|
||
}
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
|
||
drawerVisible.value = false
|
||
getDeviceList()
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || (isEdit.value ? '编辑失败' : '新增失败'))
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== false) {
|
||
console.error('提交失败:', error)
|
||
ElMessage.error('提交失败,请检查表单')
|
||
}
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
// 关闭抽屉
|
||
const handleDrawerClose = () => {
|
||
drawerVisible.value = false
|
||
resetForm()
|
||
}
|
||
|
||
// 参数配置相关
|
||
const paramDialogVisible = ref(false)
|
||
const paramLoading = ref(false)
|
||
const paramList = ref<any[]>([])
|
||
const paramTotal = ref(0)
|
||
const currentDevice = ref<any>(null)
|
||
const paramEmptyText = ref('该设备暂无参数,请完成配置')
|
||
|
||
const paramPagination = reactive({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
})
|
||
|
||
// 参数类型选项(固定数据类型)
|
||
const paramTypeOptions = [
|
||
{ label: '字符串', value: 'string' },
|
||
{ label: '整数', value: 'integer' },
|
||
{ label: '浮点数', value: 'float' },
|
||
{ label: '布尔值', value: 'boolean' },
|
||
]
|
||
|
||
// 表单控件选项(常用表单控件)
|
||
const formTypeOptions = [
|
||
{ label: '文本输入框', value: 'input' },
|
||
{ label: '数字输入框', value: 'number' },
|
||
{ label: '下拉框', value: 'select' },
|
||
{ label: '文本域', value: 'textarea' },
|
||
]
|
||
|
||
// 获取参数序号
|
||
const getParamIndex = (index: number) => {
|
||
return (paramPagination.pageNum - 1) * paramPagination.pageSize + index + 1
|
||
}
|
||
|
||
// 获取表单控件标签
|
||
const getFormTypeLabel = (formType: string) => {
|
||
const option = formTypeOptions.find((item) => item.value === formType)
|
||
return option ? option.label : formType
|
||
}
|
||
|
||
// 打开参数配置对话框
|
||
const handleConfigParams = async (row: any) => {
|
||
currentDevice.value = row
|
||
paramPagination.pageNum = 1
|
||
paramPagination.pageSize = 10
|
||
paramDialogVisible.value = true
|
||
await getParamList()
|
||
}
|
||
|
||
// 获取参数列表
|
||
const getParamList = async () => {
|
||
if (!currentDevice.value?.id) return
|
||
|
||
try {
|
||
paramLoading.value = true
|
||
const params: any = {
|
||
pageNum: paramPagination.pageNum,
|
||
pageSize: paramPagination.pageSize,
|
||
devId: currentDevice.value.id, // 只查询该设备的参数
|
||
}
|
||
|
||
const res: any = await devparamlist(params)
|
||
|
||
const data = res?.data ?? res ?? {}
|
||
|
||
// 兼容多种返回格式
|
||
const recordsFromData =
|
||
data.records ||
|
||
data.list ||
|
||
data.rows ||
|
||
data.items ||
|
||
(Array.isArray(data.data) ? data.data : undefined)
|
||
|
||
const records = Array.isArray(recordsFromData)
|
||
? recordsFromData
|
||
: Array.isArray(data)
|
||
? data
|
||
: []
|
||
|
||
// 按主键id升序排列(没有id的排在最后)
|
||
records.sort((a: any, b: any) => {
|
||
const idA = a.id ?? 0
|
||
const idB = b.id ?? 0
|
||
// 如果都没有id,保持原顺序
|
||
if (idA === 0 && idB === 0) return 0
|
||
// 如果a没有id,a排在后面
|
||
if (idA === 0) return 1
|
||
// 如果b没有id,b排在后面
|
||
if (idB === 0) return -1
|
||
// 都有id,按id升序
|
||
return idA - idB
|
||
})
|
||
|
||
const totalValue =
|
||
data.total ?? data.count ?? data.totalCount ?? records.length ?? 0
|
||
|
||
// 为每条记录添加编辑状态标识
|
||
paramList.value = records.map((item: any) => ({
|
||
...item,
|
||
isEditing: false,
|
||
}))
|
||
paramTotal.value = Number(totalValue) || 0
|
||
|
||
if (paramList.value.length === 0) {
|
||
paramEmptyText.value = '该设备暂无参数,请完成配置'
|
||
}
|
||
} catch (error) {
|
||
console.error('获取参数列表失败:', error)
|
||
ElMessage.error('获取参数列表失败')
|
||
paramList.value = []
|
||
paramTotal.value = 0
|
||
} finally {
|
||
paramLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 参数分页大小改变
|
||
const handleParamSizeChange = (size: number) => {
|
||
paramPagination.pageSize = size
|
||
getParamList()
|
||
}
|
||
|
||
// 参数当前页改变
|
||
const handleParamCurrentChange = (page: number) => {
|
||
paramPagination.pageNum = page
|
||
getParamList()
|
||
}
|
||
|
||
// 添加一行参数
|
||
const handleAddParamRow = () => {
|
||
const newRow = {
|
||
id: undefined,
|
||
devId: currentDevice.value?.id,
|
||
paramName: '',
|
||
paramType: '',
|
||
paramUnit: '',
|
||
formType: '',
|
||
paramValue: '',
|
||
isEditing: true,
|
||
isNew: true, // 标记为新添加的行
|
||
}
|
||
paramList.value.push(newRow)
|
||
paramTotal.value += 1
|
||
}
|
||
|
||
// 编辑参数
|
||
const handleEditParam = (row: any) => {
|
||
row.isEditing = true
|
||
}
|
||
|
||
// 保存参数
|
||
const handleSaveParam = async (row: any) => {
|
||
// 验证必填项
|
||
if (!row.paramName || row.paramName.trim() === '') {
|
||
ElMessage.warning('参数名称不能为空')
|
||
return
|
||
}
|
||
|
||
if (!currentDevice.value?.id) {
|
||
ElMessage.error('设备ID不存在')
|
||
return
|
||
}
|
||
|
||
try {
|
||
paramLoading.value = true
|
||
|
||
const submitData: any = {
|
||
devId: currentDevice.value.id,
|
||
paramName: row.paramName.trim(),
|
||
}
|
||
|
||
// 添加其他字段(如果非空)
|
||
if (row.paramType) {
|
||
submitData.paramType = row.paramType
|
||
}
|
||
if (row.paramUnit) {
|
||
submitData.paramUnit = row.paramUnit
|
||
}
|
||
if (row.formType) {
|
||
submitData.formType = row.formType
|
||
}
|
||
if (row.paramValue !== undefined && row.paramValue !== null) {
|
||
submitData.paramValue = row.paramValue
|
||
}
|
||
|
||
let res: any
|
||
if (row.isNew && !row.id) {
|
||
// 新增
|
||
res = await devparamadd(submitData)
|
||
} else {
|
||
// 更新 - 需要传入ID,其他字段可选(非空则更新)
|
||
res = await devparamupd({
|
||
id: row.id,
|
||
...submitData,
|
||
})
|
||
}
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
const wasNew = row.isNew
|
||
ElMessage.success(wasNew ? '新增成功' : '更新成功')
|
||
row.isEditing = false
|
||
row.isNew = false
|
||
// 如果是新增,刷新列表以获取ID
|
||
if (wasNew) {
|
||
await getParamList()
|
||
}
|
||
|
||
// 如果表单控件是下拉框,检查是否已配置过选项,只有未配置时才弹出配置对话框
|
||
if (row.formType === 'select') {
|
||
// 查找对应的字典类型
|
||
const dicTypeListRes: any = await dictypelist({
|
||
pageNum: 1,
|
||
pageSize: 1000,
|
||
})
|
||
|
||
const dicTypeData = dicTypeListRes?.data ?? dicTypeListRes ?? {}
|
||
const dicTypeRecords = dicTypeData.records || dicTypeData.list || dicTypeData.rows || []
|
||
|
||
const dicType = dicTypeRecords.find(
|
||
(item: any) => item.dicName === row.paramName
|
||
)
|
||
|
||
if (dicType) {
|
||
// 检查是否已有选项数据
|
||
const optionsRes: any = await dicdatabydicid(dicType.id)
|
||
const optionsData = optionsRes?.data ?? optionsRes ?? {}
|
||
const optionsRecords = Array.isArray(optionsData) ? optionsData : (optionsData.records || optionsData.list || optionsData.rows || [])
|
||
|
||
// 只有没有选项数据时才弹出配置对话框
|
||
if (optionsRecords.length === 0) {
|
||
await handleCreateDicTypeAndShowConfig(row)
|
||
}
|
||
} else {
|
||
// 如果字典类型不存在,创建并弹出配置对话框
|
||
await handleCreateDicTypeAndShowConfig(row)
|
||
}
|
||
}
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || (row.isNew ? '新增失败' : '更新失败'))
|
||
}
|
||
} catch (error) {
|
||
console.error('保存参数失败:', error)
|
||
ElMessage.error('保存参数失败')
|
||
} finally {
|
||
paramLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 创建字典类型并显示配置对话框
|
||
const handleCreateDicTypeAndShowConfig = async (row: any) => {
|
||
try {
|
||
// 检查是否已存在该字典类型
|
||
const dicTypeListRes: any = await dictypelist({
|
||
pageNum: 1,
|
||
pageSize: 1000,
|
||
})
|
||
|
||
const dicTypeData = dicTypeListRes?.data ?? dicTypeListRes ?? {}
|
||
const dicTypeRecords = dicTypeData.records || dicTypeData.list || dicTypeData.rows || []
|
||
|
||
// 查找是否已存在相同名称的字典类型
|
||
const existingDicType = dicTypeRecords.find(
|
||
(item: any) => item.dicName === row.paramName
|
||
)
|
||
|
||
let dicTypeId: number | undefined
|
||
|
||
if (existingDicType) {
|
||
// 如果已存在,使用现有的ID
|
||
dicTypeId = existingDicType.id
|
||
} else {
|
||
// 如果不存在,创建新的字典类型
|
||
const dicTypeRes: any = await dictypeadd({
|
||
dicName: row.paramName,
|
||
})
|
||
|
||
if (dicTypeRes?.code === '0' || dicTypeRes?.code === 0 || dicTypeRes?.success) {
|
||
// 获取新创建的字典类型ID
|
||
const newDicTypeData = dicTypeRes?.data ?? dicTypeRes ?? {}
|
||
dicTypeId = newDicTypeData.id
|
||
|
||
// 如果返回的数据中没有ID,重新查询
|
||
if (!dicTypeId) {
|
||
const refreshListRes: any = await dictypelist({
|
||
pageNum: 1,
|
||
pageSize: 1000,
|
||
})
|
||
const refreshData = refreshListRes?.data ?? refreshListRes ?? {}
|
||
const refreshRecords = refreshData.records || refreshData.list || refreshData.rows || []
|
||
const newDicType = refreshRecords.find(
|
||
(item: any) => item.dicName === row.paramName
|
||
)
|
||
dicTypeId = newDicType?.id
|
||
}
|
||
} else {
|
||
ElMessage.error('创建字典类型失败')
|
||
return
|
||
}
|
||
}
|
||
|
||
// 打开配置对话框
|
||
if (dicTypeId) {
|
||
// 先加载选项列表,检查是否已有选项数据
|
||
await loadSelectOptions(dicTypeId)
|
||
|
||
// 如果还没有选项数据,才弹出提示
|
||
if (selectOptionsList.value.length === 0) {
|
||
await ElMessageBox.alert('请编辑下拉框选项列表内容', '提示', {
|
||
confirmButtonText: '确定',
|
||
type: 'info',
|
||
})
|
||
}
|
||
|
||
currentSelectParam.value = {
|
||
...row,
|
||
dicTypeId: dicTypeId,
|
||
}
|
||
selectOptionsDialogVisible.value = true
|
||
} else {
|
||
ElMessage.error('无法获取字典类型ID')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
console.error('创建字典类型失败:', error)
|
||
ElMessage.error('创建字典类型失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除参数
|
||
const handleDeleteParam = async (row: any) => {
|
||
try {
|
||
// 如果是新添加的行(还未保存),直接从列表中移除
|
||
if (row.isNew && !row.id) {
|
||
const index = paramList.value.findIndex((item) => item === row)
|
||
if (index > -1) {
|
||
paramList.value.splice(index, 1)
|
||
paramTotal.value -= 1
|
||
}
|
||
return
|
||
}
|
||
|
||
if (!row.id) {
|
||
ElMessage.warning('无法删除:参数ID不存在')
|
||
return
|
||
}
|
||
|
||
await ElMessageBox.confirm(
|
||
`确定要删除参数"${row.paramName || '未命名'}"吗?`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
paramLoading.value = true
|
||
const res: any = await devparamdel(row.id)
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
ElMessage.success('删除成功')
|
||
await getParamList()
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || '删除失败')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
console.error('删除参数失败:', error)
|
||
ElMessage.error('删除参数失败')
|
||
}
|
||
} finally {
|
||
paramLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 关闭参数配置对话框
|
||
const handleParamDialogClose = () => {
|
||
paramDialogVisible.value = false
|
||
paramList.value = []
|
||
paramTotal.value = 0
|
||
currentDevice.value = null
|
||
}
|
||
|
||
// 下拉框选项配置相关
|
||
const selectOptionsDialogVisible = ref(false)
|
||
const currentSelectParam = ref<any>(null)
|
||
const selectOptionInput = ref('')
|
||
const selectOptionsList = ref<any[]>([])
|
||
|
||
// 打开下拉框选项配置对话框
|
||
const handleConfigSelectOptions = async (row: any) => {
|
||
try {
|
||
// 查找对应的字典类型
|
||
const dicTypeListRes: any = await dictypelist({
|
||
pageNum: 1,
|
||
pageSize: 1000,
|
||
})
|
||
|
||
const dicTypeData = dicTypeListRes?.data ?? dicTypeListRes ?? {}
|
||
const dicTypeRecords = dicTypeData.records || dicTypeData.list || dicTypeData.rows || []
|
||
|
||
const dicType = dicTypeRecords.find(
|
||
(item: any) => item.dicName === row.paramName
|
||
)
|
||
|
||
if (!dicType) {
|
||
ElMessage.warning('未找到对应的字典类型,请先保存参数')
|
||
return
|
||
}
|
||
|
||
currentSelectParam.value = {
|
||
...row,
|
||
dicTypeId: dicType.id,
|
||
}
|
||
selectOptionsDialogVisible.value = true
|
||
await loadSelectOptions(dicType.id)
|
||
} catch (error) {
|
||
console.error('打开下拉框配置失败:', error)
|
||
ElMessage.error('打开下拉框配置失败')
|
||
}
|
||
}
|
||
|
||
// 加载下拉框选项列表
|
||
const loadSelectOptions = async (dicId: number) => {
|
||
try {
|
||
const res: any = await dicdatabydicid(dicId)
|
||
|
||
const data = res?.data ?? res ?? {}
|
||
const records = Array.isArray(data) ? data : (data.records || data.list || data.rows || [])
|
||
|
||
selectOptionsList.value = records
|
||
} catch (error) {
|
||
console.error('加载下拉框选项失败:', error)
|
||
ElMessage.error('加载下拉框选项失败')
|
||
selectOptionsList.value = []
|
||
}
|
||
}
|
||
|
||
// 添加下拉框选项
|
||
const handleAddSelectOption = async () => {
|
||
if (!selectOptionInput.value || selectOptionInput.value.trim() === '') {
|
||
ElMessage.warning('请输入选项数据')
|
||
return
|
||
}
|
||
|
||
if (!currentSelectParam.value?.dicTypeId) {
|
||
ElMessage.error('字典类型ID不存在')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const res: any = await dicdataadd({
|
||
dicId: currentSelectParam.value.dicTypeId,
|
||
dicLabel: currentSelectParam.value.paramType || '',
|
||
dicValue: selectOptionInput.value.trim(),
|
||
})
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
ElMessage.success('添加成功')
|
||
selectOptionInput.value = ''
|
||
// 重新加载选项列表
|
||
await loadSelectOptions(currentSelectParam.value.dicTypeId)
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || '添加失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('添加下拉框选项失败:', error)
|
||
ElMessage.error('添加下拉框选项失败')
|
||
}
|
||
}
|
||
|
||
// 删除下拉框选项
|
||
const handleDeleteSelectOption = async (option: any) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除"${option.dicValue || '该选项'}"吗?`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
|
||
const res: any = await dicdatadel(option.id)
|
||
|
||
if (res?.code === '0' || res?.code === 0 || res?.success) {
|
||
ElMessage.success('删除成功')
|
||
// 重新加载选项列表
|
||
if (currentSelectParam.value?.dicTypeId) {
|
||
await loadSelectOptions(currentSelectParam.value.dicTypeId)
|
||
}
|
||
} else {
|
||
ElMessage.error(res?.message || res?.msg || '删除失败')
|
||
}
|
||
} catch (error: any) {
|
||
if (error !== 'cancel') {
|
||
console.error('删除下拉框选项失败:', error)
|
||
ElMessage.error('删除下拉框选项失败')
|
||
}
|
||
}
|
||
}
|
||
|
||
// 保存下拉框选项配置
|
||
const handleSaveSelectOptions = async () => {
|
||
if (!currentSelectParam.value) {
|
||
return
|
||
}
|
||
|
||
if (selectOptionsList.value.length === 0) {
|
||
await ElMessageBox.alert(
|
||
`${currentSelectParam.value.paramName}的下拉框选项列表还未配置,请先完成配置`,
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
return
|
||
}
|
||
|
||
selectOptionsDialogVisible.value = false
|
||
ElMessage.success(`${currentSelectParam.value.paramName}下拉框选项列表保存成功!`)
|
||
}
|
||
|
||
// 关闭下拉框选项配置对话框
|
||
const handleSelectOptionsDialogClose = () => {
|
||
selectOptionsDialogVisible.value = false
|
||
selectOptionInput.value = ''
|
||
selectOptionsList.value = []
|
||
currentSelectParam.value = null
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
getIslandList().then(() => {
|
||
getDeviceList()
|
||
})
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.devinfo-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.search-card {
|
||
padding-bottom: 4px;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 16px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.drawer-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.param-config-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.param-toolbar {
|
||
display: flex;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
}
|
||
|
||
.param-pagination {
|
||
margin-top: 16px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.select-options-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.select-options-input-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.input-label {
|
||
min-width: 80px;
|
||
font-weight: 500;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.select-options-cards {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
min-height: 100px;
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
background: linear-gradient(135deg, #e3f2fd 0%, #f0f2f5 100%);
|
||
border-radius: 4px;
|
||
background-image:
|
||
linear-gradient(rgba(64, 158, 255, 0.03) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(64, 158, 255, 0.03) 1px, transparent 1px);
|
||
background-size: 20px 20px;
|
||
}
|
||
|
||
.select-option-card {
|
||
position: relative;
|
||
padding: 16px 20px;
|
||
background-color: #fff;
|
||
border: 1px solid rgba(64, 158, 255, 0.1);
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
cursor: default;
|
||
transition: all 0.3s;
|
||
min-width: 120px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.select-option-card:hover {
|
||
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.2);
|
||
transform: translateY(-4px);
|
||
border-color: rgba(64, 158, 255, 0.3);
|
||
}
|
||
|
||
.option-text {
|
||
font-size: 1.2em;
|
||
color: #303133;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.option-delete-icon {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
font-size: 16px;
|
||
color: #909399;
|
||
cursor: pointer;
|
||
transition: color 0.3s;
|
||
padding: 4px;
|
||
}
|
||
|
||
.option-delete-icon:hover {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.empty-tip {
|
||
width: 100%;
|
||
text-align: center;
|
||
color: #909399;
|
||
font-size: 16px;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.form-type-select-btn {
|
||
font-size: inherit;
|
||
}
|
||
</style>
|
||
|