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
parent
d207e3b82c
commit
58f2e23654
|
|
@ -75,7 +75,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '是否处理',
|
label: '是否处理',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
|
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||||
placeholder: '请选择是否处理',
|
placeholder: '请选择是否处理',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,26 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
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();
|
modalApi.lock();
|
||||||
// 合并两个表单的值(字段不冲突,可直接合并)
|
// 合并两个表单的值(字段不冲突,可直接合并)
|
||||||
const basicValues = await formApi.getValues();
|
const basicValues = await formApi.getValues();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export function getMessageTrendChartOptions(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ['上行消息', '下行消息'],
|
data: ['上行消息量', '下行消息量'],
|
||||||
top: '5%',
|
top: '5%',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
|
|
@ -40,11 +40,21 @@ export function getMessageTrendChartOptions(
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '上行消息',
|
name: '上行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
areaStyle: {
|
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: {
|
emphasis: {
|
||||||
focus: 'series',
|
focus: 'series',
|
||||||
|
|
@ -55,11 +65,21 @@ export function getMessageTrendChartOptions(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '下行消息',
|
name: '下行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
areaStyle: {
|
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: {
|
emphasis: {
|
||||||
focus: 'series',
|
focus: 'series',
|
||||||
|
|
@ -84,8 +104,8 @@ export function getDeviceStateGaugeChartOptions(
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'gauge',
|
type: 'gauge',
|
||||||
startAngle: 225,
|
startAngle: 360,
|
||||||
endAngle: -45,
|
endAngle: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
max,
|
max,
|
||||||
center: ['50%', '50%'],
|
center: ['50%', '50%'],
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
import type { IotStatisticsApi } from '#/api/iot/statistics';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
@ -123,10 +123,6 @@ async function renderChartWhenReady() {
|
||||||
initChart();
|
initChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件挂载时查询数据 */
|
|
||||||
onMounted(() => {
|
|
||||||
fetchMessageData();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ onMounted(() => {
|
||||||
placeholder="请选择产品"
|
placeholder="请选择产品"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
option-filter-prop="label"
|
||||||
|
show-search
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,11 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form.Item label="数据类型">
|
<Form.Item
|
||||||
|
:name="['property', 'dataType']"
|
||||||
|
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
|
||||||
|
label="数据类型"
|
||||||
|
>
|
||||||
<Select
|
<Select
|
||||||
v-model:value="property.dataType"
|
v-model:value="property.dataType"
|
||||||
placeholder="请选择数据类型"
|
placeholder="请选择数据类型"
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '是否处理',
|
label: '是否处理',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING),
|
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
|
||||||
placeholder: '请选择是否处理',
|
placeholder: '请选择是否处理',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,28 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
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();
|
modalApi.lock();
|
||||||
// 合并两个表单的值(字段不冲突,可直接合并)
|
// 合并两个表单的值(字段不冲突,可直接合并)
|
||||||
const basicValues = await formApi.getValues();
|
const basicValues = await formApi.getValues();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export function getMessageTrendChartOptions(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ['上行消息', '下行消息'],
|
data: ['上行消息量', '下行消息量'],
|
||||||
top: '5%',
|
top: '5%',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
|
|
@ -40,11 +40,21 @@ export function getMessageTrendChartOptions(
|
||||||
],
|
],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '上行消息',
|
name: '上行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
areaStyle: {
|
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: {
|
emphasis: {
|
||||||
focus: 'series',
|
focus: 'series',
|
||||||
|
|
@ -55,11 +65,21 @@ export function getMessageTrendChartOptions(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '下行消息',
|
name: '下行消息量',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
areaStyle: {
|
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: {
|
emphasis: {
|
||||||
focus: 'series',
|
focus: 'series',
|
||||||
|
|
@ -84,8 +104,8 @@ export function getDeviceStateGaugeChartOptions(
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'gauge',
|
type: 'gauge',
|
||||||
startAngle: 225,
|
startAngle: 360,
|
||||||
endAngle: -45,
|
endAngle: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
max,
|
max,
|
||||||
center: ['50%', '50%'],
|
center: ['50%', '50%'],
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
import type { IotStatisticsApi } from '#/api/iot/statistics';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
@ -123,10 +123,6 @@ async function renderChartWhenReady() {
|
||||||
initChart();
|
initChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件挂载时查询数据 */
|
|
||||||
onMounted(() => {
|
|
||||||
fetchMessageData();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ onMounted(() => {
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
placeholder="请选择产品"
|
placeholder="请选择产品"
|
||||||
clearable
|
clearable
|
||||||
|
filterable
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,11 @@ if (!props.isStructDataSpecs && !props.isParams) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElFormItem label="数据类型">
|
<ElFormItem
|
||||||
|
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
|
||||||
|
label="数据类型"
|
||||||
|
prop="property.dataType"
|
||||||
|
>
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="property.dataType"
|
v-model="property.dataType"
|
||||||
placeholder="请选择数据类型"
|
placeholder="请选择数据类型"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue