fix: iot 补齐 vue3 源缺失的表单校验(P1)

- 物模型功能定义「数据类型」字段补 required 必填校验
- 产品选择器(ProductSelect)补搜索能力:antd 加 show-search + option-filter-prop ,ele 加 filterable
- 设备表单 onConfirm 增加 advancedFormApi.validate() 调用,
  否则高级表单(含经纬度等字段)的 schema rules 不会触发
- 设备经纬度增加「成对填写」跨字段校验:仅填一项时给 warning 提示,
  与 vue3 源 DeviceForm.vue 行为对齐
pull/345/head
YunaiV 2026-05-21 14:43:06 +08:00
parent d207e3b82c
commit 58f2e23654
12 changed files with 113 additions and 28 deletions

View File

@ -75,7 +75,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '是否处理',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
placeholder: '请选择是否处理',
allowClear: true,
},

View File

@ -118,6 +118,26 @@ const [Modal, modalApi] = useVbenModal({
if (!valid) {
return;
}
//
if (advancedFormApi.isMounted) {
const { valid: advancedValid } = await advancedFormApi.validate();
if (!advancedValid) {
return;
}
const advValues = await advancedFormApi.getValues();
const hasLongitude =
advValues.longitude !== undefined &&
advValues.longitude !== null &&
advValues.longitude !== '';
const hasLatitude =
advValues.latitude !== undefined &&
advValues.latitude !== null &&
advValues.latitude !== '';
if (hasLongitude !== hasLatitude) {
message.warning(hasLongitude ? '请同时填写设备纬度' : '请同时填写设备经度');
return;
}
}
modalApi.lock();
//
const basicValues = await formApi.getValues();

View File

@ -15,7 +15,7 @@ export function getMessageTrendChartOptions(
},
},
legend: {
data: ['上行消息', '下行消息'],
data: ['上行消息', '下行消息'],
top: '5%',
},
grid: {
@ -40,11 +40,21 @@ export function getMessageTrendChartOptions(
],
series: [
{
name: '上行消息',
name: '上行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
{ offset: 1, color: 'rgba(24, 144, 255, 0)' },
],
},
},
emphasis: {
focus: 'series',
@ -55,11 +65,21 @@ export function getMessageTrendChartOptions(
},
},
{
name: '下行消息',
name: '下行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
{ offset: 1, color: 'rgba(82, 196, 26, 0)' },
],
},
},
emphasis: {
focus: 'series',
@ -84,8 +104,8 @@ export function getDeviceStateGaugeChartOptions(
series: [
{
type: 'gauge',
startAngle: 225,
endAngle: -45,
startAngle: 360,
endAngle: 0,
min: 0,
max,
center: ['50%', '50%'],

View File

@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { computed, nextTick, reactive, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@ -123,10 +123,6 @@ async function renderChartWhenReady() {
initChart();
}
/** 组件挂载时查询数据 */
onMounted(() => {
fetchMessageData();
});
</script>
<template>

View File

@ -52,6 +52,8 @@ onMounted(() => {
placeholder="请选择产品"
allow-clear
class="w-full"
option-filter-prop="label"
show-search
@change="handleChange"
/>
</template>

View File

@ -102,7 +102,11 @@ if (!props.isStructDataSpecs && !props.isParams) {
</script>
<template>
<Form.Item label="数据类型">
<Form.Item
:name="['property', 'dataType']"
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
label="数据类型"
>
<Select
v-model:value="property.dataType"
placeholder="请选择数据类型"

View File

@ -75,7 +75,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '是否处理',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
placeholder: '请选择是否处理',
clearable: true,
},

View File

@ -123,6 +123,28 @@ const [Modal, modalApi] = useVbenModal({
if (!valid) {
return;
}
//
if (advancedFormApi.isMounted) {
const { valid: advancedValid } = await advancedFormApi.validate();
if (!advancedValid) {
return;
}
const advValues = await advancedFormApi.getValues();
const hasLongitude =
advValues.longitude !== undefined &&
advValues.longitude !== null &&
advValues.longitude !== '';
const hasLatitude =
advValues.latitude !== undefined &&
advValues.latitude !== null &&
advValues.latitude !== '';
if (hasLongitude !== hasLatitude) {
ElMessage.warning(
hasLongitude ? '请同时填写设备纬度' : '请同时填写设备经度',
);
return;
}
}
modalApi.lock();
//
const basicValues = await formApi.getValues();

View File

@ -15,7 +15,7 @@ export function getMessageTrendChartOptions(
},
},
legend: {
data: ['上行消息', '下行消息'],
data: ['上行消息', '下行消息'],
top: '5%',
},
grid: {
@ -40,11 +40,21 @@ export function getMessageTrendChartOptions(
],
series: [
{
name: '上行消息',
name: '上行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
{ offset: 1, color: 'rgba(24, 144, 255, 0)' },
],
},
},
emphasis: {
focus: 'series',
@ -55,11 +65,21 @@ export function getMessageTrendChartOptions(
},
},
{
name: '下行消息',
name: '下行消息',
type: 'line',
smooth: true,
areaStyle: {
opacity: 0.3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(82, 196, 26, 0.3)' },
{ offset: 1, color: 'rgba(82, 196, 26, 0)' },
],
},
},
emphasis: {
focus: 'series',
@ -84,8 +104,8 @@ export function getDeviceStateGaugeChartOptions(
series: [
{
type: 'gauge',
startAngle: 225,
endAngle: -45,
startAngle: 360,
endAngle: 0,
min: 0,
max,
center: ['50%', '50%'],

View File

@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { computed, nextTick, reactive, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@ -123,10 +123,6 @@ async function renderChartWhenReady() {
initChart();
}
/** 组件挂载时查询数据 */
onMounted(() => {
fetchMessageData();
});
</script>
<template>

View File

@ -50,6 +50,7 @@ onMounted(() => {
:loading="loading"
placeholder="请选择产品"
clearable
filterable
class="w-full"
@change="handleChange"
>

View File

@ -109,7 +109,11 @@ if (!props.isStructDataSpecs && !props.isParams) {
</script>
<template>
<ElFormItem label="数据类型">
<ElFormItem
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
label="数据类型"
prop="property.dataType"
>
<ElSelect
v-model="property.dataType"
placeholder="请选择数据类型"