perf:【IoT 物联网】场景联动目录结构优化

This commit is contained in:
puhui999
2025-07-26 22:35:59 +08:00
parent 52b9c1827e
commit 8549399ae8
29 changed files with 33 additions and 46 deletions

View File

@@ -0,0 +1,415 @@
<!-- 值输入组件 -->
<!-- TODO @yunai这个需要在看看 -->
<template>
<div class="value-input">
<!-- 布尔值选择 -->
<el-select
v-if="propertyType === 'bool'"
v-model="localValue"
placeholder="请选择布尔值"
@change="handleChange"
class="w-full"
>
<el-option label="真 (true)" value="true" />
<el-option label="假 (false)" value="false" />
</el-select>
<!-- 枚举值选择 -->
<el-select
v-else-if="propertyType === 'enum' && enumOptions.length > 0"
v-model="localValue"
placeholder="请选择枚举值"
@change="handleChange"
class="w-full"
>
<el-option
v-for="option in enumOptions"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<!-- 范围输入 (between 操作符) -->
<div v-else-if="operator === 'between'" class="range-input">
<el-input
v-model="rangeStart"
:type="getInputType()"
placeholder="最小值"
@input="handleRangeChange"
class="range-start"
/>
<span class="range-separator"></span>
<el-input
v-model="rangeEnd"
:type="getInputType()"
placeholder="最大值"
@input="handleRangeChange"
class="range-end"
/>
</div>
<!-- 列表输入 (in 操作符) -->
<div v-else-if="operator === 'in'" class="list-input">
<el-input
v-model="localValue"
placeholder="请输入值列表,用逗号分隔"
@input="handleChange"
class="w-full"
>
<template #suffix>
<el-tooltip content="多个值用逗号分隔1,2,3" placement="top">
<Icon icon="ep:question-filled" class="input-tip" />
</el-tooltip>
</template>
</el-input>
<div v-if="listPreview.length > 0" class="list-preview">
<span class="preview-label">解析结果</span>
<el-tag v-for="(item, index) in listPreview" :key="index" size="small" class="preview-tag">
{{ item }}
</el-tag>
</div>
</div>
<!-- 日期时间输入 -->
<el-date-picker
v-else-if="propertyType === 'date'"
v-model="dateValue"
type="datetime"
placeholder="请选择日期时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
@change="handleDateChange"
class="w-full"
/>
<!-- 数字输入 -->
<el-input-number
v-else-if="isNumericType()"
v-model="numberValue"
:precision="getPrecision()"
:step="getStep()"
:min="getMin()"
:max="getMax()"
placeholder="请输入数值"
@change="handleNumberChange"
class="w-full"
/>
<!-- 文本输入 -->
<el-input
v-else
v-model="localValue"
:type="getInputType()"
:placeholder="getPlaceholder()"
@input="handleChange"
class="w-full"
>
<template #suffix>
<el-tooltip
v-if="propertyConfig?.unit"
:content="`单位:${propertyConfig.unit}`"
placement="top"
>
<span class="input-unit">{{ propertyConfig.unit }}</span>
</el-tooltip>
</template>
</el-input>
<!-- 验证提示 -->
<div v-if="validationMessage" class="validation-message">
<el-text :type="isValid ? 'success' : 'danger'" size="small">
<Icon :icon="isValid ? 'ep:check' : 'ep:warning-filled'" />
{{ validationMessage }}
</el-text>
</div>
</div>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
/** 值输入组件 */
defineOptions({ name: 'ValueInput' })
interface Props {
modelValue?: string
propertyType?: string
operator?: string
propertyConfig?: any
}
interface Emits {
(e: 'update:modelValue', value: string): void
(e: 'validate', result: { valid: boolean; message: string }): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const localValue = useVModel(props, 'modelValue', emit, {
defaultValue: ''
})
// 状态
const rangeStart = ref('')
const rangeEnd = ref('')
const dateValue = ref('')
const numberValue = ref<number>()
const validationMessage = ref('')
const isValid = ref(true)
// 计算属性
const enumOptions = computed(() => {
if (props.propertyConfig?.enum) {
return props.propertyConfig.enum.map((item: any) => ({
label: item.name || item.label || item.value,
value: item.value
}))
}
return []
})
const listPreview = computed(() => {
if (props.operator === 'in' && localValue.value) {
return localValue.value
.split(',')
.map((item) => item.trim())
.filter((item) => item)
}
return []
})
// 工具函数
const isNumericType = () => {
return ['int', 'float', 'double'].includes(props.propertyType || '')
}
const getInputType = () => {
switch (props.propertyType) {
case 'int':
case 'float':
case 'double':
return 'number'
default:
return 'text'
}
}
const getPlaceholder = () => {
const typeMap = {
string: '请输入字符串',
int: '请输入整数',
float: '请输入浮点数',
double: '请输入双精度数',
struct: '请输入JSON格式数据',
array: '请输入数组格式数据'
}
return typeMap[props.propertyType || ''] || '请输入值'
}
const getPrecision = () => {
return props.propertyType === 'int' ? 0 : 2
}
const getStep = () => {
return props.propertyType === 'int' ? 1 : 0.1
}
const getMin = () => {
return props.propertyConfig?.min || undefined
}
const getMax = () => {
return props.propertyConfig?.max || undefined
}
// 事件处理
const handleChange = () => {
validateValue()
}
const handleRangeChange = () => {
if (rangeStart.value && rangeEnd.value) {
localValue.value = `${rangeStart.value},${rangeEnd.value}`
} else {
localValue.value = ''
}
validateValue()
}
const handleDateChange = (value: string) => {
localValue.value = value || ''
validateValue()
}
const handleNumberChange = (value: number | undefined) => {
localValue.value = value?.toString() || ''
validateValue()
}
// 验证函数
const validateValue = () => {
if (!localValue.value) {
isValid.value = false
validationMessage.value = '请输入值'
emit('validate', { valid: false, message: validationMessage.value })
return
}
// 数字类型验证
if (isNumericType()) {
const num = parseFloat(localValue.value)
if (isNaN(num)) {
isValid.value = false
validationMessage.value = '请输入有效的数字'
emit('validate', { valid: false, message: validationMessage.value })
return
}
// 范围验证
const min = getMin()
const max = getMax()
if (min !== undefined && num < min) {
isValid.value = false
validationMessage.value = `值不能小于 ${min}`
emit('validate', { valid: false, message: validationMessage.value })
return
}
if (max !== undefined && num > max) {
isValid.value = false
validationMessage.value = `值不能大于 ${max}`
emit('validate', { valid: false, message: validationMessage.value })
return
}
}
// 范围输入验证
if (props.operator === 'between') {
const parts = localValue.value.split(',')
if (parts.length !== 2) {
isValid.value = false
validationMessage.value = '范围格式错误'
emit('validate', { valid: false, message: validationMessage.value })
return
}
const start = parseFloat(parts[0])
const end = parseFloat(parts[1])
if (isNaN(start) || isNaN(end)) {
isValid.value = false
validationMessage.value = '范围值必须是数字'
emit('validate', { valid: false, message: validationMessage.value })
return
}
if (start >= end) {
isValid.value = false
validationMessage.value = '起始值必须小于结束值'
emit('validate', { valid: false, message: validationMessage.value })
return
}
}
// 列表输入验证
if (props.operator === 'in') {
if (listPreview.value.length === 0) {
isValid.value = false
validationMessage.value = '请输入至少一个值'
emit('validate', { valid: false, message: validationMessage.value })
return
}
}
// 验证通过
isValid.value = true
validationMessage.value = '输入值验证通过'
emit('validate', { valid: true, message: validationMessage.value })
}
// 监听值变化
watch(
() => localValue.value,
() => {
validateValue()
}
)
// 监听操作符变化
watch(
() => props.operator,
() => {
localValue.value = ''
rangeStart.value = ''
rangeEnd.value = ''
dateValue.value = ''
numberValue.value = undefined
}
)
// 初始化
onMounted(() => {
if (localValue.value) {
validateValue()
}
})
</script>
<style scoped>
.value-input {
width: 100%;
}
.range-input {
display: flex;
align-items: center;
gap: 8px;
}
.range-start,
.range-end {
flex: 1;
}
.range-separator {
font-size: 12px;
color: var(--el-text-color-secondary);
white-space: nowrap;
}
.list-input {
width: 100%;
}
.input-tip {
color: var(--el-text-color-placeholder);
cursor: help;
}
.input-unit {
font-size: 12px;
color: var(--el-text-color-secondary);
padding: 0 4px;
}
.list-preview {
margin-top: 8px;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.preview-label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.preview-tag {
margin: 0;
}
.validation-message {
margin-top: 4px;
}
</style>