Commit 90f6f285169662b326b805e70be3af1e16f07377

Authored by fengwotao
2 parents 95a41d9f d8ac959b

Merge branch 'main_dev' into fengtao

Showing 85 changed files with 2490 additions and 1644 deletions

Too many changes to show.

To preserve performance only 85 of 151 files are displayed.

... ... @@ -6,6 +6,7 @@
6 6 ],
7 7 "cSpell.words": [
8 8 "ACKS",
  9 + "Cascader",
9 10 "clazz",
10 11 "Cmds",
11 12 "COAP",
... ...
1 1 import { DeviceProfileModel } from '../../device/model/deviceModel';
2   -import { HistoryData } from './model';
  2 +import { DeviceAttributeItemType, HistoryData } from './model';
3 3 import { defHttp } from '/@/utils/http/axios';
4 4 import { isString } from '/@/utils/is';
5 5 import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
... ... @@ -17,7 +17,7 @@ export const getDeviceHistoryInfo = (params: Recordable, orderBy = OrderByEnum.D
17 17 return defHttp.get<HistoryData>(
18 18 {
19 19 url: `/plugins/telemetry/DEVICE/${params.entityId}/values/timeseries`,
20   - params: { orderBy, ...params, entityId: null },
  20 + params: { ...params, entityId: null, orderBy },
21 21 },
22 22 {
23 23 joinPrefix: false,
... ... @@ -38,7 +38,7 @@ export const getDeviceDataKeys = (id: string) => {
38 38 };
39 39 // 获取设备状态,在线 or 离线时间
40 40 export const getDeviceActiveTime = (entityId: string) => {
41   - return defHttp.get(
  41 + return defHttp.get<DeviceAttributeItemType[]>(
42 42 {
43 43 url: `/plugins/telemetry/DEVICE/${entityId}/values/attributes?keys=active`,
44 44 },
... ...
1 1 export interface HistoryData {
2 2 [key: string]: { ts: number; value: string }[];
3 3 }
  4 +
  5 +export interface DeviceAttributeItemType {
  6 + key: string;
  7 + value: boolean;
  8 + lastUpdateTs: number;
  9 +}
... ...
... ... @@ -13,7 +13,7 @@ import { ChildDeviceParams } from './model/deviceModel';
13 13 import { PaginationResult } from '/#/axios';
14 14 import { AlarmLogItem } from './model/deviceConfigModel';
15 15 import { omit } from 'lodash-es';
16   -import { CommandDeliveryWayEnum } from '/@/enums/toolEnum';
  16 +import { CommandDeliveryWayEnum } from '/@/enums/deviceEnum';
17 17 enum DeviceManagerApi {
18 18 /**
19 19 * 设备URL
... ... @@ -137,7 +137,7 @@ export const createOrEditDevice = (data) => {
137 137
138 138 // 查询设备详情
139 139 export const getDeviceDetail = (id: string) => {
140   - return defHttp.get({
  140 + return defHttp.get<DeviceRecord>({
141 141 url: DeviceManagerApi.DEVICE_URL + `/${id}`,
142 142 });
143 143 };
... ...
1 1 import { BasicPageParams } from '/@/api/model/baseModel';
  2 +import { CommandTypeEnum, RPCCommandMethodEnum } from '/@/enums/deviceEnum';
2 3
3 4 export type TDeviceConfigPageQueryParam = BasicPageParams & TDeviceConfigParams;
4 5
... ... @@ -184,3 +185,10 @@ export interface Configuration {
184 185 export interface TransportConfiguration {
185 186 type: string;
186 187 }
  188 +
  189 +export interface RpcCommandType {
  190 + additionalInfo: { cmdType: CommandTypeEnum };
  191 + method: RPCCommandMethodEnum;
  192 + params: Recordable | string | number;
  193 + persistent: boolean;
  194 +}
... ...
1 1 import { DataTypeEnum } from '/@/enums/objectModelEnum';
2   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
  2 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
3 3
4 4 export interface Specs {
5   - min: string;
6   - max: string;
  5 + min: number;
  6 + max: number;
7 7 unit: string;
8 8 unitName: string;
9 9
10 10 dataType?: string;
11 11 name?: string;
12   - value?: string;
13   - step: string;
14   - length: string;
  12 + value?: string | number;
  13 + step: number;
  14 + length: number;
15 15 boolOpen: string;
16 16 boolClose: string;
17 17 valueRange?: {
18   - min: string;
19   - max: string;
  18 + min: number;
  19 + max: number;
20 20 };
21 21 }
22 22
... ... @@ -26,17 +26,23 @@ export interface DataType {
26 26 specsList?: Specs[];
27 27 }
28 28
  29 +export interface ExtensionDesc {
  30 + zoomFactor?: number;
  31 + actionType?: string;
  32 + dataType: string;
  33 + registerAddress: number;
  34 +}
  35 +
29 36 export interface StructJSON {
30   - functionName?: string;
  37 + functionName: string;
31 38 identifier: string;
32   - remark?: string;
33 39 dataType?: DataType;
  40 + remark?: string;
34 41 serviceCommand?: string;
35   - accessMode?: string;
36 42 }
37 43
38 44 export interface FunctionJson {
39   - dataType?: DataType | DataType[];
  45 + dataType?: DataType;
40 46 inputData?: StructJSON[];
41 47 outputData?: StructJSON[];
42 48 serviceCommand?: string;
... ... @@ -46,19 +52,19 @@ export interface ModelOfMatterParams {
46 52 deviceProfileId?: string;
47 53 functionJson: FunctionJson;
48 54 functionName: string;
49   - functionType: FunctionType;
  55 + functionType: FunctionTypeEnum;
50 56 identifier: string;
51   - remark: string;
  57 + remark?: string;
52 58 id?: string;
53 59 categoryId?: string;
54 60 callType?: string;
55 61 eventType?: string;
56 62 accessMode?: string;
57   - extensionDesc?: Recordable;
  63 + extensionDesc?: ExtensionDesc;
58 64 }
59 65
60 66 export interface GetModelTslParams {
61   - functionType: FunctionType;
  67 + functionType: FunctionTypeEnum;
62 68 deviceProfileId: string;
63 69 ifShowClass?: string | Boolean;
64 70 }
... ... @@ -85,3 +91,26 @@ export interface ModelOfMatterItemRecordType {
85 91 status: number;
86 92 deviceProfileId: string;
87 93 }
  94 +
  95 +export interface BatchGetObjectModelItemType {
  96 + id: string;
  97 + name: string;
  98 + transportType: string;
  99 + deviceType: string;
  100 + tsl: Tsl[];
  101 +}
  102 +
  103 +export interface Tsl {
  104 + functionName: string;
  105 + identifier: string;
  106 + functionType: string;
  107 + accessMode?: string;
  108 + specs?: {
  109 + dataType: DataType;
  110 + };
  111 + extensionDesc: ExtensionDesc;
  112 + eventType?: string;
  113 + outputData?: StructJSON[];
  114 + callType?: string;
  115 + inputData?: StructJSON[];
  116 +}
... ...
1 1 import { BasicPageParams } from '../model/baseModel';
2 2 import {
  3 + BatchGetObjectModelItemType,
3 4 GetModelTslParams,
4 5 ImportModelOfMatterType,
5 6 ModelOfMatterItemRecordType,
6 7 ModelOfMatterParams,
7 8 } from './model/modelOfMatterModel';
  9 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
8 10 import { defHttp } from '/@/utils/http/axios';
9   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
10 11
11 12 enum ModelOfMatter {
12 13 CREATE = '/things_model',
... ... @@ -26,12 +27,14 @@ enum ModelOfMatter {
26 27
27 28 IMPORT_CSV = '/things_model/csvImport',
28 29 EXCEL_EXPORT = '/things_model/downloadTemplate',
  30 +
  31 + BATCH_GET_TSL_BY_DEVICE_PROFILES = '/things_model/batch/get_tsl',
29 32 }
30 33
31 34 export const getModelList = (
32 35 params: BasicPageParams & {
33 36 deviceProfileId?: string;
34   - functionTyp?: FunctionType;
  37 + functionTyp?: FunctionTypeEnum;
35 38 nameOrIdentifier?: string;
36 39 selectType?: string | undefined;
37 40 id?: string;
... ... @@ -181,3 +184,16 @@ export const excelExport = () => {
181 184 responseType: 'blob',
182 185 });
183 186 };
  187 +
  188 +export const batchGetObjectModel = ({
  189 + deviceProfileIds,
  190 + functionTypeEnum = 'all',
  191 +}: {
  192 + deviceProfileIds: string[];
  193 + functionTypeEnum?: FunctionTypeEnum | 'all';
  194 +}) => {
  195 + return defHttp.post<BatchGetObjectModelItemType[]>({
  196 + url: ModelOfMatter.BATCH_GET_TSL_BY_DEVICE_PROFILES,
  197 + data: { deviceProfileIds, functionTypeEnum },
  198 + });
  199 +};
... ...
... ... @@ -59,7 +59,7 @@ export interface GenModbusCommandType {
59 59 crc: string;
60 60 deviceCode: string;
61 61 method: string;
62   - registerAddress: string;
  62 + registerAddress: number;
63 63 registerNumber?: number;
64 64 registerValues?: number[];
65 65 }
... ...
... ... @@ -2,7 +2,7 @@ import { withInstall } from '/@/utils';
2 2 // @ts-ignore
3 3 import codeEditor from './src/CodeEditor.vue';
4 4 import jsonPreview from './src/json-preview/JsonPreview.vue';
5   -export { JSONEditor } from './src/JSONEditor';
  5 +export { JSONEditor, JSONEditorValidator, parseStringToJSON } from './src/JSONEditor';
6 6
7 7 export const CodeEditor = withInstall(codeEditor);
8 8 export const JsonPreview = withInstall(jsonPreview);
... ...
... ... @@ -11,18 +11,18 @@ export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.v
11 11 export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
12 12 export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
13 13 export { default as ApiUpload } from './src/components/ApiUpload.vue';
  14 +export { default as ApiCascader } from './src/components/ApiCascader.vue';
14 15
15   -export { default as StructForm } from './src/externalCompns/components/StructForm/StructForm.vue';
16 16 export { default as JavaScriptFunctionEditor } from './src/components/JavaScriptFunctionEditor.vue';
17 17
18   -export { ThingsModelForm } from './src/externalCompns/components/ThingsModelForm';
  18 +export { ThingsModelForm } from './src/components/ThingsModelForm';
19 19
20 20 //注册自定义组件
21 21 export {
22 22 JEasyCron,
23 23 JEasyCronInner,
24 24 JEasyCronModal,
25   -} from '/@/components/Form/src/externalCompns/components/JEasyCron';
  25 +} from '/@/components/Form/src/components/JEasyCron';
26 26 // Jeecg自定义校验
27   -export { JCronValidator } from '/@/components/Form/src/externalCompns/components/JEasyCron';
  27 +export { JCronValidator } from '/@/components/Form/src/components/JEasyCron';
28 28 // export { BasicForm };
... ...
... ... @@ -29,20 +29,18 @@ import { IconPicker } from '/@/components/Icon';
29 29 import { CountdownInput } from '/@/components/CountDown';
30 30 import ApiRadioGroup from './components/ApiRadioGroup.vue';
31 31 //自定义组件
32   -import JAddInput from './externalCompns/components/JAddInput.vue';
33   -import { JEasyCron } from './externalCompns/components/JEasyCron';
  32 +import JAddInput from './components/JAddInput.vue';
  33 +import { JEasyCron } from './components/JEasyCron';
34 34 import ColorPicker from './components/ColorPicker.vue';
35 35 import IconDrawer from './components/IconDrawer.vue';
36 36 import ApiUpload from './components/ApiUpload.vue';
37 37 import ApiSearchSelect from './components/ApiSearchSelect.vue';
38   -import CustomMinMaxInput from './externalCompns/components/CustomMinMaxInput.vue';
39   -import StructForm from './externalCompns/components/StructForm/StructForm.vue';
  38 +import CustomMinMaxInput from './components/CustomMinMaxInput.vue';
40 39 import ApiSelectScrollLoad from './components/ApiSelectScrollLoad.vue';
41 40 import InputGroup from './components/InputGroup.vue';
42 41 import RegisterAddressInput from '/@/views/task/center/components/PollCommandInput/RegisterAddressInput.vue';
43   -import ExtendDesc from '/@/components/Form/src/externalCompns/components/ExtendDesc/index.vue';
44   -import DeviceProfileForm from '/@/components/Form/src/externalCompns/components/DeviceProfileForm/index.vue';
45   -import EnumList from './externalCompns/components/StructForm/EnumList.vue';
  42 +import DeviceProfileForm from '/@/components/Form/src/components/DeviceProfileForm/index.vue';
  43 +import { Segmented } from './components/Segmented';
46 44
47 45 const componentMap = new Map<ComponentType, Component>();
48 46
... ... @@ -76,6 +74,7 @@ componentMap.set('TimePicker', TimePicker);
76 74 componentMap.set('StrengthMeter', StrengthMeter);
77 75 componentMap.set('IconPicker', IconPicker);
78 76 componentMap.set('InputCountDown', CountdownInput);
  77 +componentMap.set('Segmented', Segmented);
79 78
80 79 componentMap.set('Upload', BasicUpload);
81 80 //注册自定义组件
... ... @@ -86,12 +85,9 @@ componentMap.set('IconDrawer', IconDrawer);
86 85 componentMap.set('ApiUpload', ApiUpload);
87 86 componentMap.set('ApiSearchSelect', ApiSearchSelect);
88 87 componentMap.set('CustomMinMaxInput', CustomMinMaxInput);
89   -componentMap.set('StructForm', StructForm);
90 88 componentMap.set('ApiSelectScrollLoad', ApiSelectScrollLoad);
91 89 componentMap.set('InputGroup', InputGroup);
92 90 componentMap.set('RegisterAddressInput', RegisterAddressInput);
93   -componentMap.set('ExtendDesc', ExtendDesc);
94   -componentMap.set('EnumList', EnumList);
95 91 componentMap.set('DeviceProfileForm', DeviceProfileForm);
96 92
97 93 export function add(compName: ComponentType, component: Component) {
... ...
  1 +<script setup lang="ts">
  2 + import { Cascader } from 'ant-design-vue';
  3 + import { LoadingOutlined } from '@ant-design/icons-vue';
  4 + import { CascaderOptionType } from 'ant-design-vue/lib/cascader';
  5 + import { get } from 'lodash';
  6 + import { ref, unref, watch, watchEffect } from 'vue';
  7 + import { useRuleFormItem } from '/@/hooks/component/useFormItem';
  8 + import { useI18n } from '/@/hooks/web/useI18n';
  9 + import { isFunction } from '/@/utils/is';
  10 +
  11 + const { t } = useI18n();
  12 +
  13 + const props = withDefaults(
  14 + defineProps<{
  15 + value?: (string | number)[];
  16 + immediate?: boolean;
  17 + params?: any;
  18 + api?: Fn<any, Promise<any>>;
  19 + options?: CascaderOptionType[];
  20 + resultField?: string;
  21 + fieldNames?: Record<'label' | 'value' | 'children', string>;
  22 + }>(),
  23 + {
  24 + value: () => [],
  25 + immediate: true,
  26 + fieldNames: () => ({ label: 'label', value: 'value', children: 'children' }),
  27 + }
  28 + );
  29 +
  30 + const emit = defineEmits(['change', 'options-change', 'update:value']);
  31 +
  32 + const options = ref<CascaderOptionType[]>([]);
  33 + const loading = ref(false);
  34 + const isFirstLoad = ref(true);
  35 + const emitData = ref<any[]>([]);
  36 +
  37 + // Embedded in the form, just use the hook binding to perform form verification
  38 + const [state] = useRuleFormItem(props, 'value', 'update:value', emitData);
  39 +
  40 + watchEffect(() => {
  41 + props.immediate && fetch();
  42 + });
  43 +
  44 + watch(
  45 + () => props.params,
  46 + () => {
  47 + !unref(isFirstLoad) && fetch();
  48 + },
  49 + { deep: true }
  50 + );
  51 +
  52 + async function fetch() {
  53 + const api = props.api;
  54 + if (!api || !isFunction(api)) return;
  55 + options.value = [];
  56 + try {
  57 + loading.value = true;
  58 + const res = await api(props.params);
  59 + if (Array.isArray(res)) {
  60 + options.value = res;
  61 + emitChange();
  62 + return;
  63 + }
  64 + if (props.resultField) {
  65 + options.value = get(res, props.resultField) || [];
  66 + }
  67 + emitChange();
  68 + } catch (error) {
  69 + console.warn(error);
  70 + } finally {
  71 + loading.value = false;
  72 + }
  73 + }
  74 +
  75 + async function handleFetch(open) {
  76 + if (open) {
  77 + await fetch();
  78 + }
  79 + }
  80 +
  81 + function emitChange() {
  82 + emit('options-change', options);
  83 + }
  84 +
  85 + function handleChange(_, ...args) {
  86 + emitData.value = args;
  87 + }
  88 +</script>
  89 +
  90 +<template>
  91 + <Cascader
  92 + v-bind="$attrs"
  93 + @change="handleChange"
  94 + :fieldNames="fieldNames"
  95 + :options="options"
  96 + v-model:value="state"
  97 + @popupVisibleChange="handleFetch"
  98 + >
  99 + <template #notFoundContent v-if="loading">
  100 + <span>
  101 + <LoadingOutlined spin class="mr-1" />
  102 + {{ t('component.form.apiSelectNotFound') }}
  103 + </span>
  104 + </template>
  105 + </Cascader>
  106 +</template>
... ...
src/components/Form/src/components/CustomMinMaxInput.vue renamed from src/components/Form/src/externalCompns/components/CustomMinMaxInput.vue
... ... @@ -4,17 +4,13 @@
4 4 placeholder="最小值"
5 5 :disabled="$props.disabled"
6 6 :value="getValue.min!"
7   - style="width: 38%"
8 7 @change="(value) => emitChange(value, 'min')"
9 8 />
10   - <span style="width: 8px"></span>
11   - <span>~</span>
12   - <span style="width: 8px"></span>
  9 + <div class="text-center flex-shrink-0 w-6">~</div>
13 10 <InputNumber
14 11 placeholder="最大值"
15 12 :disabled="$props.disabled"
16 13 :value="getValue.max!"
17   - style="width: 38%"
18 14 @change="(value) => emitChange(value, 'max')"
19 15 />
20 16 </div>
... ...
src/components/Form/src/components/DeviceProfileForm/index.vue renamed from src/components/Form/src/externalCompns/components/DeviceProfileForm/index.vue
src/components/Form/src/components/JAddInput.vue renamed from src/components/Form/src/externalCompns/components/JAddInput.vue
src/components/Form/src/components/JEasyCron/EasyCronInner.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/EasyCronInner.vue
src/components/Form/src/components/JEasyCron/EasyCronInput.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/EasyCronInput.vue
src/components/Form/src/components/JEasyCron/EasyCronModal.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/EasyCronModal.vue
src/components/Form/src/components/JEasyCron/LICENSE renamed from src/components/Form/src/externalCompns/components/JEasyCron/LICENSE
src/components/Form/src/components/JEasyCron/easy.cron.data.ts renamed from src/components/Form/src/externalCompns/components/JEasyCron/easy.cron.data.ts
src/components/Form/src/components/JEasyCron/easy.cron.inner.less renamed from src/components/Form/src/externalCompns/components/JEasyCron/easy.cron.inner.less
src/components/Form/src/components/JEasyCron/easy.cron.input.less renamed from src/components/Form/src/externalCompns/components/JEasyCron/easy.cron.input.less
src/components/Form/src/components/JEasyCron/index.ts renamed from src/components/Form/src/externalCompns/components/JEasyCron/index.ts
src/components/Form/src/components/JEasyCron/tabs/DayUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/DayUI.vue
src/components/Form/src/components/JEasyCron/tabs/HourUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/HourUI.vue
src/components/Form/src/components/JEasyCron/tabs/MinuteUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/MinuteUI.vue
src/components/Form/src/components/JEasyCron/tabs/MonthUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/MonthUI.vue
src/components/Form/src/components/JEasyCron/tabs/SecondUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/SecondUI.vue
src/components/Form/src/components/JEasyCron/tabs/WeekUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/WeekUI.vue
src/components/Form/src/components/JEasyCron/tabs/YearUI.vue renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/YearUI.vue
src/components/Form/src/components/JEasyCron/tabs/useTabMixin.ts renamed from src/components/Form/src/externalCompns/components/JEasyCron/tabs/useTabMixin.ts
src/components/Form/src/components/JEasyCron/validator.ts renamed from src/components/Form/src/externalCompns/components/JEasyCron/validator.ts
  1 +<script setup lang="ts">
  2 + import {
  3 + computed,
  4 + CSSProperties,
  5 + nextTick,
  6 + onBeforeUnmount,
  7 + ref,
  8 + TransitionProps,
  9 + unref,
  10 + watch,
  11 + } from 'vue';
  12 + import { addClass } from './styleClass';
  13 + import { ThumbReact } from './type';
  14 + import { removeClass } from '/@/utils/domUtils';
  15 +
  16 + const calcThumbStyle = (targetElement: HTMLElement | null | undefined): ThumbReact =>
  17 + targetElement
  18 + ? {
  19 + left: targetElement.offsetLeft,
  20 + right:
  21 + (targetElement.parentElement!.clientWidth as number) -
  22 + targetElement.clientWidth -
  23 + targetElement.offsetLeft,
  24 + width: targetElement.clientWidth,
  25 + }
  26 + : null;
  27 +
  28 + const toPX = (value?: number) => (value !== undefined ? `${value}px` : undefined);
  29 +
  30 + const props = defineProps<{
  31 + value?: string | number;
  32 + getValueIndex: (value: string | number) => number;
  33 + containerRef?: HTMLDivElement;
  34 + prefixCls: string;
  35 + direction?: 'rtl' | 'ltr';
  36 + motionName: string;
  37 + }>();
  38 +
  39 + const emits = defineEmits(['motionStart', 'motionEnd']);
  40 +
  41 + const thumbRef = ref<HTMLDivElement>();
  42 +
  43 + const findValueElement = (val: string | number) => {
  44 + const { getValueIndex, prefixCls } = props;
  45 + const index = getValueIndex(val);
  46 + const ele = unref(props.containerRef)?.querySelectorAll<HTMLDivElement>(`.${prefixCls}-item`)?.[
  47 + index
  48 + ];
  49 +
  50 + return ele?.offsetParent && ele;
  51 + };
  52 +
  53 + const prevStyle = ref<ThumbReact>(null);
  54 + const nextStyle = ref<ThumbReact>(null);
  55 +
  56 + watch(
  57 + () => props.value,
  58 + (value, prevValue) => {
  59 + const prev = findValueElement(prevValue as number);
  60 + const next = findValueElement(value as number);
  61 +
  62 + const calcPrevStyle = calcThumbStyle(prev);
  63 + const calcNextStyle = calcThumbStyle(next);
  64 +
  65 + prevStyle.value = calcPrevStyle;
  66 + nextStyle.value = calcNextStyle;
  67 +
  68 + prev && next ? emits('motionStart') : emits('motionEnd');
  69 + },
  70 + { flush: 'post' }
  71 + );
  72 +
  73 + const thumbStart = computed(() => {
  74 + const { direction } = props;
  75 +
  76 + return direction === 'rtl'
  77 + ? toPX(-(prevStyle.value?.right as number))
  78 + : toPX(prevStyle.value?.left as number);
  79 + });
  80 +
  81 + const thumbActive = computed(() => {
  82 + const { direction } = props;
  83 +
  84 + return direction === 'rtl'
  85 + ? toPX(-(nextStyle.value?.right as number))
  86 + : toPX(nextStyle.value?.left as number);
  87 + });
  88 +
  89 + let timeId: any;
  90 + const onAppearStart: TransitionProps['onBeforeEnter'] = (el: HTMLDivElement) => {
  91 + clearTimeout(timeId);
  92 + nextTick(() => {
  93 + if (el) {
  94 + el.style.transform = `translateX(var(--thumb-start-left))`;
  95 + el.style.width = `var(--thumb-start-width)`;
  96 + }
  97 + });
  98 + };
  99 +
  100 + const onAppearActive: TransitionProps['onEnter'] = (el: HTMLDivElement) => {
  101 + timeId = setTimeout(() => {
  102 + if (el) {
  103 + addClass(el, `${props.motionName}-appear-active`);
  104 + el.style.transform = `translateX(var(--thumb-active-left))`;
  105 + el.style.width = `var(--thumb-active-width)`;
  106 + }
  107 + });
  108 + };
  109 +
  110 + const onAppearEnd: TransitionProps['onAfterEnter'] = (el: HTMLDivElement) => {
  111 + prevStyle.value = null;
  112 + nextStyle.value = null;
  113 + if (el) {
  114 + el.style.transform = '';
  115 + el.style.width = '';
  116 + removeClass(el, `${props.motionName}-appear-active`);
  117 + }
  118 + emits('motionEnd');
  119 + };
  120 +
  121 + const mergedStyle = computed(
  122 + () =>
  123 + ({
  124 + '--thumb-start-left': thumbStart.value,
  125 + '--thumb-start-width': toPX(prevStyle.value?.width),
  126 + '--thumb-active-left': thumbActive.value,
  127 + '--thumb-active-width': toPX(nextStyle.value?.width),
  128 + } as CSSProperties)
  129 + );
  130 +
  131 + onBeforeUnmount(() => {
  132 + clearTimeout(timeId);
  133 + });
  134 +
  135 + const motionProps = computed(() => {
  136 + return {
  137 + ref: thumbRef,
  138 + style: mergedStyle.value,
  139 + class: `${props.prefixCls}-thumb`,
  140 + };
  141 + });
  142 +</script>
  143 +
  144 +<template>
  145 + <Transition
  146 + appear
  147 + @before-enter="onAppearStart"
  148 + @enter="onAppearActive"
  149 + @after-enter="onAppearEnd"
  150 + >
  151 + <div v-if="prevStyle && nextStyle" v-bind="motionProps"></div>
  152 + </Transition>
  153 +</template>
... ...
  1 +<script setup lang="ts">
  2 + import { computed, ref } from 'vue';
  3 + import MotionThumb from './MotionThumb.vue';
  4 + import { SegmentedBaseOption, SegmentedOption } from './type';
  5 +
  6 + const props = withDefaults(
  7 + defineProps<{
  8 + block?: boolean;
  9 + disabled?: boolean;
  10 + options?: string[] | number[] | SegmentedOption[];
  11 + value?: string | number;
  12 + motionName?: string;
  13 + }>(),
  14 + {
  15 + options: () => [],
  16 + motionName: 'thumb-motion',
  17 + }
  18 + );
  19 +
  20 + const slots = defineSlots<{ label?(props: SegmentedBaseOption): any }>();
  21 +
  22 + const prefixCls = 'segmented';
  23 +
  24 + const thumbShow = ref(false);
  25 +
  26 + const rootRef = ref<HTMLDivElement>();
  27 +
  28 + const getOptions = computed<SegmentedBaseOption[]>(() => {
  29 + const { options } = props;
  30 + return (options as SegmentedOption[]).map((item) => {
  31 + return {
  32 + value: item?.value || item,
  33 + disabled: item?.disabled || false,
  34 + payload: item?.payload || {},
  35 + title: item?.title || item,
  36 + className: item?.className || '',
  37 + } as SegmentedBaseOption;
  38 + });
  39 + });
  40 +
  41 + const emits = defineEmits(['change', 'update:value']);
  42 +
  43 + const handleChange = (value: string | number) => {
  44 + emits('update:value', value);
  45 + emits('change', value);
  46 + };
  47 +</script>
  48 +
  49 +<template>
  50 + <div :class="prefixCls" ref="rootRef">
  51 + <div :class="`${prefixCls}-group`">
  52 + <MotionThumb
  53 + :containerRef="rootRef"
  54 + :prefix-cls="prefixCls"
  55 + :value="value"
  56 + :motion-name="`${prefixCls}-${motionName}`"
  57 + direction="ltr"
  58 + :get-value-index="(val) => getOptions.findIndex((item) => item.value === val)"
  59 + @motion-start="thumbShow = true"
  60 + @motion-end="thumbShow = false"
  61 + />
  62 + <label
  63 + v-for="item in getOptions"
  64 + :key="item.value"
  65 + :class="[
  66 + `${prefixCls}-item`,
  67 + value === item.value && !thumbShow && `${prefixCls}-item-selected`,
  68 + (disabled || item.disabled) && `${prefixCls}-item-disabled`,
  69 + ]"
  70 + @click="!disabled && !item.disabled && handleChange(item.value)"
  71 + >
  72 + <input type="radio" class="absolute pointer-events-none w-0 h-0 opacity-0" />
  73 + <slot v-if="slots.label" name="label" v-bind="item"></slot>
  74 + <template v-else>
  75 + <div :class="`${prefixCls}-item-label`"> {{ item.title }}</div>
  76 + </template>
  77 + </label>
  78 + </div>
  79 + </div>
  80 +</template>
  81 +
  82 +<style lang="less">
  83 + .segmented {
  84 + box-sizing: border-box;
  85 + padding: 2px;
  86 + color: #000000a6;
  87 + font-size: 14px;
  88 + display: inline-block;
  89 + border-radius: 4px;
  90 + background-color: #f5f5f5;
  91 + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  92 +
  93 + &-block {
  94 + display: flex;
  95 + }
  96 +
  97 + &-block.segmented-item {
  98 + flex: 1;
  99 + }
  100 +
  101 + &-group {
  102 + position: relative;
  103 + display: flex;
  104 + align-items: stretch;
  105 + justify-content: flex-start;
  106 + width: 100%;
  107 + border-radius: inherit;
  108 + transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  109 + }
  110 +
  111 + &-item {
  112 + position: relative;
  113 + cursor: pointer;
  114 + border-radius: inherit;
  115 + text-align: center;
  116 + transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  117 +
  118 + &-label {
  119 + min-height: 28px;
  120 + line-height: 28px;
  121 + padding: 0 11px;
  122 + overflow: hidden;
  123 + white-space: nowrap;
  124 + text-overflow: ellipsis;
  125 + }
  126 +
  127 + &::after {
  128 + content: '';
  129 + position: absolute;
  130 + width: 100%;
  131 + height: 100%;
  132 + top: 0;
  133 + inset-inline-start: 0;
  134 + border-radius: inherit;
  135 + transition: background-color 0.2s;
  136 + }
  137 +
  138 + &:hover:not(.segmented-item-selected):not(.segmented-item-disabled) {
  139 + color: #000000e0;
  140 +
  141 + &::after {
  142 + background-color: rgba(0, 0, 0, 0.06);
  143 + }
  144 + }
  145 +
  146 + &-disabled {
  147 + cursor: not-allowed;
  148 + color: rgba(0, 0, 0, 0.25);
  149 + }
  150 +
  151 + &-selected {
  152 + background-color: #fff;
  153 +
  154 + ::after {
  155 + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02),
  156 + 0 2px 4px 0 rgba(0, 0, 0, 0.02);
  157 + }
  158 + }
  159 +
  160 + &-selected:not(.segmented-item-disabled) {
  161 + color: rgba(0, 0, 0, 0.88);
  162 + }
  163 + }
  164 +
  165 + &-thumb {
  166 + position: absolute;
  167 + inset-block-start: 0;
  168 + inset-inline-start: 0;
  169 + width: 0;
  170 + height: 100%;
  171 + background-color: #fff;
  172 +
  173 + &-motion-appear-active {
  174 + transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
  175 + width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  176 + will-change: transform, width;
  177 + }
  178 +
  179 + & ~ .segmented-item:not(.segmented-item-selected):not(.segmented-item-disabled)::after {
  180 + background-color: transparent;
  181 + }
  182 + }
  183 + }
  184 +</style>
... ...
  1 +export { default as Segmented } from './Segmented.vue';
... ...
  1 +export function hasClass(node: HTMLElement, className: string) {
  2 + if (node.classList) {
  3 + return node.classList.contains(className);
  4 + }
  5 + const originClass = node.className;
  6 + return ` ${originClass} `.indexOf(` ${className} `) > -1;
  7 +}
  8 +
  9 +export function addClass(node: HTMLElement, className: string) {
  10 + if (node.classList) {
  11 + node.classList.add(className);
  12 + } else {
  13 + if (!hasClass(node, className)) {
  14 + node.className = `${node.className} ${className}`;
  15 + }
  16 + }
  17 +}
... ...
  1 +export interface SegmentedBaseOption {
  2 + value: string | number;
  3 + disabled?: boolean;
  4 + payload?: any;
  5 + title?: string;
  6 + className?: string;
  7 +}
  8 +
  9 +export interface SegmentedOption extends SegmentedBaseOption {
  10 + label?: VueNode | ((option: SegmentedBaseOption) => VueNode);
  11 +}
  12 +
  13 +export type ThumbReact = {
  14 + left: number;
  15 + right: number;
  16 + width: number;
  17 +} | null;
... ...
src/components/Form/src/components/ThingsModelForm/StructPreview.vue renamed from src/components/Form/src/externalCompns/components/ThingsModelForm/StructPreview.vue
src/components/Form/src/components/ThingsModelForm/config.ts renamed from src/components/Form/src/externalCompns/components/ThingsModelForm/config.ts
1   -import { FormSchema } from '../../../types/form';
  1 +import { FormSchema } from '/@/components/Form';
2 2 import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  3 +import { TransportTypeEnum } from '/@/enums/deviceEnum';
3 4 import { DataTypeEnum } from '/@/enums/objectModelEnum';
4   -import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  5 +import { validateTCPCustomCommand } from '.';
5 6
6 7 export const getFormSchemas = ({
7 8 structJSON: structJson,
... ... @@ -127,6 +128,7 @@ export const getFormSchemas = ({
127 128 label: functionName!,
128 129 component: 'Input',
129 130 changeEvent: 'update:value',
  131 + rules: [{ required: required, validator: () => Promise.resolve() }],
130 132 componentProps: () => {
131 133 return {
132 134 inputData: dataType?.specs || [],
... ... @@ -143,6 +145,7 @@ export const getFormSchemas = ({
143 145 component: 'Input',
144 146 required,
145 147 defaultValue: serviceCommand,
  148 + rules: [{ validator: validateTCPCustomCommand }],
146 149 componentProps: {
147 150 placeholder: `请输入服务命令`,
148 151 },
... ...
src/components/Form/src/components/ThingsModelForm/index.ts renamed from src/components/Form/src/externalCompns/components/ThingsModelForm/index.ts
src/components/Form/src/components/ThingsModelForm/index.vue renamed from src/components/Form/src/externalCompns/components/ThingsModelForm/index.vue
1 1 <script setup lang="ts">
  2 + import {
  3 + ComponentPublicInstance,
  4 + computed,
  5 + nextTick,
  6 + reactive,
  7 + ref,
  8 + toRaw,
  9 + unref,
  10 + watch,
  11 + } from 'vue';
2 12 import { Card } from 'ant-design-vue';
3   - import { ComponentPublicInstance, computed, nextTick, reactive, unref, watch } from 'vue';
4 13 import { getFormSchemas } from './config';
5 14 import { ThingsModelForm } from '.';
6   - import { DefineComponentsBasicExpose } from '/#/utils';
7 15 import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
8   - import { useForm } from '../../../hooks/useForm';
  16 + import { useForm } from '../../hooks/useForm';
9 17 import { DataTypeEnum } from '/@/enums/objectModelEnum';
10   - import { BasicForm } from '/@/components/Form';
11   -
12   - const props = withDefaults(
13   - defineProps<{
14   - value?: Recordable;
15   - inputData?: StructJSON[];
16   - required?: boolean;
17   - title?: string;
18   - transportType?: string;
19   - disabled?: boolean;
20   - identifier?: string;
21   - }>(),
22   - {
23   - inputData: () => [],
24   - required: true,
25   - }
26   - );
  18 + import { BasicForm, FormProps } from '/@/components/Form';
  19 + import { deepMerge } from '/@/utils';
  20 +
  21 + interface ThingsModelFormPropsType {
  22 + value?: Recordable;
  23 + inputData?: StructJSON[];
  24 + required?: boolean;
  25 + title?: string;
  26 + transportType?: string;
  27 + disabled?: boolean;
  28 + identifier?: string;
  29 + formProps?: FormProps;
  30 + }
  31 +
  32 + const props = withDefaults(defineProps<ThingsModelFormPropsType>(), {
  33 + inputData: () => [],
  34 + required: true,
  35 + formProps: () => ({}),
  36 + });
27 37
28 38 const thingsModelFormListElMap = reactive<
29 39 Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>
30 40 >({});
31 41
  42 + const propsRef = ref<Partial<ThingsModelFormPropsType>>({});
  43 +
  44 + const getProps = computed<ThingsModelFormPropsType>(
  45 + () => ({ ...props, ...unref(propsRef) } as ThingsModelFormPropsType)
  46 + );
  47 +
32 48 const [register, formActionType] = useForm({
33   - schemas: getFormSchemas({
34   - structJSON: props.inputData || [],
35   - required: props.required,
36   - transportType: props.transportType,
37   - }),
38   - showActionButtonGroup: false,
39 49 layout: 'inline',
40 50 labelWidth: 100,
  51 + ...toRaw(unref(getProps)),
  52 + schemas: getFormSchemasByProps(),
  53 + showActionButtonGroup: false,
41 54 });
42 55
43 56 const getStructFormItem = computed(() => {
44   - const { inputData } = props;
45   - return inputData.filter((item) => item.dataType?.type === DataTypeEnum.STRUCT);
  57 + const { inputData } = unref(getProps);
  58 + return (inputData || []).filter((item) => item.dataType?.type === DataTypeEnum.STRUCT);
46 59 });
47 60
48 61 const setFormElRef = (
... ... @@ -87,7 +100,7 @@
87 100 };
88 101
89 102 watch(
90   - () => props.value,
  103 + () => unref(getProps).value,
91 104 async (value) => {
92 105 await nextTick();
93 106 formActionType.resetFields();
... ... @@ -97,30 +110,36 @@
97 110 );
98 111
99 112 watch(
100   - () => [props.inputData, props.identifier],
  113 + () => [unref(getProps).inputData, unref(getProps).identifier],
101 114 (value) => {
102   - if (value && value.length) {
103   - const schemas = getFormSchemas({
104   - structJSON: props.inputData || [],
105   - required: props.required,
106   - transportType: props.transportType,
107   - });
108   - formActionType.setProps({ schemas });
109   - }
  115 + if (value && value.length) formActionType.setProps({ schemas: getFormSchemasByProps() });
110 116 }
111 117 );
112 118
113 119 watch(
114   - () => props.disabled,
  120 + () => unref(getProps).disabled,
115 121 (value) => {
116 122 formActionType.setProps({ disabled: value });
117 123 }
118 124 );
119 125
120   - defineExpose<DefineComponentsBasicExpose>({
  126 + function getFormSchemasByProps() {
  127 + return getFormSchemas({
  128 + structJSON: unref(getProps).inputData || [],
  129 + required: unref(getProps).required,
  130 + transportType: unref(getProps).transportType,
  131 + });
  132 + }
  133 +
  134 + function setProps(props: Partial<ThingsModelFormPropsType>) {
  135 + propsRef.value = deepMerge(unref(propsRef) || {}, props);
  136 + }
  137 +
  138 + defineExpose({
121 139 getFieldsValue,
122 140 setFieldsValue,
123 141 validate,
  142 + setProps,
124 143 });
125 144 </script>
126 145
... ... @@ -155,6 +174,10 @@
155 174 width: 100%;
156 175 }
157 176
  177 + :deep(.ant-col.ant-form-item-control) {
  178 + min-height: auto;
  179 + }
  180 +
158 181 :deep(.ant-input-number) {
159 182 width: 100%;
160 183 }
... ...
1   -<script lang="ts" setup>
2   - import { Card } from 'ant-design-vue';
3   - import { computed, nextTick, onMounted, onUpdated, unref, watch } from 'vue';
4   - import { BasicCreateFormParams } from './type';
5   - import { DynamicProps } from '/#/utils';
6   - import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
7   - import { BasicForm, FormProps, FormSchema, useForm } from '/@/components/Form';
8   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
9   - import { ReadAndWriteEnum } from '/@/enums/toolEnum';
10   -
11   - const props = withDefaults(
12   - defineProps<{
13   - inputData?: StructJSON[];
14   - formProps?: FormProps;
15   - value?: Recordable;
16   - required?: boolean;
17   - }>(),
18   - {
19   - inputData: () => [] as StructJSON[],
20   - formProps: () => ({}),
21   - value: () => ({}),
22   - }
23   - );
24   -
25   - const emit = defineEmits(['update:value']);
26   -
27   - const getCurrentKeys = computed(() => {
28   - const { inputData } = props;
29   - return (inputData || []).map((item) => item.identifier);
30   - });
31   -
32   - const [register, { setProps, getFieldsValue, setFieldsValue }] = useForm({
33   - schemas: [],
34   - showActionButtonGroup: false,
35   - layout: 'inline',
36   - labelWidth: 80,
37   - wrapperCol: { span: 12 },
38   - ...(props.formProps || ({} as unknown as Partial<DynamicProps<FormProps>>)),
39   - });
40   -
41   - const syncValue = (key: string, value: any) => {
42   - let record = getFieldsValue();
43   - record = { ...record, [key]: value };
44   - const setValues = {};
45   - Object.keys(record).forEach((key) => {
46   - const hasKey = unref(getCurrentKeys).includes(key);
47   - if (hasKey) {
48   - setValues[key] = record[key];
49   - }
50   - });
51   - emit('update:value', setValues);
52   - };
53   -
54   - const createInputNumber = ({
55   - identifier,
56   - functionName,
57   - specs,
58   - }: BasicCreateFormParams): FormSchema => {
59   - const { valueRange } = specs! as Partial<Specs>;
60   - const { max = 2147483647, min = -2147483648 } = valueRange || {};
61   - return {
62   - field: identifier,
63   - label: functionName,
64   - component: 'InputNumber',
65   - rules: [
66   - {
67   - required: props.required,
68   - message: `${functionName}是必填项`,
69   - },
70   - {
71   - type: 'number',
72   - trigger: 'change',
73   - validator: (_rule, value) => {
74   - const reg = /^[0-9]*$/;
75   - if (!reg.test(value)) return Promise.reject(`${functionName}不是一个有效的数字`);
76   - if (value < min || value > max) {
77   - return Promise.reject(`${functionName}取值范围在${min}~${max}之间`);
78   - }
79   - return Promise.resolve(value);
80   - },
81   - },
82   - ],
83   - componentProps: {
84   - max,
85   - min,
86   - // step: step,
87   - // formatter: (value: string) => value,
88   - // parser: (string: string) => {
89   - // if (dataType === DataTypeEnum.NUMBER_INT) {
90   - // return Number(Number(string).toFixed());
91   - // }
92   - // return Number(string);
93   - // },
94   - onChange: (value: string) => {
95   - syncValue(identifier, value);
96   - },
97   - },
98   - } as FormSchema;
99   - };
100   -
101   - const createInput = ({ identifier, functionName, specs }: BasicCreateFormParams): FormSchema => {
102   - const { length = 10240 } = specs! as Partial<Specs>;
103   - return {
104   - field: identifier,
105   - label: functionName,
106   - component: 'Input',
107   - rules: [
108   - {
109   - required: props.required,
110   - message: `${functionName}是必填项`,
111   - },
112   - {
113   - type: 'string',
114   - trigger: 'change',
115   - validator: (_rule, value) => {
116   - if (value.length > length) {
117   - return Promise.reject(`${functionName}数据长度应该小于${length}`);
118   - }
119   - return Promise.resolve(value);
120   - },
121   - },
122   - ],
123   - componentProps: {
124   - maxLength: length,
125   - onChange: (value: InputEvent) => {
126   - syncValue(identifier, (value.target as HTMLInputElement).value);
127   - },
128   - },
129   - } as FormSchema;
130   - };
131   -
132   - const createSelect = ({ identifier, functionName, specs }: BasicCreateFormParams): FormSchema => {
133   - const { boolClose, boolOpen } = specs! as Partial<Specs>;
134   - return {
135   - field: identifier,
136   - label: functionName,
137   - component: 'Select',
138   - rules: [
139   - {
140   - required: props.required,
141   - message: `${functionName}是必填项`,
142   - type: 'number',
143   - },
144   - ],
145   - componentProps: {
146   - options: [
147   - { label: `${boolClose}-0`, value: 0 },
148   - { label: `${boolOpen}-1`, value: 1 },
149   - ],
150   - onChange: (value: string) => {
151   - syncValue(identifier, value);
152   - },
153   - },
154   - };
155   - };
156   -
157   - const createInputJson = ({ identifier, functionName }: BasicCreateFormParams): FormSchema => {
158   - return {
159   - field: identifier,
160   - label: functionName,
161   - component: 'InputTextArea',
162   - rules: [
163   - {
164   - required: props.required,
165   - message: `${functionName}是必填项`,
166   - },
167   - ],
168   - componentProps: {
169   - onChange: (value: InputEvent) => {
170   - syncValue(identifier, (value.target as HTMLInputElement).value);
171   - },
172   - },
173   - };
174   - };
175   -
176   - const transformToFormSchema = (inputData: StructJSON[]) => {
177   - const schemas: FormSchema[] = [];
178   - for (const item of inputData) {
179   - const { dataType, identifier, functionName, accessMode } = item;
180   - if (accessMode === ReadAndWriteEnum.READ) {
181   - continue;
182   - }
183   - const { type, specs } = dataType! || {};
184   -
185   - const params: BasicCreateFormParams = {
186   - identifier: identifier!,
187   - functionName: functionName!,
188   - dataType: dataType! as unknown as DataTypeEnum,
189   - specs: specs as Partial<Specs>,
190   - };
191   - if (type === DataTypeEnum.NUMBER_INT || type === DataTypeEnum.NUMBER_DOUBLE) {
192   - schemas.push(createInputNumber(params));
193   - }
194   -
195   - if (type === DataTypeEnum.BOOL) {
196   - schemas.push(createSelect(params));
197   - }
198   -
199   - if (type === DataTypeEnum.STRING) {
200   - schemas.push(createInput(params));
201   - }
202   -
203   - if (type === DataTypeEnum.STRUCT) {
204   - schemas.push(createInputJson(params));
205   - }
206   - }
207   -
208   - return schemas;
209   - };
210   -
211   - const generateSchemas = (value: StructJSON[]) => {
212   - if (value && value.length) {
213   - const schemas = transformToFormSchema(value);
214   - setProps({ schemas });
215   - }
216   - };
217   -
218   - onMounted(() => {
219   - generateSchemas(props.inputData);
220   - });
221   -
222   - onUpdated(async () => {
223   - if (props.inputData && props.inputData.length) {
224   - await nextTick();
225   - setFieldsValue(props.value || {});
226   - }
227   - });
228   -
229   - watch(
230   - () => props.inputData,
231   - (value) => {
232   - generateSchemas(value);
233   - }
234   - );
235   -
236   - watch(
237   - () => props.formProps,
238   - (value) => {
239   - setProps(value);
240   - }
241   - );
242   -</script>
243   -
244   -<template>
245   - <Card bordered class="!border-dashed !rounded-lg !border-2px">
246   - <!-- <Alert class="!mb-4 w-32" message="服务参数配置" type="info" /> -->
247   - <BasicForm class="object-model-validate-form" @register="register" />
248   - </Card>
249   -</template>
250   -
251   -<style lang="less">
252   - .object-model-validate-form {
253   - .ant-input-number {
254   - width: 100% !important;
255   - }
256   - }
257   -</style>
1   -import { Specs } from '/@/api/device/model/modelOfMatterModel';
2   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
3   -
4   -export interface BasicCreateFormParams {
5   - identifier: string;
6   - functionName: string;
7   - dataType: DataTypeEnum;
8   - specs: Partial<Specs>;
9   -}
1   -<script lang="ts">
2   - export default {
3   - inheritAttrs: false,
4   - };
5   -</script>
6   -<script lang="ts" setup>
7   - import StructFormModel from './StructFormModel.vue';
8   - import { useModal } from '/@/components/Modal';
9   - import { PlusOutlined } from '@ant-design/icons-vue';
10   - import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
11   - import { computed, unref } from 'vue';
12   - import { buildUUID } from '/@/utils/uuid';
13   - import { Divider, Button } from 'ant-design-vue';
14   - import { OpenModalMode, OpenModalParams, StructRecord } from './type';
15   - import { cloneDeep } from 'lodash-es';
16   - import { isArray } from '/@/utils/is';
17   - import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
18   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
19   -
20   - const emit = defineEmits(['update:value']);
21   -
22   - const props = withDefaults(
23   - defineProps<{
24   - value: ModelOfMatterParams[];
25   - disabled: boolean;
26   - hasStructForm?: boolean;
27   - hiddenAccessMode?: boolean;
28   - }>(),
29   - {
30   - value: () => [],
31   - hiddenAccessMode: false,
32   - hasStructForm: false,
33   - }
34   - );
35   -
36   - const getValue = computed<StructRecord[]>(() => {
37   - const { value } = props;
38   -
39   - return (isArray(value) ? value : []).map((item) => {
40   - return {
41   - ...(item as StructRecord),
42   - ...((item as StructRecord).id ? {} : { id: buildUUID() }),
43   - };
44   - });
45   - });
46   -
47   - const [registerModal, { openModal }] = useModal();
48   -
49   - const handleCreateParams = () => {
50   - openModal(true, {
51   - mode: OpenModalMode.CREATE,
52   - } as OpenModalParams);
53   - };
54   -
55   - const handleUpdate = (value: StructRecord) => {
56   - openModal(true, {
57   - mode: OpenModalMode.UPDATE,
58   - record: {
59   - ...value,
60   - [FormField.HAS_STRUCT_FROM]: value?.dataType?.type === DataTypeEnum.STRUCT,
61   - },
62   - } as OpenModalParams);
63   - };
64   -
65   - const handleDelete = (value: StructRecord) => {
66   - const index = unref(getValue).findIndex((item) => item.id === value.id);
67   - const _value = cloneDeep(unref(getValue));
68   - _value.splice(index, 1);
69   - emit('update:value', _value);
70   - };
71   -
72   - const handleSaveStruct = (mode: OpenModalMode, value: StructRecord) => {
73   - const _value = cloneDeep(unref(getValue));
74   -
75   - if (mode === OpenModalMode.UPDATE) {
76   - const index = unref(getValue).findIndex((item) => item.id === value.id);
77   - ~index && _value.splice(index, 1, value);
78   - } else {
79   - _value.push(value);
80   - }
81   -
82   - emit('update:value', _value);
83   - };
84   -</script>
85   -
86   -<template>
87   - <section>
88   - <div class="text-blue-500 cursor-pointer">
89   - <section>
90   - <div
91   - class="flex bg-blue-50 mb-2 p-2 text-gray-500 justify-between items-center"
92   - v-for="item in getValue"
93   - :key="item.id"
94   - >
95   - <div>参数名称: {{ item.functionName }}</div>
96   - <div class="flex">
97   - <Button class="!p-0" type="link" @click="handleUpdate(item)">
98   - <span>{{ disabled ? '查看' : '编辑' }}</span>
99   - </Button>
100   - <Divider type="vertical" />
101   - <Button :disabled="disabled" class="!p-0" type="link" @click="handleDelete(item)">
102   - <span>删除</span>
103   - </Button>
104   - </div>
105   - </div>
106   - </section>
107   - <div :class="$props.disabled && 'text-gray-400'">
108   - <span class="mr-2">
109   - <PlusOutlined />
110   - </span>
111   - <span @click="!disabled && handleCreateParams()">增加参数</span>
112   - </div>
113   - </div>
114   - <StructFormModel
115   - :has-struct-form="hasStructForm!"
116   - :hidden-access-mode="hiddenAccessMode"
117   - :disabled="disabled"
118   - :value-list="getValue"
119   - @register="registerModal"
120   - @submit="handleSaveStruct"
121   - />
122   - </section>
123   -</template>
124   -
125   -<style lang="less" scoped></style>
1   -<script lang="ts">
2   - export default {
3   - inheritAttrs: false,
4   - };
5   -</script>
6   -<script lang="ts" setup>
7   - import { BasicForm, useForm } from '/@/components/Form';
8   - import { formSchemas } from './config';
9   - import { BasicModal, useModalInner } from '/@/components/Modal';
10   - import { OpenModalMode, OpenModalParams, StructRecord } from './type';
11   - import { computed, ref, unref } from 'vue';
12   - import { transfromToStructJSON } from './util';
13   - import { cloneDeep } from 'lodash-es';
14   - import { DataType, StructJSON } from '/@/api/device/model/modelOfMatterModel';
15   - import { isArray } from '/@/utils/is';
16   - import { useMessage } from '/@/hooks/web/useMessage';
17   - import EnumList from './EnumList.vue';
18   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
19   -
20   - const modalReceiveRecord = ref<OpenModalParams>({
21   - mode: OpenModalMode.CREATE,
22   - });
23   -
24   - const props = defineProps<{
25   - disabled: boolean;
26   - hasStructForm: boolean;
27   - valueList: StructRecord[];
28   - hiddenAccessMode: boolean;
29   - }>();
30   -
31   - const enumListRef = ref<InstanceType<typeof EnumList>>();
32   -
33   - const emit = defineEmits(['register', 'submit']);
34   -
35   - const { createMessage } = useMessage();
36   -
37   - const [register, { validate, setFieldsValue, setProps }] = useForm({
38   - labelWidth: 100,
39   - actionColOptions: {
40   - span: 14,
41   - },
42   - disabled: props.disabled,
43   - showResetButton: false,
44   - submitOnReset: false,
45   - showActionButtonGroup: false,
46   - });
47   -
48   - const getFormSchemas = computed(() => {
49   - const { hasStructForm, hiddenAccessMode } = props;
50   - return formSchemas({
51   - hasStructForm,
52   - hiddenAccessMode,
53   - });
54   - });
55   -
56   - const [registerModal, { closeModal }] = useModalInner((record: OpenModalParams) => {
57   - modalReceiveRecord.value = record;
58   - const data = record.record || {};
59   - const { dataType = {} } = data! as StructJSON;
60   - const { specs = {}, type, specsList } = dataType as DataType;
61   -
62   - if (record.record) {
63   - const value = {
64   - type,
65   - ...data,
66   - ...(isArray(specs) ? { specs } : { ...specs }),
67   - enumList: type === DataTypeEnum.ENUM ? specsList : [],
68   - };
69   -
70   - setFieldsValue(value);
71   - }
72   - setProps({ disabled: props.disabled });
73   - });
74   -
75   - const validateRepeat = (value: StructRecord, valueList: StructRecord[]) => {
76   - return valueList.filter((item) => item.identifier === value.identifier).length >= 1;
77   - };
78   -
79   - const handleSubmit = async () => {
80   - try {
81   - const _value = await validate();
82   - await unref(enumListRef)?.validate?.();
83   - let structJSON = transfromToStructJSON(_value, unref(enumListRef)?.getFieldsValue?.() || []);
84   - const value = {
85   - ...structJSON,
86   - ...(unref(modalReceiveRecord)?.record?.id
87   - ? { id: unref(modalReceiveRecord)?.record?.id }
88   - : {}),
89   - };
90   -
91   - if (
92   - unref(modalReceiveRecord).mode === OpenModalMode.CREATE &&
93   - validateRepeat(_value, props.valueList)
94   - ) {
95   - createMessage.error('存在一致的标识符');
96   - return;
97   - }
98   - emit('submit', unref(modalReceiveRecord).mode, cloneDeep(value));
99   - closeModal();
100   - } catch (error) {}
101   - };
102   -</script>
103   -
104   -<template>
105   - <BasicModal
106   - @register="registerModal"
107   - :title="modalReceiveRecord.mode === OpenModalMode.CREATE ? '创建参数' : '编辑参数'"
108   - :width="800"
109   - @ok="handleSubmit"
110   - destroy-on-close
111   - :show-ok-btn="!$props.disabled"
112   - >
113   - <BasicForm @register="register" :schemas="getFormSchemas">
114   - <template #EnumList="{ field, model }">
115   - <EnumList ref="enumListRef" :value="model[field]" :disabled="disabled" />
116   - </template>
117   - </BasicForm>
118   - </BasicModal>
119   -</template>
120   -
121   -<style lang="less" scoped></style>
1   -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
2   -import { findDictItemByCode } from '/@/api/system/dict';
3   -import { Rule } from '/@/components/Form';
4   -import { FormSchema } from '/@/components/Table';
5   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
6   -import { isNullOrUnDef } from '/@/utils/is';
7   -import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
8   -
9   -export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => {
10   - value = value || {};
11   - const { min, max } = value;
12   - if (min > max) {
13   - return Promise.reject('最大值小于最小值');
14   - }
15   - return Promise.resolve();
16   -};
17   -
18   -export const validateJSON = (_rule, value = [] as ModelOfMatterParams[], _callback) => {
19   - if (value.length) {
20   - return Promise.resolve();
21   - }
22   - return Promise.reject('JSON对象不能为空');
23   -};
24   -
25   -interface StructFormSchemasParmasType {
26   - hasStructForm: boolean;
27   - hiddenAccessMode: boolean;
28   - isTcp?: boolean;
29   -}
30   -
31   -//功能名称和标识符 输入框不能包含逗号
32   -const validateExcludeComma = (field: string, errorName: string): Rule[] => {
33   - return [
34   - {
35   - required: true,
36   - trigger: 'blur',
37   - validator: () => {
38   - const reg = /[,,]+/;
39   - if (reg.test(field)) {
40   - return Promise.reject(`${errorName}不能包含逗号`);
41   - }
42   - return Promise.resolve();
43   - },
44   - },
45   - ];
46   -};
47   -
48   -export const formSchemas = ({
49   - hasStructForm,
50   - hiddenAccessMode,
51   - isTcp = false,
52   -}: StructFormSchemasParmasType): FormSchema[] => {
53   - return [
54   - {
55   - field: FormField.FUNCTION_NAME,
56   - label: '功能名称',
57   - required: true,
58   - component: 'Input',
59   - colProps: {
60   - span: 18,
61   - },
62   - componentProps: {
63   - maxLength: 32,
64   - placeholder: '请输入功能名称',
65   - },
66   - dynamicRules: ({ values }) => {
67   - return [
68   - { required: true, message: '请输入功能名称' },
69   - ...validateExcludeComma(values[FormField.FUNCTION_NAME], '功能名称'),
70   - ];
71   - },
72   - },
73   - {
74   - field: FormField.IDENTIFIER,
75   - label: '标识符',
76   - required: true,
77   - component: 'Input',
78   - colProps: {
79   - span: 18,
80   - },
81   - componentProps: {
82   - maxLength: 128,
83   - placeholder: '请输入标识符',
84   - },
85   - dynamicRules: ({ values }) => {
86   - return [
87   - { required: true, message: '请输入标识符' },
88   - ...validateExcludeComma(values[FormField.IDENTIFIER], '标识符'),
89   - ];
90   - },
91   - },
92   - {
93   - field: FormField.HAS_STRUCT_FROM,
94   - label: '是否已存在结构体',
95   - component: 'Input',
96   - ifShow: false,
97   - },
98   - {
99   - field: FormField.TYPE,
100   - label: '数据类型',
101   - required: true,
102   - component: 'ApiSelect',
103   - colProps: {
104   - span: 9,
105   - },
106   - defaultValue: 'INT',
107   - componentProps: ({ formActionType }) => {
108   - const { setFieldsValue } = formActionType;
109   - return {
110   - placeholder: '请选择数据类型',
111   - api: async (params: Recordable) => {
112   - try {
113   - const record = await findDictItemByCode(params);
114   -
115   - if (isTcp) {
116   - // TCP 产品 属性可创建范围
117   - return record.filter((item) =>
118   - [
119   - DataTypeEnum.BOOL,
120   - DataTypeEnum.NUMBER_DOUBLE,
121   - DataTypeEnum.NUMBER_INT,
122   - DataTypeEnum.STRING,
123   - ].includes(item.itemValue as DataTypeEnum)
124   - );
125   - }
126   -
127   - return hasStructForm
128   - ? record.filter((item) => item.itemValue !== DataTypeEnum.STRUCT)
129   - : record;
130   - } catch (error) {
131   - return [];
132   - }
133   - },
134   - params: {
135   - dictCode: 'data_type',
136   - },
137   - labelField: 'itemText',
138   - valueField: 'itemValue',
139   - getPopupContainer: () => document.body,
140   - onChange: (value: string) => {
141   - if (value == DataTypeEnum.STRUCT) {
142   - setFieldsValue({ [FormField.SPECS_LIST]: [], [FormField.HAS_STRUCT_FROM]: true });
143   - }
144   - },
145   - };
146   - },
147   - },
148   - {
149   - field: FormField.ENUM_LIST,
150   - component: 'Input',
151   - label: '枚举',
152   - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.ENUM,
153   - slot: 'EnumList',
154   - colProps: {
155   - span: 24,
156   - },
157   - },
158   - {
159   - field: FormField.VALUE_RANGE,
160   - label: '取值范围',
161   - component: 'CustomMinMaxInput',
162   - valueField: 'value',
163   - changeEvent: 'update:value',
164   - colProps: {
165   - span: 18,
166   - },
167   - ifShow: ({ values }) =>
168   - values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
169   - values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
170   - rules: [{ validator: validateValueRange }],
171   - },
172   - {
173   - field: FormField.STEP,
174   - label: '步长',
175   - component: 'InputNumber',
176   - colProps: {
177   - span: 18,
178   - },
179   - componentProps: {
180   - maxLength: 255,
181   - placeholder: '请输入步长',
182   - min: 1,
183   - formatter: (value: number | string) => {
184   - return value ? Math.floor(Number(value)) : value;
185   - },
186   - },
187   - ifShow: ({ values }) =>
188   - values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
189   - values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
190   - dynamicRules: ({ model }) => {
191   - const valueRange = model[FormField.VALUE_RANGE] || {};
192   - const { min, max } = valueRange;
193   - const step = model[FormField.STEP];
194   - return [
195   - {
196   - validator: () => {
197   - if ([min, max].every(isNullOrUnDef)) return Promise.resolve();
198   - if (step > max - min) {
199   - return Promise.reject('步长不能大于取值范围的差值');
200   - }
201   - return Promise.resolve();
202   - },
203   - },
204   - ];
205   - },
206   - },
207   - {
208   - field: FormField.UNIT_NAME,
209   - label: '单位名称',
210   - component: 'Input',
211   - show: false,
212   - },
213   - {
214   - field: FormField.UNIT,
215   - label: '单位',
216   - component: 'ApiSelect',
217   - colProps: {
218   - span: 9,
219   - },
220   - componentProps: ({ formActionType }) => {
221   - const { setFieldsValue } = formActionType;
222   - return {
223   - placeholder: '请选择单位',
224   - api: async (params) => {
225   - const list = await findDictItemByCode(params);
226   - list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`));
227   - return list;
228   - },
229   - params: {
230   - dictCode: 'attribute_unit',
231   - },
232   - labelInValue: true,
233   - labelField: 'itemText',
234   - valueField: 'itemValue',
235   - onChange(_, record: Record<'label' | 'value', string>) {
236   - if (record) {
237   - const { label } = record;
238   - setFieldsValue({ [FormField.UNIT_NAME]: label });
239   - }
240   - },
241   - getPopupContainer: () => document.body,
242   - showSearch: true,
243   - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
244   - let { label, value } = option;
245   - label = label.toLowerCase();
246   - value = value.toLowerCase();
247   - inputValue = inputValue.toLowerCase();
248   - return label.includes(inputValue) || value.includes(inputValue);
249   - },
250   - };
251   - },
252   - ifShow: ({ values }) =>
253   - values[FormField.TYPE] === DataTypeEnum.NUMBER_INT ||
254   - values[FormField.TYPE] === DataTypeEnum.NUMBER_DOUBLE,
255   - },
256   - {
257   - field: FormField.BOOL_CLOSE,
258   - component: 'Input',
259   - required: true,
260   - label: '0 -',
261   - colProps: {
262   - span: 18,
263   - },
264   - componentProps: {
265   - placeholder: '如:关',
266   - },
267   - defaultValue: '关',
268   - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.BOOL,
269   - dynamicRules: ({ model }) => {
270   - const close = model[FormField.BOOL_CLOSE];
271   - const open = model[FormField.BOOL_OPEN];
272   - return [
273   - {
274   - required: true,
275   - },
276   - {
277   - validator() {
278   - if (open === close) return Promise.reject('布尔值不能相同');
279   - return Promise.resolve();
280   - },
281   - },
282   - ];
283   - },
284   - },
285   - {
286   - field: FormField.BOOL_OPEN,
287   - component: 'Input',
288   - required: true,
289   - label: '1 -',
290   - colProps: {
291   - span: 18,
292   - },
293   - componentProps: {
294   - placeholder: '如:开',
295   - },
296   - defaultValue: '开',
297   - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.BOOL,
298   - dynamicRules: ({ model }) => {
299   - const close = model[FormField.BOOL_CLOSE];
300   - const open = model[FormField.BOOL_OPEN];
301   - return [
302   - {
303   - required: true,
304   - },
305   - {
306   - validator() {
307   - if (open === close) return Promise.reject('布尔值不能相同');
308   - return Promise.resolve();
309   - },
310   - },
311   - ];
312   - },
313   - },
314   - {
315   - field: FormField.LENGTH,
316   - component: 'Input',
317   - required: true,
318   - label: '数据长度',
319   - defaultValue: '10240',
320   - colProps: {
321   - span: 8,
322   - },
323   - componentProps: {
324   - placeholder: '请输入数据长度',
325   - },
326   - renderComponentContent: () => {
327   - return {
328   - suffix: () => '字节',
329   - };
330   - },
331   - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.STRING,
332   - },
333   - {
334   - field: FormField.EXTENSION_DESC,
335   - component: 'ExtendDesc',
336   - label: '扩展描述',
337   - valueField: 'value',
338   - changeEvent: 'update:value',
339   - ifShow: isTcp,
340   - colProps: {
341   - span: 16,
342   - },
343   - componentProps: ({ formModel }) => {
344   - return {
345   - dataType: formModel[FormField.TYPE],
346   - };
347   - },
348   - },
349   - {
350   - field: FormField.ACCESS_MODE,
351   - component: 'ApiRadioGroup',
352   - label: '读写类型',
353   - required: true,
354   - colProps: {
355   - span: 24,
356   - },
357   - ifShow: () => !hiddenAccessMode && !hasStructForm,
358   - defaultValue: 'r',
359   - componentProps: {
360   - placeholder: '请选择读写类型',
361   - api: findDictItemByCode,
362   - params: {
363   - dictCode: 'read_write_type',
364   - },
365   - labelField: 'itemText',
366   - valueField: 'itemValue',
367   - },
368   - },
369   - {
370   - field: FormField.SPECS_LIST,
371   - label: 'JSON对象',
372   - component: 'StructForm',
373   - valueField: 'value',
374   - changeEvent: 'update:value',
375   - colProps: { span: 24 },
376   - ifShow: ({ values }) => values[FormField.TYPE] === DataTypeEnum.STRUCT,
377   - rules: [{ required: true, validator: validateJSON }],
378   - componentProps: ({ formModel }) => {
379   - return {
380   - hasStructForm: formModel[FormField.HAS_STRUCT_FROM],
381   - };
382   - },
383   - },
384   - {
385   - field: FormField.REFARK,
386   - label: '备注',
387   - component: 'InputTextArea',
388   - componentProps: {
389   - rows: 4,
390   - maxLength: 100,
391   - placeholder: '请输入描述',
392   - },
393   - },
394   - ];
395   -};
1   -import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
2   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
3   -import { FormField } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
4   -
5   -export enum OpenModalMode {
6   - CREATE = 'create',
7   - UPDATE = 'update',
8   -}
9   -
10   -export interface OpenModalParams {
11   - mode: OpenModalMode;
12   - record?: StructRecord;
13   -}
14   -
15   -export interface StructFormValue
16   - extends Partial<Record<Exclude<FormField, FormField.VALUE_RANGE | FormField.STRUCT>, string>> {
17   - [FormField.TYPE]: DataTypeEnum;
18   - [FormField.VALUE_RANGE]?: {
19   - [FormField.MIN]: string;
20   - [FormField.MAX]: string;
21   - };
22   - [FormField.STRUCT]: StructRecord[];
23   -}
24   -
25   -export interface StructRecord extends StructJSON {
26   - id: string;
27   -}
1   -import { cloneDeep } from 'lodash-es';
2   -import { StructFormValue } from './type';
3   -import {
4   - DataType,
5   - ModelOfMatterParams,
6   - Specs,
7   - StructJSON,
8   -} from '/@/api/device/model/modelOfMatterModel';
9   -import { isArray } from '/@/utils/is';
10   -import { DataTypeEnum } from '/@/enums/objectModelEnum';
11   -
12   -export function transfromToStructJSON(value: StructFormValue, enumList: Specs[] = []): StructJSON {
13   - const {
14   - type,
15   - valueRange,
16   - step,
17   - length = '',
18   - boolClose,
19   - boolOpen,
20   - unit,
21   - unitName,
22   - functionName,
23   - identifier,
24   - remark,
25   - specs,
26   - accessMode,
27   - } = value;
28   - const basic = { functionName, identifier, remark, accessMode };
29   - let dataType = {} as unknown as DataType;
30   -
31   - switch (type) {
32   - case DataTypeEnum.NUMBER_INT:
33   - dataType = {
34   - type,
35   - specs: { valueRange, step, unit, unitName },
36   - };
37   - break;
38   -
39   - case DataTypeEnum.NUMBER_DOUBLE:
40   - dataType = {
41   - type,
42   - specs: { valueRange, step, unit, unitName },
43   - };
44   - break;
45   -
46   - case DataTypeEnum.BOOL:
47   - dataType = {
48   - type,
49   - specs: {
50   - boolOpen: boolOpen,
51   - boolClose: boolClose,
52   - },
53   - };
54   - break;
55   -
56   - case DataTypeEnum.STRING:
57   - dataType = {
58   - type,
59   - specs: { length },
60   - };
61   - break;
62   -
63   - case DataTypeEnum.ENUM:
64   - dataType = {
65   - type,
66   - specsList: enumList,
67   - };
68   - break;
69   -
70   - case DataTypeEnum.STRUCT:
71   - dataType = {
72   - type,
73   - specs: specs! as unknown as ModelOfMatterParams[],
74   - };
75   - break;
76   - }
77   - return { ...basic, dataType } as StructJSON;
78   -}
79   -
80   -export const excludeIdInStructJSON = (struct: DataType) => {
81   - const _value = cloneDeep(struct);
82   - const { specs } = _value;
83   - if (!specs) return _value;
84   - const list = [specs];
85   -
86   - while (list.length) {
87   - for (const item of list) {
88   - if (isArray(item)) {
89   - (item as StructJSON[]).forEach((temp) => {
90   - if (temp.dataType?.specs) {
91   - list.push(temp.dataType.specs);
92   - }
93   - Reflect.has(temp, 'id') && Reflect.deleteProperty(temp, 'id');
94   - });
95   - } else {
96   - Reflect.has(item as Recordable, 'id') && Reflect.deleteProperty(item as Recordable, 'id');
97   - }
98   - list.shift();
99   - }
100   - }
101   -
102   - return _value;
103   -};
... ... @@ -117,12 +117,12 @@ export type ComponentType =
117 117 | 'ColorPicker'
118 118 | 'IconDrawer'
119 119 | 'ApiUpload'
  120 + | 'ApiCascader'
120 121 | 'ApiSearchSelect'
121 122 | 'StructForm'
122 123 | 'ApiSelectScrollLoad'
123 124 | 'TransferModal'
124 125 | 'TransferTableModal'
125   - | 'ObjectModelValidateForm'
126 126 | 'ThingsModelForm'
127 127 | 'DevicePicker'
128 128 | 'ProductPicker'
... ... @@ -145,4 +145,6 @@ export type ComponentType =
145 145 | 'TriggerDurationInput'
146 146 | 'AlarmProfileSelect'
147 147 | 'LockControlGroup'
148   - | 'EnumList';
  148 + | 'EnumList'
  149 + | 'Segmented'
  150 + | 'StructFormItem';
... ...
  1 +export enum TransportTypeEnum {
  2 + DEFAULT = 'DEFAULT',
  3 + MQTT = 'MQTT',
  4 + COAP = 'COAP',
  5 + LWM2M = 'LWM2M',
  6 + SNMP = 'SNMP',
  7 + TCP = 'TCP',
  8 +}
  9 +
  10 +export enum ReadAndWriteEnum {
  11 + READ = 'r',
  12 + READ_AND_WRITE = 'rw',
  13 +}
  14 +
  15 +export enum ServiceCallTypeEnum {
  16 + ASYNC = 'ASYNC',
  17 + SYNC = 'SYNC',
  18 +}
  19 +
  20 +export enum ServiceCallTypeNameEnum {
  21 + ASYNC = '异步',
  22 + SYNC = '同步',
  23 +}
  24 +
  25 +export enum CommandDeliveryWayEnum {
  26 + ONE_WAY = 'oneway',
  27 + TWO_WAY = 'twoway',
  28 +}
  29 +
  30 +export enum CommandDeliveryWayNameEnum {
  31 + ONE_WAY = '单向',
  32 + TWO_WAY = '双向',
  33 +}
  34 +
  35 +export enum CommandTypeEnum {
  36 + CUSTOM = 0,
  37 + SERVICE = 1,
  38 + ATTRIBUTE = 2,
  39 + API = 'api',
  40 +}
  41 +
  42 +export enum CommandTypeNameEnum {
  43 + CUSTOM = '自定义',
  44 + SERVICE = '服务',
  45 + ATTRIBUTE = '属性',
  46 +}
  47 +
  48 +export enum RPCCommandMethodEnum {
  49 + THINGSKIT = 'methodThingskit',
  50 +}
... ...
1 1 export enum DictEnum {
  2 + // 物模型数据类型
  3 + DATA_TYPE = 'data_type',
  4 +
  5 + // 事件类型
  6 + EVENT_TYPE = 'event_type',
  7 + // 物模型单位
  8 + ATTRIBUTE_UNIT = 'attribute_unit',
  9 + // 物模型读写类型
  10 + READ_WRITE_TYP = 'read_write_type',
2 11 // 从机地址
3 12 SLAVE_ADDRESS = 'slave_address',
4 13 // 功能码
... ...
... ... @@ -154,16 +154,3 @@ export enum ExecutionActionNameEnum {
154 154 DEVICE_OUT = '设备输出',
155 155 MSG_NOTIFY = '告警输出',
156 156 }
157   -
158   -export enum CommandTypeEnum {
159   - CUSTOM = 0,
160   - SERVICE = 1,
161   - ATTRIBUTE = 2,
162   - API = 'api',
163   -}
164   -
165   -export enum CommandTypeNameEnum {
166   - CUSTOM = '自定义',
167   - SERVICE = '服务',
168   - ATTRIBUTE = '属性',
169   -}
... ...
... ... @@ -7,6 +7,30 @@ export enum DataTypeEnum {
7 7 ENUM = 'ENUM',
8 8 }
9 9
  10 +export enum FunctionTypeEnum {
  11 + PROPERTIES = 'properties',
  12 + SERVICE = 'services',
  13 + EVENTS = 'events',
  14 +}
  15 +
  16 +export enum FunctionTypeNameEnum {
  17 + PROPERTIES = '属性',
  18 + SERVICE = '服务',
  19 + EVENTS = '事件',
  20 +}
  21 +
  22 +export enum ObjectEventTypeEnum {
  23 + INFO = 'INFO',
  24 + ALERT = 'ALERT',
  25 + ERROR = 'ERROR',
  26 +}
  27 +
  28 +export enum ObjectEventTypeNameEnum {
  29 + INFO = '信息',
  30 + ALERT = '告警',
  31 + ERROR = '故障',
  32 +}
  33 +
10 34 export enum RegisterDataTypeEnum {
11 35 UN_SHORT = 'unshort',
12 36 }
... ... @@ -26,3 +50,12 @@ export enum RegisterActionTypeNameEnum {
26 50 INT = '06写入单个保持寄存器',
27 51 DOUBLE = '16写入多个保持寄存器',
28 52 }
  53 +
  54 +export enum ObjectModelAccessModeEnum {
  55 + READ = 'r',
  56 + READ_AND_WRITE = 'rw',
  57 +}
  58 +
  59 +export enum ModbusCRCEnum {
  60 + CRC_16_LOWER = 'CRC_16_LOWER',
  61 +}
... ...
... ... @@ -29,23 +29,3 @@ export enum BooleanStringEnum {
29 29 TRUE = 'true',
30 30 FALSE = 'false',
31 31 }
32   -
33   -export enum ReadAndWriteEnum {
34   - READ = 'r',
35   - READ_AND_WRITE = 'rw',
36   -}
37   -
38   -export enum ServiceCallTypeEnum {
39   - ASYNC = 'ASYNC',
40   - SYNC = 'SYNC',
41   -}
42   -
43   -export enum ServiceCallTypeNameEnum {
44   - ASYNC = '异步',
45   - SYNC = '同步',
46   -}
47   -
48   -export enum CommandDeliveryWayEnum {
49   - ONE_WAY = 'oneway',
50   - TWO_WAY = 'twoway',
51   -}
... ...
... ... @@ -6,6 +6,7 @@
6 6 :title="getTitle"
7 7 width="30%"
8 8 @ok="handleSubmit"
  9 + wrapClassName="camera-configration-drawer"
9 10 >
10 11 <BasicForm @register="registerForm">
11 12 <template #videoPlatformIdSlot="{ model, field }">
... ... @@ -125,7 +126,11 @@
125 126 });
126 127 }
127 128
128   - setFieldsValue({ ...record, videoType: record.videoPlatformDTO?.type });
  129 + setFieldsValue({
  130 + ...record,
  131 + videoType: record.videoPlatformDTO?.type,
  132 + ...(record?.params || {}),
  133 + });
129 134 } else {
130 135 editId.value = '';
131 136 }
... ... @@ -157,6 +162,7 @@
157 162 ) {
158 163 values.streamType = values.articulation;
159 164 values.playProtocol = values.videoFormat;
  165 + values.params = values.channelNo ? { channelNo: values.channelNo } : null;
160 166 }
161 167
162 168 await createOrEditCameraManage(values);
... ... @@ -190,3 +196,11 @@
190 196 },
191 197 });
192 198 </script>
  199 +
  200 +<style lang="less">
  201 + .camera-configration-drawer {
  202 + .ant-input-number {
  203 + width: 100% !important;
  204 + }
  205 + }
  206 +</style>
... ...
... ... @@ -21,11 +21,6 @@ export enum CameraPermission {
21 21 DELETE = 'api:yt:video:delete',
22 22 }
23 23
24   -export enum VideoPlatformEnum {
25   - HAIKANG = 0,
26   - FLUORITE = 1,
27   -}
28   -
29 24 export enum AccessMode {
30 25 ManuallyEnter = 0,
31 26 Streaming = 1,
... ... @@ -250,16 +245,28 @@ export const formSchema: QFormSchema[] = [
250 245 required: true,
251 246 defaultValue: VideoPlatformEnum.SCI,
252 247 ifShow: ({ values }) => values.accessMode === AccessMode.Streaming,
253   - componentProps: {
254   - api: async (params) => {
255   - const values = await findDictItemByCode(params);
256   - return values.map((item) => ({ label: item.itemText, value: Number(item.itemValue) }));
257   - },
258   - params: {
259   - dictCode: DictEnum.STREAMING_MEDIA_TYPE,
260   - },
261   - getPopupContainer: () => document.body,
262   - placeholder: `请选择平台类型`,
  248 + componentProps: ({ formActionType }) => {
  249 + return {
  250 + api: async (params) => {
  251 + const values = await findDictItemByCode(params);
  252 + return values.map((item) => ({ label: item.itemText, value: Number(item.itemValue) }));
  253 + },
  254 + params: {
  255 + dictCode: DictEnum.STREAMING_MEDIA_TYPE,
  256 + },
  257 + getPopupContainer: () => document.body,
  258 + placeholder: `请选择平台类型`,
  259 + onChange() {
  260 + const { setFieldsValue } = formActionType;
  261 + setFieldsValue({
  262 + articulation: ArticulationEnumType.HIGH_DEFINITION,
  263 + playProtocol: PlayProtocol.HTTP,
  264 + sn: null,
  265 + channelNo: null,
  266 + videoPlatformId: null,
  267 + });
  268 + },
  269 + };
263 270 },
264 271 },
265 272 {
... ... @@ -384,4 +391,22 @@ export const formSchema: QFormSchema[] = [
384 391 placeholder: '请输入监控点编号',
385 392 },
386 393 },
  394 + {
  395 + field: 'channelNo',
  396 + label: '通道号',
  397 + component: 'InputNumber',
  398 + ifShow({ values }) {
  399 + return (
  400 + values.accessMode === AccessMode.Streaming &&
  401 + values.videoType === VideoPlatformEnum.FLUORITE
  402 + );
  403 + },
  404 + componentProps: {
  405 + min: 1,
  406 + max: 10000,
  407 + step: 1,
  408 + placeholder: '请输入通道号',
  409 + precision: 0,
  410 + },
  411 + },
387 412 ];
... ...
1   -import { FormProps, FormSchema, useComponentRegister } from '/@/components/Form';
  1 +import { FormSchema, useComponentRegister } from '/@/components/Form';
2 2 import { findDictItemByCode } from '/@/api/system/dict';
3 3 import { getGatewayDevice, queryDeviceProfileBy } from '/@/api/device/deviceManager';
4   -import { TransportTypeEnum } from '../../profiles/components/TransportDescript/const';
5   -import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor';
6 4 import { JSONEditor } from '/@/components/CodeEditor';
7 5 import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
8   -import { getModelServices } from '/@/api/device/modelOfMatter';
9   -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
10   -import { h, toRaw, unref } from 'vue';
11   -import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
12   -import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
  6 +import { h } from 'vue';
13 7 import { TaskTypeEnum } from '/@/views/task/center/config';
14 8 import { AddressTypeEnum } from '/@/views/task/center/components/PollCommandInput';
15 9 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
... ... @@ -17,9 +11,9 @@ import { createImgPreview } from '/@/components/Preview';
17 11 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
18 12 import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue';
19 13 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  14 +import { TransportTypeEnum } from '/@/enums/deviceEnum';
20 15
21 16 useComponentRegister('JSONEditor', JSONEditor);
22   -useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
23 17 useComponentRegister('LockControlGroup', LockControlGroup);
24 18 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
25 19
... ... @@ -910,214 +904,3 @@ export const TokenSchemas: FormSchema[] = [
910 904 },
911 905 },
912 906 ];
913   -
914   -export enum ValueType {
915   - JSON = 'json',
916   - STRING = 'string',
917   -}
918   -
919   -export enum CommandFieldsEnum {
920   - COMMAND_TYPE = 'commandType',
921   - VALUE_TYPE = 'valueType',
922   - COMMAND_TEXT = 'commandText',
923   - COMAND_VALUE = 'commandValue',
924   - SERVICE = 'service',
925   - SERVICE_TYPE = 'service_type',
926   - TCP_SERVICE = 'tcpService',
927   - MODEL_INPUT = 'modelInput',
928   - CUSTOM_TYPE = 'customType',
929   -}
930   -
931   -export enum CommandType {
932   - CUSTOM = 'custom',
933   - SERVICE = 'service',
934   -}
935   -
936   -export const CommandSchemas = (
937   - transportType: TransportTypeEnum,
938   - deviceProfileId: string
939   -): FormSchema[] => {
940   - return [
941   - {
942   - field: CommandFieldsEnum.COMMAND_TYPE,
943   - component: 'RadioGroup',
944   - label: '下发类型',
945   - defaultValue: CommandType.CUSTOM,
946   - componentProps: ({ formActionType }) => {
947   - const { setFieldsValue } = formActionType;
948   - return {
949   - options: [
950   - { label: '自定义', value: CommandType.CUSTOM },
951   - { label: '服务', value: CommandType.SERVICE },
952   - ],
953   - onChange() {
954   - setFieldsValue({
955   - [CommandFieldsEnum.SERVICE]: null,
956   - [CommandFieldsEnum.MODEL_INPUT]: null,
957   - [CommandFieldsEnum.COMAND_VALUE]: null,
958   - [CommandFieldsEnum.COMMAND_TEXT]: null,
959   - });
960   - },
961   - };
962   - },
963   - },
964   - {
965   - field: CommandFieldsEnum.CUSTOM_TYPE,
966   - component: 'RadioGroup',
967   - label: '单向/双向',
968   - defaultValue: CommandDeliveryWayEnum.ONE_WAY,
969   - ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM,
970   - componentProps: {
971   - options: [
972   - {
973   - label: '单向',
974   - value: CommandDeliveryWayEnum.ONE_WAY,
975   - },
976   - {
977   - label: '双向',
978   - value: CommandDeliveryWayEnum.TWO_WAY,
979   - },
980   - ],
981   - },
982   - },
983   - {
984   - field: CommandFieldsEnum.VALUE_TYPE,
985   - label: '命令类型',
986   - component: 'RadioGroup',
987   - ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM,
988   - defaultValue: transportType === TransportTypeEnum.TCP ? ValueType.STRING : ValueType.JSON,
989   - componentProps: () => {
990   - const options: Record<'label' | 'value', string>[] = [];
991   - if (transportType === TransportTypeEnum.TCP) {
992   - options.push({ label: '字符串', value: ValueType.STRING });
993   - } else {
994   - options.push({ label: 'JSON', value: ValueType.JSON });
995   - }
996   - return {
997   - options,
998   - };
999   - },
1000   - },
1001   - {
1002   - field: CommandFieldsEnum.COMMAND_TEXT,
1003   - label: '命令',
1004   - ifShow: ({ model }) =>
1005   - model[CommandFieldsEnum.VALUE_TYPE] === ValueType.STRING &&
1006   - model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM,
1007   - component: 'InputTextArea',
1008   - componentProps: {
1009   - autoSize: {
1010   - minRows: 3,
1011   - },
1012   - placeholder: '请输入命令内容',
1013   - },
1014   - dynamicRules: () => {
1015   - return [
1016   - {
1017   - required: false,
1018   - validator: (_, value) => {
1019   - const zg = /^[0-9a-zA-Z]*$/;
1020   - if (!zg.test(value)) {
1021   - return Promise.reject('输入的内容只能是字母和数字的组合');
1022   - } else {
1023   - return Promise.resolve();
1024   - }
1025   - },
1026   - },
1027   - ];
1028   - },
1029   - },
1030   - {
1031   - field: CommandFieldsEnum.COMAND_VALUE,
1032   - label: '命令',
1033   - component: 'JSONEditor',
1034   - colProps: { span: 20 },
1035   - changeEvent: 'update:value',
1036   - valueField: 'value',
1037   - rules: [...JSONEditorValidator()],
1038   - ifShow: ({ model }) =>
1039   - model[CommandFieldsEnum.VALUE_TYPE] === ValueType.JSON &&
1040   - model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM,
1041   -
1042   - componentProps: {
1043   - height: 250,
1044   - },
1045   - },
1046   - {
1047   - field: CommandFieldsEnum.SERVICE,
1048   - label: '服务',
1049   - component: 'ApiSelect',
1050   - ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM,
1051   - rules: [{ required: true, message: '请选择服务' }],
1052   - componentProps: ({ formActionType }) => {
1053   - const { setFieldsValue, updateSchema } = formActionType;
1054   - return {
1055   - api: async () => {
1056   - try {
1057   - const result = await getModelServices({ deviceProfileId });
1058   - return result || [];
1059   - } catch (error) {
1060   - return [];
1061   - }
1062   - },
1063   - valueField: 'identifier',
1064   - labelField: 'functionName',
1065   - getPopupContainer: () => document.body,
1066   - onChange(value: string, options: ModelOfMatterParams) {
1067   - if (!value) return;
1068   -
1069   - const setValues = {
1070   - [CommandFieldsEnum.CUSTOM_TYPE]:
1071   - options.callType === ServiceCallTypeEnum.ASYNC
1072   - ? CommandDeliveryWayEnum.ONE_WAY
1073   - : CommandDeliveryWayEnum.TWO_WAY,
1074   - [CommandFieldsEnum.MODEL_INPUT]: null,
1075   - };
1076   -
1077   - if (transportType !== TransportTypeEnum.TCP) {
1078   - updateSchema({
1079   - field: CommandFieldsEnum.MODEL_INPUT,
1080   - componentProps: {
1081   - inputData: toRaw(unref(options.functionJson.inputData)),
1082   - },
1083   - });
1084   - } else {
1085   - Object.assign(setValues, {
1086   - [CommandFieldsEnum.TCP_SERVICE]:
1087   - options.functionJson?.inputData?.[0]?.serviceCommand,
1088   - });
1089   - }
1090   -
1091   - setFieldsValue(setValues);
1092   - },
1093   - };
1094   - },
1095   - },
1096   - {
1097   - field: CommandFieldsEnum.TCP_SERVICE,
1098   - component: 'Input',
1099   - label: '命令',
1100   - dynamicDisabled: true,
1101   - ifShow: ({ model }) =>
1102   - model[CommandFieldsEnum.SERVICE] &&
1103   - transportType === TransportTypeEnum.TCP &&
1104   - model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM,
1105   - },
1106   - {
1107   - field: CommandFieldsEnum.MODEL_INPUT,
1108   - component: 'ObjectModelValidateForm',
1109   - label: '输入参数',
1110   - changeEvent: 'update:value',
1111   - valueField: 'value',
1112   - ifShow: ({ model }) =>
1113   - model[CommandFieldsEnum.SERVICE] &&
1114   - transportType !== TransportTypeEnum.TCP &&
1115   - model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM,
1116   - componentProps: {
1117   - formProps: {
1118   - wrapperCol: { span: 24 },
1119   - } as FormProps,
1120   - },
1121   - },
1122   - ];
1123   -};
... ...
... ... @@ -5,8 +5,8 @@ import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
5 5 import { findDictItemByCode } from '/@/api/system/dict';
6 6 import { FormSchema, useComponentRegister } from '/@/components/Form';
7 7 import { BasicColumn } from '/@/components/Table';
  8 +import { TransportTypeEnum } from '/@/enums/deviceEnum';
8 9 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
9   -import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
10 10 import XLSX, { CellObject } from 'xlsx';
11 11
12 12 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
... ...
  1 +import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
  2 +import { getModelServices } from '/@/api/device/modelOfMatter';
  3 +import { FormProps, FormSchema, useComponentRegister } from '/@/components/Form';
  4 +import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
  5 +import { JSONEditor, JSONEditorValidator } from '/@/components/CodeEditor';
  6 +import {
  7 + TransportTypeEnum,
  8 + CommandTypeNameEnum,
  9 + CommandTypeEnum,
  10 + ServiceCallTypeEnum,
  11 + CommandDeliveryWayEnum,
  12 + CommandDeliveryWayNameEnum,
  13 +} from '/@/enums/deviceEnum';
  14 +
  15 +export interface CommandDeliveryFormFieldType {
  16 + [CommandFieldsEnum.COMMAND_TYPE]: CommandTypeEnum;
  17 + [CommandFieldsEnum.TCP_COMMAND_VALUE]?: string;
  18 + [CommandFieldsEnum.COMAND_VALUE]?: string;
  19 + [CommandFieldsEnum.SERVICE]?: string;
  20 + [CommandFieldsEnum.MODEL_INPUT]?: ModelOfMatterParams;
  21 + [CommandFieldsEnum.CALL_TYPE]: CommandDeliveryWayEnum;
  22 +}
  23 +
  24 +export enum CommandFieldsEnum {
  25 + COMMAND_TYPE = 'commandType',
  26 + TCP_COMMAND_VALUE = 'tcpCommandValue',
  27 + COMAND_VALUE = 'commandValue',
  28 + SERVICE = 'service',
  29 + MODEL_INPUT = 'modelInput',
  30 + CALL_TYPE = 'callType',
  31 +
  32 + SERVICE_COMMAND = 'serviceCommand',
  33 +
  34 + SERVICE_OBJECT_MODEL = 'serviceObjectModel',
  35 +}
  36 +
  37 +useComponentRegister('JSONEditor', JSONEditor);
  38 +
  39 +export const CommandSchemas = (
  40 + transportType: TransportTypeEnum,
  41 + deviceProfileId: string
  42 +): FormSchema[] => {
  43 + return [
  44 + {
  45 + field: CommandFieldsEnum.COMMAND_TYPE,
  46 + component: 'RadioGroup',
  47 + label: '下发类型',
  48 + defaultValue: CommandTypeEnum.CUSTOM,
  49 + required: true,
  50 + componentProps: ({ formActionType }) => {
  51 + const { setFieldsValue } = formActionType;
  52 + return {
  53 + options: [
  54 + { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
  55 + { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE },
  56 + ],
  57 + onChange() {
  58 + setFieldsValue({
  59 + [CommandFieldsEnum.SERVICE]: null,
  60 + [CommandFieldsEnum.MODEL_INPUT]: null,
  61 + [CommandFieldsEnum.COMAND_VALUE]: null,
  62 + [CommandFieldsEnum.TCP_COMMAND_VALUE]: null,
  63 + });
  64 + },
  65 + };
  66 + },
  67 + },
  68 + {
  69 + field: CommandFieldsEnum.CALL_TYPE,
  70 + component: 'RadioGroup',
  71 + label: '单向/双向',
  72 + required: true,
  73 + defaultValue: CommandDeliveryWayEnum.ONE_WAY,
  74 + ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  75 + componentProps: {
  76 + options: Object.keys(CommandDeliveryWayEnum).map((key) => ({
  77 + label: CommandDeliveryWayNameEnum[key],
  78 + value: CommandDeliveryWayEnum[key],
  79 + })),
  80 + },
  81 + },
  82 + {
  83 + field: CommandFieldsEnum.TCP_COMMAND_VALUE,
  84 + label: '命令',
  85 + required: true,
  86 + ifShow: ({ model }) =>
  87 + transportType === TransportTypeEnum.TCP &&
  88 + model[CommandFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  89 + component: 'Input',
  90 + rules: [{ validator: validateTCPCustomCommand }],
  91 + componentProps: {
  92 + placeholder: '请输入命令',
  93 + },
  94 + },
  95 + {
  96 + field: CommandFieldsEnum.COMAND_VALUE,
  97 + label: '命令',
  98 + component: 'JSONEditor',
  99 + colProps: { span: 20 },
  100 + changeEvent: 'update:value',
  101 + valueField: 'value',
  102 + required: true,
  103 + rules: JSONEditorValidator(),
  104 + ifShow: ({ model }) =>
  105 + transportType !== TransportTypeEnum.TCP &&
  106 + model[CommandFieldsEnum.COMMAND_TYPE] === CommandTypeEnum.CUSTOM,
  107 + componentProps: {
  108 + height: 250,
  109 + },
  110 + },
  111 + {
  112 + field: CommandFieldsEnum.SERVICE,
  113 + label: '服务',
  114 + component: 'ApiSelect',
  115 + required: true,
  116 + ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] !== CommandTypeEnum.CUSTOM,
  117 + rules: [{ required: true, message: '请选择服务' }],
  118 + componentProps: ({ formActionType }) => {
  119 + const { setFieldsValue } = formActionType;
  120 + return {
  121 + api: getModelServices,
  122 + params: {
  123 + deviceProfileId,
  124 + },
  125 + valueField: 'identifier',
  126 + labelField: 'functionName',
  127 + getPopupContainer: () => document.body,
  128 + placeholder: '请选择服务',
  129 + onChange(
  130 + value: string,
  131 + options: ModelOfMatterParams & Record<'label' | 'value', string>
  132 + ) {
  133 + if (!value) return;
  134 + setFieldsValue({
  135 + [CommandFieldsEnum.CALL_TYPE]:
  136 + options.callType === ServiceCallTypeEnum.ASYNC
  137 + ? CommandDeliveryWayEnum.ONE_WAY
  138 + : CommandDeliveryWayEnum.TWO_WAY,
  139 + [CommandFieldsEnum.MODEL_INPUT]: null,
  140 + [CommandFieldsEnum.SERVICE_OBJECT_MODEL]: Object.assign(options, {
  141 + functionName: options.label,
  142 + identifier: options.value,
  143 + }),
  144 + });
  145 + },
  146 + };
  147 + },
  148 + },
  149 + {
  150 + field: CommandFieldsEnum.SERVICE_OBJECT_MODEL,
  151 + label: '服务物模型',
  152 + component: 'Input',
  153 + ifShow: false,
  154 + },
  155 + {
  156 + field: CommandFieldsEnum.MODEL_INPUT,
  157 + component: 'Input',
  158 + label: '输入参数',
  159 + changeEvent: 'update:value',
  160 + valueField: 'value',
  161 + ifShow: ({ model }) =>
  162 + model[CommandFieldsEnum.SERVICE] &&
  163 + model[CommandFieldsEnum.SERVICE_OBJECT_MODEL] &&
  164 + model[CommandFieldsEnum.COMMAND_TYPE] !== CommandTypeEnum.CUSTOM,
  165 + componentProps: {
  166 + formProps: {
  167 + wrapperCol: { span: 24 },
  168 + } as FormProps,
  169 + },
  170 + slot: 'serviceCommand',
  171 + },
  172 + ];
  173 +};
... ...
  1 +export { default as CommandDeliveryModal } from './index.vue';
... ...
  1 +<template>
  2 + <BasicModal
  3 + title="命令下发"
  4 + :width="650"
  5 + @register="registerModal"
  6 + @ok="handleOk"
  7 + @cancel="handleCancel"
  8 + >
  9 + <BasicForm @register="registerForm">
  10 + <template #serviceCommand="{ field, model }">
  11 + <ThingsModelForm
  12 + :disabled="deviceDetail?.transportType === TransportTypeEnum.TCP"
  13 + ref="thingsModelFormRef"
  14 + v-model:value="model[field]"
  15 + :key="model[CommandFieldsEnum.SERVICE_OBJECT_MODEL]?.identifier"
  16 + :inputData="model[CommandFieldsEnum.SERVICE_OBJECT_MODEL]?.functionJson?.inputData"
  17 + :transportType="deviceDetail?.transportType"
  18 + />
  19 + </template>
  20 + </BasicForm>
  21 + </BasicModal>
  22 +</template>
  23 +<script lang="ts" setup>
  24 + import { nextTick, ref, unref } from 'vue';
  25 + import { BasicForm, ThingsModelForm, useForm } from '/@/components/Form';
  26 + import { CommandDeliveryFormFieldType, CommandFieldsEnum, CommandSchemas } from './config';
  27 + import { useMessage } from '/@/hooks/web/useMessage';
  28 + import { DeviceRecord } from '/@/api/device/model/deviceModel';
  29 + import { CommandDeliveryWayEnum } from '/@/enums/deviceEnum';
  30 + import { CommandTypeEnum, RPCCommandMethodEnum, TransportTypeEnum } from '/@/enums/deviceEnum';
  31 + import { BasicModal, useModalInner } from '/@/components/Modal';
  32 + import { getDeviceActiveTime } from '/@/api/alarm/position';
  33 + import { RpcCommandType } from '/@/api/device/model/deviceConfigModel';
  34 + import { parseStringToJSON } from '/@/components/CodeEditor';
  35 + import { commandIssuanceApi } from '/@/api/device/deviceManager';
  36 +
  37 + defineEmits(['register']);
  38 +
  39 + const thingsModelFormRef = ref<InstanceType<typeof ThingsModelForm>>();
  40 + const deviceDetail = ref<DeviceRecord>();
  41 +
  42 + const [registerModal, { setModalProps }] = useModalInner(
  43 + (params: ModalParamsType<DeviceRecord>) => {
  44 + const { record } = params;
  45 + deviceDetail.value = record;
  46 + setProps({
  47 + schemas: CommandSchemas(record.transportType as TransportTypeEnum, record.deviceProfileId),
  48 + });
  49 + }
  50 + );
  51 +
  52 + const { createMessage } = useMessage();
  53 +
  54 + const [registerForm, { setProps, getFieldsValue, validate, resetFields, clearValidate }] =
  55 + useForm({
  56 + labelWidth: 120,
  57 + baseColProps: { span: 20 },
  58 + labelAlign: 'right',
  59 + showSubmitButton: false,
  60 + showResetButton: false,
  61 + });
  62 +
  63 + const handleCancel = async () => {
  64 + await resetFields();
  65 + await nextTick();
  66 + await clearValidate();
  67 + };
  68 +
  69 + const handleValidate = async () => {
  70 + await validate();
  71 + await unref(thingsModelFormRef)?.validate?.();
  72 + };
  73 +
  74 + const handleValidateDeviceActive = async (): Promise<boolean> => {
  75 + const result = await getDeviceActiveTime(unref(deviceDetail)!.tbDeviceId);
  76 + const [firstItem] = result;
  77 + return !!firstItem.value;
  78 + };
  79 +
  80 + const handleCommandParams = (
  81 + values: CommandDeliveryFormFieldType,
  82 + serviceCommand: Recordable
  83 + ) => {
  84 + const { commandType, service } = values;
  85 +
  86 + const isTcpDevice = unref(deviceDetail)?.transportType === TransportTypeEnum.TCP;
  87 + if (commandType === CommandTypeEnum.CUSTOM) {
  88 + if (isTcpDevice) {
  89 + return values.tcpCommandValue;
  90 + }
  91 + return parseStringToJSON(values.commandValue!).json;
  92 + } else {
  93 + if (isTcpDevice) return Reflect.get(serviceCommand, CommandFieldsEnum.SERVICE_COMMAND);
  94 + return {
  95 + [service!]: serviceCommand,
  96 + };
  97 + }
  98 + };
  99 +
  100 + const handleOk = async () => {
  101 + await handleValidate();
  102 +
  103 + try {
  104 + setModalProps({ loading: true, confirmLoading: true });
  105 +
  106 + const values = getFieldsValue() as CommandDeliveryFormFieldType;
  107 + const { callType, commandType } = values;
  108 + const serviceCommand = unref(thingsModelFormRef)?.getFieldsValue() || {};
  109 +
  110 + if (callType === CommandDeliveryWayEnum.TWO_WAY && !(await handleValidateDeviceActive())) {
  111 + createMessage.warn('当前设备不在线');
  112 + return;
  113 + }
  114 +
  115 + const rpcCommands: RpcCommandType = {
  116 + additionalInfo: {
  117 + cmdType:
  118 + commandType === CommandTypeEnum.CUSTOM
  119 + ? CommandTypeEnum.CUSTOM
  120 + : CommandTypeEnum.SERVICE,
  121 + },
  122 + method: RPCCommandMethodEnum.THINGSKIT,
  123 + persistent: true,
  124 + params: handleCommandParams(values, serviceCommand),
  125 + };
  126 +
  127 + await commandIssuanceApi(callType, unref(deviceDetail)!.tbDeviceId, rpcCommands);
  128 +
  129 + createMessage.success('命令下发成功');
  130 + } finally {
  131 + setModalProps({ loading: false, confirmLoading: false });
  132 + }
  133 + };
  134 +</script>
  135 +<style scoped lang="less"></style>
... ...
1   -<template>
2   - <div class="tabs-detail">
3   - <div>
4   - <BasicForm @register="registerForm" />
5   - <Space class="w-full justify-end py-2" justify="end">
6   - <Button :loading="loading" type="primary" @click="handleOk" class="mr-2">确定</Button>
7   - <Button type="default" @click="handleCancel" class="mr-2">重置</Button>
8   - </Space>
9   - </div>
10   - </div>
11   -</template>
12   -<script lang="ts">
13   - import { defineComponent, nextTick, ref } from 'vue';
14   - import { BasicForm, useForm } from '/@/components/Form';
15   - import { CommandFieldsEnum, CommandSchemas, CommandType, ValueType } from '../../config/data';
16   - import { commandIssuanceApi } from '/@/api/device/deviceManager';
17   - import { useMessage } from '/@/hooks/web/useMessage';
18   - import { Button } from '/@/components/Button';
19   - import { Space } from 'ant-design-vue';
20   - import { DeviceRecord } from '/@/api/device/model/deviceModel';
21   - import { TransportTypeEnum } from '../../../profiles/components/TransportDescript/const';
22   - import { parseStringToJSON } from '/@/components/CodeEditor/src/JSONEditor';
23   - import { CommandDeliveryWayEnum } from '/@/enums/toolEnum';
24   -
25   - export default defineComponent({
26   - components: { BasicForm, Button, Space },
27   - props: {
28   - deviceDetail: {
29   - type: Object as PropType<DeviceRecord>,
30   - required: true,
31   - },
32   - },
33   - emits: ['register'],
34   - setup(props) {
35   - const { createMessage } = useMessage();
36   - const loading = ref(false);
37   -
38   - const [registerForm, { getFieldsValue, validate, resetFields, clearValidate }] = useForm({
39   - labelWidth: 120,
40   - schemas: CommandSchemas(
41   - props.deviceDetail.deviceProfile.transportType as TransportTypeEnum,
42   - props.deviceDetail.deviceProfileId
43   - ),
44   - baseColProps: { span: 20 },
45   - labelAlign: 'right',
46   - showSubmitButton: false,
47   - showResetButton: false,
48   - });
49   -
50   - const handleCancel = async () => {
51   - await resetFields();
52   - await nextTick();
53   - await clearValidate();
54   - };
55   -
56   - const handleOk = async () => {
57   - loading.value = true;
58   - try {
59   - // 验证
60   - const valid = await validate();
61   - if (!valid) return;
62   - // 收集表单数据
63   - const field = getFieldsValue();
64   - let command: Recordable = {
65   - persistent: true,
66   - method: 'methodThingskit',
67   - params: field[CommandFieldsEnum.COMMAND_TEXT],
68   - };
69   -
70   - if (field[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM) {
71   - if (field[CommandFieldsEnum.VALUE_TYPE] === ValueType.JSON) {
72   - const { json } = parseStringToJSON(field.commandValue);
73   - command.params = json;
74   - }
75   - } else {
76   - const { transportType } = props.deviceDetail.deviceProfile;
77   - command.params =
78   - transportType === TransportTypeEnum.TCP
79   - ? field[CommandFieldsEnum.TCP_SERVICE]
80   - : {
81   - [field[CommandFieldsEnum.SERVICE]]: field[CommandFieldsEnum.MODEL_INPUT],
82   - };
83   - command.additionalInfo = { cmdType: 1 };
84   - }
85   - commandIssuanceApi(
86   - field[CommandFieldsEnum.CUSTOM_TYPE] as CommandDeliveryWayEnum,
87   - props.deviceDetail.tbDeviceId,
88   - command
89   - )
90   - .then((res) => {
91   - if (!res) return;
92   - createMessage.success('命令下发成功');
93   - loading.value = true;
94   - // 请求
95   - handleCancel();
96   - })
97   - .catch((e) => {
98   - if (e?.message) {
99   - createMessage.error(e?.message);
100   - }
101   - handleCancel();
102   - })
103   - .finally(() => {
104   - setTimeout(() => {
105   - loading.value = false;
106   - }, 300);
107   - });
108   - } catch (e) {
109   - throw e;
110   - } finally {
111   - loading.value = false;
112   - }
113   - };
114   - return {
115   - registerForm,
116   - handleCancel,
117   - handleOk,
118   - loading,
119   - };
120   - },
121   - });
122   -</script>
123   -<style scoped lang="less">
124   - .jsoneditor-transform {
125   - background-position: -144px -96px;
126   - display: none !important;
127   - }
128   -
129   - .tabs-detail:deep(.object-model-validate-form) {
130   - > .ant-row {
131   - @apply w-full;
132   - }
133   - }
134   -</style>
... ... @@ -14,7 +14,7 @@
14 14 import { ColEx } from '/@/components/Form/src/types';
15 15 import { useHistoryData } from '../../hook/useHistoryData';
16 16 import { formatToDateTime } from '/@/utils/dateUtil';
17   - import { useTable, BasicTable, BasicColumn } from '/@/components/Table';
  17 + import { useTable, BasicTable, BasicColumn, SorterResult } from '/@/components/Table';
18 18 import {
19 19 ModeSwitchButton,
20 20 TABLE_CHART_MODE_LIST,
... ... @@ -64,7 +64,7 @@
64 64 }
65 65
66 66 const sortOrder = ref<any>('descend');
67   - const columns: BasicColumn[] | any = computed(() => {
  67 + const columns = computed<BasicColumn[]>(() => {
68 68 return [
69 69 {
70 70 title: '属性',
... ... @@ -119,7 +119,7 @@
119 119 loading.value = false;
120 120 };
121 121
122   - const handleTableChange = async (_pag, _filters, sorter: any) => {
  122 + const handleTableChange = async (_pag, _filters, sorter: SorterResult) => {
123 123 sortOrder.value = sorter.order;
124 124 await setColumns(unref(columns));
125 125 if (sorter.field == 'ts') {
... ...
... ... @@ -18,7 +18,8 @@
18 18 import { useGlobSetting } from '/@/hooks/setting';
19 19 import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
20 20 import { toRaw } from 'vue';
21   - import { DataActionModeEnum, ReadAndWriteEnum } from '/@/enums/toolEnum';
  21 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  22 + import { ReadAndWriteEnum } from '/@/enums/deviceEnum';
22 23 import { ObjectModelCommandDeliveryModal } from './ObjectModelCommandDeliveryModal';
23 24 import { ModalParamsType } from '/#/utils';
24 25 import { AreaChartOutlined } from '@ant-design/icons-vue';
... ...
1 1 import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
2 2 import { FormSchema } from '/@/components/Form';
3   -import { validateTCPCustomCommand } from '/@/components/Form/src/externalCompns/components/ThingsModelForm';
  3 +import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
4 4 import { DataTypeEnum } from '/@/enums/objectModelEnum';
5 5
6 6 const InsertString = (t, c, n) => {
... ...
... ... @@ -12,8 +12,8 @@
12 12 import { genModbusCommand } from '/@/api/task';
13 13 import { TaskTypeEnum } from '/@/views/task/center/config';
14 14 import { SingleToHex, formSchemasConfig } from './config';
15   - import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
16 15 import { DataTypeEnum } from '/@/enums/objectModelEnum';
  16 + import { TransportTypeEnum } from '/@/enums/deviceEnum';
17 17
18 18 defineEmits(['register']);
19 19 const props = defineProps<{ deviceId: string; deviceName: string }>();
... ...
... ... @@ -97,7 +97,7 @@
97 97
98 98 const handleControl = (action: number, direction: string) => {
99 99 const organizationId = getId();
100   - controlling({ cameralndexCode: organizationId, action, command: direction });
  100 + controlling({ cameraIndexCode: organizationId, action, command: direction });
101 101 };
102 102
103 103 const isPlay = ref<Boolean | null | undefined>(false);
... ...
... ... @@ -6,7 +6,7 @@
6 6 <template #toolbar>
7 7 <Space>
8 8 <Authority value="api:yt:device:rpc">
9   - <Button type="primary" @click="openModal(true)">命令下发</Button>
  9 + <Button type="primary" @click="handleOpenModal">命令下发</Button>
10 10 </Authority>
11 11 </Space>
12 12 </template>
... ... @@ -24,16 +24,7 @@
24 24 </template>
25 25 </BasicTable>
26 26
27   - <BasicModal
28   - @register="registerCommandIssuanceModal"
29   - width="700px"
30   - title="命令下发"
31   - :showOkBtn="false"
32   - cancelText="关闭"
33   - :footer="null"
34   - >
35   - <CommandIssuance :deviceDetail="deviceDetail" />
36   - </BasicModal>
  27 + <CommandDeliveryModal @register="registerCommandDeliverModal" :deviceDetail="deviceDetail" />
37 28 </template>
38 29 <script lang="ts" setup>
39 30 import { h } from 'vue';
... ... @@ -43,9 +34,10 @@
43 34 import { Button, Modal, Space } from 'ant-design-vue';
44 35 import { JsonPreview } from '/@/components/CodeEditor';
45 36 import { DeviceRecord } from '/@/api/device/model/deviceModel';
46   - import { BasicModal, useModal } from '/@/components/Modal';
47   - import CommandIssuance from '../CommandIssuance.vue';
  37 + import { useModal } from '/@/components/Modal';
  38 + import { CommandDeliveryModal } from '../CommandDeliveryModal';
48 39 import { Authority } from '/@/components/Authority';
  40 + import { DataActionModeEnum } from '/@/enums/toolEnum';
49 41
50 42 const props = defineProps({
51 43 fromId: {
... ... @@ -58,7 +50,7 @@
58 50 },
59 51 });
60 52
61   - const [registerCommandIssuanceModal, { openModal }] = useModal();
  53 + const [registerCommandDeliverModal, { openModal }] = useModal();
62 54
63 55 const [registerTable] = useTable({
64 56 api: deviceCommandRecordGetQuery,
... ... @@ -79,6 +71,7 @@
79 71 },
80 72 useSearchForm: true,
81 73 });
  74 +
82 75 const commonModalInfo = (title, value) => {
83 76 Modal.info({
84 77 title,
... ... @@ -86,14 +79,23 @@
86 79 content: h(JsonPreview, { data: value }),
87 80 });
88 81 };
  82 +
89 83 const handleRecordContent = (record) => {
90 84 if (!record?.request?.body) return;
91 85 if (Object.prototype.toString.call(record?.request?.body) !== '[object Object]') return;
92 86 const jsonParams = record?.request?.body?.params;
93 87 commonModalInfo('命令下发内容', jsonParams);
94 88 };
  89 +
95 90 const handleRecordResponseContent = (record) => {
96 91 const jsonParams = record?.response;
97 92 commonModalInfo('响应内容', jsonParams);
98 93 };
  94 +
  95 + function handleOpenModal() {
  96 + openModal(true, {
  97 + mode: DataActionModeEnum.READ,
  98 + record: props.deviceDetail,
  99 + } as ModalParamsType<DeviceRecord>);
  100 + }
99 101 </script>
... ...
... ... @@ -287,6 +287,7 @@ export function getPacketIntervalByRange(
287 287 break;
288 288 }
289 289 }
  290 + if (!options.length) options = rangeIntervalOption[rangeIntervalOption.length - 1].linkage;
290 291 return options;
291 292 }
292 293 return [];
... ...
  1 +import { unref } from 'vue';
  2 +import { FormFieldsEnum, FormFieldsNameEnum } from '../config';
  3 +import { useObjectModelFormContext } from '../useObjectModelFormContext';
  4 +import { Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  5 +import { findDictItemByCode } from '/@/api/system/dict';
  6 +import { FormSchema } from '/@/components/Form';
  7 +import { TransportTypeEnum } from '/@/enums/deviceEnum';
  8 +import { DictEnum } from '/@/enums/dictEnum';
  9 +import { DataTypeEnum, FunctionTypeEnum } from '/@/enums/objectModelEnum';
  10 +import { isNullOrUnDef } from '/@/utils/is';
  11 +import { ValidatorRule } from 'ant-design-vue/lib/form/interface';
  12 +
  13 +export interface DataTypeFormGetFieldsValueType {
  14 + [FormFieldsEnum.FUNCTION_NAME]: string;
  15 + [FormFieldsEnum.IDENTIFIER]: string;
  16 + [FormFieldsEnum.DATA_TYPE]: DataTypeEnum;
  17 + [FormFieldsEnum.VALUE_RANGE]?: Record<'min' | 'max', number>;
  18 + [FormFieldsEnum.STEP]?: number;
  19 + [FormFieldsEnum.UNIT]?: string;
  20 + [FormFieldsEnum.UNIT_NAME]?: string;
  21 + [FormFieldsEnum.BOOL_CLOSE]?: string;
  22 + [FormFieldsEnum.BOOL_OPEN]?: string;
  23 + [FormFieldsEnum.LENGTH]?: number;
  24 + [FormFieldsEnum.ENUMS_DATA]?: Specs[];
  25 + [FormFieldsEnum.STRUCT_DATA]?: StructJSON[];
  26 + [FormFieldsEnum.REMARK]?: string;
  27 +}
  28 +
  29 +export const validateValueRange: ValidatorRule['validator'] = (
  30 + _rule,
  31 + value: Record<'min' | 'max', number>,
  32 + _callback
  33 +) => {
  34 + value = value || {};
  35 + const { min, max } = value;
  36 + if (min > max) {
  37 + return Promise.reject('最大值小于最小值');
  38 + }
  39 + return Promise.resolve();
  40 +};
  41 +
  42 +export const validateFunctionName: ValidatorRule['validator'] = (_rule, value: any) => {
  43 + if (/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(value)) return Promise.resolve();
  44 + return Promise.reject('支持中文、大小写字母、数字、短划线、下划线.');
  45 +};
  46 +
  47 +export const validateIdentifier: ValidatorRule['validator'] = (_rule, value: any) => {
  48 + if (/^[a-zA-Z0-9_]+$/.test(value)) {
  49 + return Promise.resolve();
  50 + }
  51 + return Promise.reject('支持大小写字母、数字和下划线.');
  52 +};
  53 +
  54 +export const createFunctionNameFormItem = (params: Partial<FormSchema> = {}): FormSchema => {
  55 + return {
  56 + field: FormFieldsEnum.FUNCTION_NAME,
  57 + label: FormFieldsNameEnum.FUNCTION_NAME,
  58 + component: 'Input',
  59 + required: true,
  60 + helpMessage: '支持中文、大小写字母、数字、短划线、下划线。',
  61 + rules: [
  62 + {
  63 + required: true,
  64 + validator: validateFunctionName,
  65 + },
  66 + ],
  67 + componentProps: {
  68 + placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`,
  69 + },
  70 + ...params,
  71 + };
  72 +};
  73 +
  74 +export const createIdentifierFormItem = (params: Partial<FormSchema> = {}): FormSchema => {
  75 + return {
  76 + field: FormFieldsEnum.IDENTIFIER,
  77 + label: FormFieldsNameEnum.IDENTIFIER,
  78 + required: true,
  79 + component: 'Input',
  80 + helpMessage: '支持大小写字母、数字和下划线.',
  81 + componentProps: {
  82 + maxLength: 128,
  83 + placeholder: '请输入标识符',
  84 + },
  85 + rules: [
  86 + {
  87 + required: true,
  88 + validator: validateIdentifier,
  89 + },
  90 + ],
  91 + ...params,
  92 + };
  93 +};
  94 +
  95 +export const getFormSchemas = (dataType: DataTypeEnum[], showRemark: boolean): FormSchema[] => {
  96 + const { getTransportType } = useObjectModelFormContext();
  97 + return [
  98 + createFunctionNameFormItem(),
  99 + createIdentifierFormItem(),
  100 + {
  101 + field: FormFieldsEnum.DATA_TYPE,
  102 + label: FormFieldsNameEnum.DATA_TYPE,
  103 + required: true,
  104 + component: 'ApiSelect',
  105 + defaultValue: DataTypeEnum.NUMBER_INT,
  106 + ifShow: () => !!dataType.length,
  107 + componentProps: () => {
  108 + return {
  109 + placeholder: '请选择数据类型',
  110 + api: async (params: Recordable) => {
  111 + let result = await findDictItemByCode(params);
  112 + if (unref(getTransportType) === TransportTypeEnum.TCP) {
  113 + result = result.filter(
  114 + (item) =>
  115 + ![DataTypeEnum.STRUCT, DataTypeEnum.ENUM].includes(item.itemValue as DataTypeEnum)
  116 + );
  117 + }
  118 + return result.filter((item) => dataType.includes(item.itemValue as DataTypeEnum));
  119 + },
  120 + params: {
  121 + dictCode: DictEnum.DATA_TYPE,
  122 + },
  123 + allowClear: false,
  124 + labelField: 'itemText',
  125 + valueField: 'itemValue',
  126 + getPopupContainer: () => document.body,
  127 + };
  128 + },
  129 + },
  130 + {
  131 + field: FormFieldsEnum.VALUE_RANGE,
  132 + label: FormFieldsNameEnum.VALUE_RANGE,
  133 + component: 'CustomMinMaxInput',
  134 + valueField: 'value',
  135 + changeEvent: 'update:value',
  136 + ifShow: ({ model }) =>
  137 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  138 + model[FormFieldsEnum.FUNCTION_TYPE]
  139 + ) &&
  140 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  141 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  142 + dynamicRules: ({ model }) => [
  143 + {
  144 + required:
  145 + model[FormFieldsEnum.VALUE_RANGE]?.min || model[FormFieldsEnum.VALUE_RANGE]?.max,
  146 + validator: validateValueRange,
  147 + },
  148 + ],
  149 + },
  150 + {
  151 + field: FormFieldsEnum.STEP,
  152 + label: FormFieldsNameEnum.STEP,
  153 + component: 'InputNumber',
  154 + componentProps: {
  155 + maxLength: 255,
  156 + placeholder: '请输入步长',
  157 + min: 1,
  158 + formatter: (value: number | string) => {
  159 + return value ? Math.floor(Number(value)) : value;
  160 + },
  161 + },
  162 + ifShow: ({ model }) =>
  163 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  164 + model[FormFieldsEnum.FUNCTION_TYPE]
  165 + ) &&
  166 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  167 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  168 + dynamicRules: ({ model }) => {
  169 + const valueRange = model[FormFieldsEnum.VALUE_RANGE] || {};
  170 + const { min, max } = valueRange;
  171 + const step = model[FormFieldsEnum.STEP];
  172 + return [
  173 + {
  174 + validator: () => {
  175 + if ([min, max].every(isNullOrUnDef)) return Promise.resolve();
  176 + if (step > max - min) {
  177 + return Promise.reject('步长不能大于取值范围的差值');
  178 + }
  179 + return Promise.resolve();
  180 + },
  181 + },
  182 + ];
  183 + },
  184 + },
  185 + {
  186 + field: FormFieldsEnum.UNIT_NAME,
  187 + label: FormFieldsNameEnum.UNIT_NAME,
  188 + component: 'Input',
  189 + show: false,
  190 + },
  191 + {
  192 + field: FormFieldsEnum.UNIT,
  193 + label: FormFieldsNameEnum.UNIT,
  194 + component: 'ApiSelect',
  195 + ifShow: ({ model }) =>
  196 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  197 + model[FormFieldsEnum.FUNCTION_TYPE]
  198 + ) &&
  199 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  200 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  201 + componentProps: ({ formActionType }) => {
  202 + const { setFieldsValue } = formActionType;
  203 + return {
  204 + placeholder: '请选择单位',
  205 + api: async (params: Recordable) => {
  206 + const list = await findDictItemByCode(params);
  207 + list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`));
  208 + return list;
  209 + },
  210 + params: {
  211 + dictCode: DictEnum.ATTRIBUTE_UNIT,
  212 + },
  213 + labelField: 'itemText',
  214 + valueField: 'itemValue',
  215 + onChange(_, record: Record<'label' | 'value', string>) {
  216 + if (record) {
  217 + const { label } = record;
  218 + setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label });
  219 + }
  220 + },
  221 + getPopupContainer: () => document.body,
  222 + showSearch: true,
  223 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  224 + let { label, value } = option;
  225 + label = label.toLowerCase();
  226 + value = value.toLowerCase();
  227 + inputValue = inputValue.toLowerCase();
  228 + return label.includes(inputValue) || value.includes(inputValue);
  229 + },
  230 + };
  231 + },
  232 + },
  233 + {
  234 + field: FormFieldsEnum.BOOL_CLOSE,
  235 + component: 'Input',
  236 + required: true,
  237 + label: FormFieldsNameEnum.BOOL_CLOSE,
  238 + componentProps: {
  239 + placeholder: '如:关',
  240 + },
  241 + defaultValue: '关',
  242 + ifShow: ({ model }) =>
  243 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  244 + model[FormFieldsEnum.FUNCTION_TYPE]
  245 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
  246 + dynamicRules: ({ model }) => {
  247 + const close = model[FormFieldsEnum.BOOL_CLOSE];
  248 + const open = model[FormFieldsEnum.BOOL_OPEN];
  249 + return [
  250 + {
  251 + required: true,
  252 + message: `布尔值不能为空`,
  253 + },
  254 + {
  255 + validator() {
  256 + if (open === close) return Promise.reject('布尔值不能相同');
  257 + return Promise.resolve();
  258 + },
  259 + },
  260 + ];
  261 + },
  262 + },
  263 + {
  264 + field: FormFieldsEnum.BOOL_OPEN,
  265 + component: 'Input',
  266 + required: true,
  267 + label: FormFieldsNameEnum.BOOL_OPEN,
  268 + componentProps: {
  269 + placeholder: '如:开',
  270 + },
  271 + defaultValue: '开',
  272 + ifShow: ({ model }) =>
  273 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  274 + model[FormFieldsEnum.FUNCTION_TYPE]
  275 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
  276 + dynamicRules: ({ model }) => {
  277 + const close = model[FormFieldsEnum.BOOL_CLOSE];
  278 + const open = model[FormFieldsEnum.BOOL_OPEN];
  279 + return [
  280 + {
  281 + required: true,
  282 + message: `布尔值不能为空`,
  283 + },
  284 + {
  285 + validator() {
  286 + if (open === close) return Promise.reject('布尔值不能相同');
  287 + return Promise.resolve();
  288 + },
  289 + },
  290 + ];
  291 + },
  292 + },
  293 + {
  294 + field: FormFieldsEnum.LENGTH,
  295 + component: 'InputNumber',
  296 + required: true,
  297 + label: FormFieldsNameEnum.LENGTH,
  298 + defaultValue: 1024,
  299 + componentProps: {
  300 + placeholder: '请输入数据长度',
  301 + },
  302 + ifShow: ({ model }) =>
  303 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  304 + model[FormFieldsEnum.FUNCTION_TYPE]
  305 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRING,
  306 + },
  307 + {
  308 + field: FormFieldsEnum.ENUMS_DATA,
  309 + label: FormFieldsNameEnum.ENUMS_DATA,
  310 + component: 'Input',
  311 + slot: FormFieldsEnum.ENUMS_DATA,
  312 + changeEvent: 'update:value',
  313 + valueField: 'value',
  314 + ifShow: ({ model }) =>
  315 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  316 + model[FormFieldsEnum.FUNCTION_TYPE]
  317 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.ENUM,
  318 + },
  319 + {
  320 + field: FormFieldsEnum.STRUCT_DATA,
  321 + label: FormFieldsNameEnum.STRUCT_DATA,
  322 + component: 'Input',
  323 + slot: FormFieldsEnum.STRUCT_DATA,
  324 + changeEvent: 'update:value',
  325 + valueField: 'value',
  326 + required: true,
  327 + ifShow: ({ model }) =>
  328 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRUCT &&
  329 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  330 + model[FormFieldsEnum.FUNCTION_TYPE]
  331 + ),
  332 + },
  333 +
  334 + {
  335 + field: FormFieldsEnum.REMARK,
  336 + label: FormFieldsNameEnum.REMARK,
  337 + component: 'InputTextArea',
  338 + ifShow: showRemark,
  339 + componentProps: {
  340 + rows: 4,
  341 + maxLength: 100,
  342 + placeholder: '请输入描述',
  343 + },
  344 + },
  345 + ];
  346 +};
... ...
  1 +export { default as DataTypeForm } from './index.vue';
... ...
  1 +<script setup lang="ts">
  2 + import { useForm, BasicForm } from '/@/components/Form';
  3 + import { getFormSchemas } from './config';
  4 + import { EnumList } from '../EnumList';
  5 + import { ref } from 'vue';
  6 + import { StructFormItem } from '../StructFormItem';
  7 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  8 + import { useDataTypeFormData } from './useDataTypeFormData';
  9 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  10 +
  11 + const props = withDefaults(
  12 + defineProps<{
  13 + dataType?: DataTypeEnum[];
  14 + mode?: DataActionModeEnum;
  15 + showRemark?: boolean;
  16 + }>(),
  17 + {
  18 + dataType: () => Object.values(DataTypeEnum),
  19 + showRemark: false,
  20 + }
  21 + );
  22 +
  23 + defineEmits<{
  24 + (event: 'field-value-change', field: string, value: any): void;
  25 + }>();
  26 +
  27 + const [register, formActionType] = useForm({
  28 + schemas: getFormSchemas(props.dataType, props.showRemark),
  29 + layout: 'vertical',
  30 + showActionButtonGroup: false,
  31 + });
  32 +
  33 + const enumListRef = ref<InstanceType<typeof EnumList>>();
  34 +
  35 + const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } = useDataTypeFormData({
  36 + formActionType,
  37 + enumListRef,
  38 + });
  39 +
  40 + defineExpose({ getFieldsValue, setFieldsValue, validate, resetFieldsValue });
  41 +</script>
  42 +
  43 +<template>
  44 + <BasicForm
  45 + @register="register"
  46 + class="data-type-form"
  47 + :disabled="mode === DataActionModeEnum.READ"
  48 + @field-value-change="(field, value) => $emit('field-value-change', field, value)"
  49 + >
  50 + <template #enumsData="{ model, field }">
  51 + <EnumList
  52 + ref="enumListRef"
  53 + v-model:value="model[field]"
  54 + :disabled="mode === DataActionModeEnum.READ"
  55 + />
  56 + </template>
  57 + <template #structData="{ model, field }">
  58 + <StructFormItem
  59 + v-model:value="model[field]"
  60 + :mode="mode"
  61 + :dataType="[
  62 + DataTypeEnum.BOOL,
  63 + DataTypeEnum.ENUM,
  64 + DataTypeEnum.NUMBER_DOUBLE,
  65 + DataTypeEnum.NUMBER_INT,
  66 + DataTypeEnum.STRING,
  67 + ]"
  68 + />
  69 + </template>
  70 + </BasicForm>
  71 +</template>
  72 +
  73 +<style lang="less" scoped>
  74 + .data-type-form {
  75 + :deep(.ant-input-number) {
  76 + width: 100%;
  77 + }
  78 + }
  79 +</style>
  80 +./config
... ...
  1 +import { Ref, unref } from 'vue';
  2 +import { FormActionType } from '/@/components/Form';
  3 +import EnumList from './EnumList.vue';
  4 +import { DefineComponentsBasicExpose } from '/#/utils';
  5 +import { DataTypeFormGetFieldsValueType } from './config';
  6 +import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  7 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  8 +import { isObject } from '/@/utils/is';
  9 +
  10 +interface UseDataTypeFormDataParams {
  11 + formActionType: FormActionType;
  12 + enumListRef: Ref<InstanceType<typeof EnumList> | undefined>;
  13 +}
  14 +
  15 +function getDataType(value: DataTypeFormGetFieldsValueType, enumListFormValue?: Specs[]): DataType {
  16 + const { dataType, valueRange, unit, unitName, step, length, boolClose, boolOpen, structData } =
  17 + value;
  18 +
  19 + const basic: DataType = { type: dataType };
  20 +
  21 + if (dataType === DataTypeEnum.BOOL) {
  22 + basic.specs = {
  23 + boolClose,
  24 + boolOpen,
  25 + };
  26 + } else if (dataType === DataTypeEnum.ENUM) {
  27 + basic.specsList = enumListFormValue;
  28 + } else if ([DataTypeEnum.NUMBER_DOUBLE, DataTypeEnum.NUMBER_INT].includes(dataType)) {
  29 + basic.specs = {
  30 + valueRange,
  31 + unit,
  32 + unitName,
  33 + step,
  34 + };
  35 + } else if (dataType === DataTypeEnum.STRING) {
  36 + basic.specs = {
  37 + length,
  38 + };
  39 + } else if (dataType === DataTypeEnum.STRUCT) {
  40 + basic.specs = structData || [];
  41 + }
  42 +
  43 + return basic;
  44 +}
  45 +
  46 +export function useDataTypeFormData({
  47 + formActionType,
  48 + enumListRef,
  49 +}: UseDataTypeFormDataParams): DefineComponentsBasicExpose<StructJSON> {
  50 + const getFieldsValue = (): StructJSON => {
  51 + const value = formActionType.getFieldsValue() as DataTypeFormGetFieldsValueType;
  52 + const enumListFormValue = unref(enumListRef)?.getFieldsValue();
  53 + const { functionName, identifier, remark } = value;
  54 +
  55 + return {
  56 + functionName,
  57 + identifier,
  58 + remark,
  59 + dataType: getDataType(value, enumListFormValue),
  60 + };
  61 + };
  62 +
  63 + const setFieldsValue = (record: StructJSON) => {
  64 + const { dataType, identifier, functionName, remark } = record;
  65 + const { type, specs, specsList = [] } = dataType || {};
  66 +
  67 + // 兼容处理 unit
  68 + if (isObject(specs) && isObject((specs as Specs).unit)) {
  69 + (specs as Specs).unit = ((specs as Specs).unit as unknown as Record<'value', string>)?.value;
  70 + }
  71 +
  72 + // 兼容处理 length 数据长度类型
  73 + if (isObject(specs) && isObject((specs as Specs).length)) {
  74 + (specs as Specs).length = Number((specs as Specs).length);
  75 + }
  76 +
  77 + formActionType.setFieldsValue({
  78 + dataType: type,
  79 + identifier,
  80 + functionName,
  81 + remark,
  82 + ...(isObject(specs) ? specs : {}),
  83 + structData: type === DataTypeEnum.STRUCT ? specs : [],
  84 + enumsData: type === DataTypeEnum.ENUM ? specsList : [],
  85 + } as DataTypeFormGetFieldsValueType);
  86 + };
  87 +
  88 + const validate = async () => {
  89 + await formActionType.validate();
  90 + await unref(enumListRef)?.validate?.();
  91 + };
  92 +
  93 + const resetFieldsValue = () => {
  94 + formActionType.resetFields();
  95 + };
  96 +
  97 + return {
  98 + validate,
  99 + getFieldsValue,
  100 + setFieldsValue,
  101 + resetFieldsValue,
  102 + };
  103 +}
... ...
src/views/device/profiles/components/ObjectModelForm/EnumList/config.ts renamed from src/components/Form/src/externalCompns/components/StructForm/EnumList.config.ts
1   -import { FormSchema } from '/@/components/Table';
  1 +import { FormSchema } from '/@/components/Form';
2 2
3 3 export enum FormFieldsEnum {
4 4 VALUE = 'value',
... ... @@ -41,14 +41,7 @@ export const getFormSchemas = (): FormSchema[] => {
41 41 field: FormFieldsEnum.NAME,
42 42 label: '',
43 43 component: 'Input',
44   - rules: [
45   - {
46   - required: true,
47   - message: `支持中文、英文大小写、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符`,
48   - type: 'string',
49   - pattern: /^[a-zA-Z0-9\u4e00-\u9fa5a][\u4e00-\u9fa5a-zA-Z0-9_-]*$/,
50   - },
51   - ],
  44 + rules: [{ required: true, message: `参数描述不能为空`, type: 'string' }],
52 45 componentProps: () => {
53 46 return {
54 47 placeholder: '对该枚举项的描述',
... ...
  1 +export { default as EnumList } from './index.vue';
... ...
src/views/device/profiles/components/ObjectModelForm/EnumList/index.vue renamed from src/components/Form/src/externalCompns/components/StructForm/EnumList.vue
1 1 <script setup lang="ts">
2 2 import { Button, Tooltip } from 'ant-design-vue';
3 3 import { computed, nextTick, ref, unref, watch } from 'vue';
4   - import { useForm, BasicForm } from '/@/components/Form';
  4 + import { useForm, BasicForm, FormActionType } from '/@/components/Form';
5 5 import { Specs } from '/@/api/device/model/modelOfMatterModel';
6 6 import { Icon } from '/@/components/Icon';
7   - import { getFormSchemas } from './EnumList.config';
8   - import { FormActionType } from '../../../types/form';
  7 + import { getFormSchemas } from './config';
9 8 import { buildUUID } from '/@/utils/uuid';
10 9 import { DataTypeEnum } from '/@/enums/objectModelEnum';
11 10 import { isNullOrUnDef } from '/@/utils/is';
... ... @@ -65,7 +64,6 @@
65 64
66 65 const setFieldsValue = (spaceList: Specs[]) => {
67 66 enumsListElRef.value = spaceList.map((item) => ({ uuid: buildUUID(), dataSource: item }));
68   -
69 67 nextTick(() => {
70 68 unref(enumsListElRef).forEach((item) =>
71 69 item.formActionType?.setFieldsValue?.(item.dataSource)
... ... @@ -76,10 +74,7 @@
76 74 const handleDeleteEnums = (item: EnumElItemType) => {
77 75 const index = unref(enumsListElRef).findIndex((temp) => item.uuid === temp.uuid);
78 76
79   - if (~index) {
80   - enumsListElRef.value.splice(index, 1);
81   - validateSameEnum();
82   - }
  77 + ~index && enumsListElRef.value.splice(index, 1);
83 78 };
84 79
85 80 const handleAddEnums = () => {
... ... @@ -107,15 +102,17 @@
107 102 <section class="w-full">
108 103 <header class="flex h-8 items-center">
109 104 <div class="w-1/2">
110   - <span>参考值</span>
  105 + <span class="mr-1 text-red-400">*</span>
  106 + <span> 参考值 </span>
111 107 <Tooltip title="支持整型,取值范围:-2147483648 ~ 2147483647">
112 108 <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" />
113 109 </Tooltip>
114 110 </div>
115 111 <div class="w-1/2">
116   - <span>参考描述</span>
  112 + <span class="mr-1 text-red-400">*</span>
  113 + <span> 参考描述 </span>
117 114 <Tooltip
118   - title="支持中文、英文大小写、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符"
  115 + title="支持中文、英文大小写、日文、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符"
119 116 >
120 117 <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" />
121 118 </Tooltip>
... ...
src/views/device/profiles/components/ObjectModelForm/ExtendDesc/config.ts renamed from src/components/Form/src/externalCompns/components/ExtendDesc/config.ts
  1 +import { unref } from 'vue';
  2 +import { useObjectModelFormContext } from '../useObjectModelFormContext';
1 3 import { findDictItemByCode } from '/@/api/system/dict';
2 4 import { FormSchema } from '/@/components/Table';
3 5 import { DictEnum } from '/@/enums/dictEnum';
... ... @@ -37,66 +39,62 @@ function getActionTypeByObjectModelType(dataType: DataTypeEnum) {
37 39 return list;
38 40 }
39 41
40   -export const formSchemas: FormSchema[] = [
41   - {
42   - field: FormFieldsEnum.OBJECT_MODEL_TYPE,
43   - component: 'Input',
44   - label: '物模型数据类型',
45   - ifShow: false,
46   - },
47   - {
48   - field: FormFieldsEnum.REGISTER_ADDRESS,
49   - component: 'RegisterAddressInput',
50   - label: '寄存器地址',
51   - changeEvent: 'update:value',
52   - valueField: 'value',
53   - rules: [{ message: '请输入寄存器地址', required: true, type: 'number' }],
54   - componentProps: {
55   - placeholder: '请输入寄存器地址',
56   - },
57   - },
58   - {
59   - field: FormFieldsEnum.DATA_TYPE,
60   - component: 'ApiSelect',
61   - label: '数据格式',
62   - rules: [{ message: '请选择数据格式', required: true }],
63   - defaultValue: RegisterDataTypeEnum.UN_SHORT,
64   - componentProps: {
65   - api: findDictItemByCode,
66   - params: {
67   - dictCode: DictEnum.REGISTER_DATA_FORMAT,
  42 +export const getFormSchemas = (): FormSchema[] => {
  43 + const { getDataType } = useObjectModelFormContext();
  44 + return [
  45 + {
  46 + field: FormFieldsEnum.REGISTER_ADDRESS,
  47 + component: 'RegisterAddressInput',
  48 + label: '寄存器地址',
  49 + changeEvent: 'update:value',
  50 + valueField: 'value',
  51 + rules: [{ message: '请输入寄存器地址', required: true, type: 'number' }],
  52 + componentProps: {
  53 + placeholder: '请输入寄存器地址',
68 54 },
69   - labelField: 'itemText',
70   - valueField: 'itemValue',
71   - placeholder: '请选择数据格式',
72   - getPopupContainer: () => document.body,
73 55 },
74   - },
75   - {
76   - field: FormFieldsEnum.ACTION_TYPE,
77   - component: 'Select',
78   - label: '操作类型',
79   - rules: [{ message: '请选择操作类型', required: true }],
80   - componentProps: ({ formModel }) => {
81   - const objectModelType = formModel[FormFieldsEnum.OBJECT_MODEL_TYPE];
82   - return {
83   - options: getActionTypeByObjectModelType(objectModelType),
84   - placeholder: '请选择操作类型',
  56 + {
  57 + field: FormFieldsEnum.DATA_TYPE,
  58 + component: 'ApiSelect',
  59 + label: '数据格式',
  60 + rules: [{ message: '请选择数据格式', required: true }],
  61 + defaultValue: RegisterDataTypeEnum.UN_SHORT,
  62 + componentProps: {
  63 + api: findDictItemByCode,
  64 + params: {
  65 + dictCode: DictEnum.REGISTER_DATA_FORMAT,
  66 + },
  67 + labelField: 'itemText',
  68 + valueField: 'itemValue',
  69 + placeholder: '请选择数据格式',
85 70 getPopupContainer: () => document.body,
86   - };
  71 + },
87 72 },
88   - },
89   - {
90   - field: FormFieldsEnum.ZOOM_FACTOR,
91   - component: 'InputNumber',
92   - label: '缩放因子',
93   - helpMessage: ['缩放因子不能为0,默认为1'],
94   - defaultValue: 1,
95   - ifShow: ({ model }) =>
96   - ![DataTypeEnum.BOOL, DataTypeEnum.STRING].includes(model[FormFieldsEnum.OBJECT_MODEL_TYPE]),
97   - componentProps: {
98   - min: 1,
99   - placeholder: '请输入缩放因子',
  73 + {
  74 + field: FormFieldsEnum.ACTION_TYPE,
  75 + component: 'Select',
  76 + label: '操作类型',
  77 + rules: [{ message: '请选择操作类型', required: true }],
  78 + componentProps: () => {
  79 + return {
  80 + options: getActionTypeByObjectModelType(unref(getDataType)),
  81 + placeholder: '请选择操作类型',
  82 + getPopupContainer: () => document.body,
  83 + };
  84 + },
  85 + },
  86 + {
  87 + field: FormFieldsEnum.ZOOM_FACTOR,
  88 + component: 'InputNumber',
  89 + label: '缩放因子',
  90 + helpMessage: ['缩放因子不能为0,默认为1'],
  91 + defaultValue: 1,
  92 + ifShow: ({ model }) =>
  93 + ![DataTypeEnum.BOOL, DataTypeEnum.STRING].includes(model[FormFieldsEnum.OBJECT_MODEL_TYPE]),
  94 + componentProps: {
  95 + min: 1,
  96 + placeholder: '请输入缩放因子',
  97 + },
100 98 },
101   - },
102   -];
  99 + ];
  100 +};
... ...
  1 +export { default as ExtendDesc } from './index.vue';
... ...
src/views/device/profiles/components/ObjectModelForm/ExtendDesc/index.vue renamed from src/components/Form/src/externalCompns/components/ExtendDesc/index.vue
... ... @@ -4,19 +4,18 @@
4 4 import { BasicForm, useForm } from '/@/components/Form';
5 5 import { BasicModal } from '/@/components/Modal';
6 6 import { PlusCircleOutlined } from '@ant-design/icons-vue';
7   - import { FormFieldsEnum, formSchemas } from './config';
8   - import { DataTypeEnum } from '/@/enums/objectModelEnum';
  7 + import { getFormSchemas } from './config';
  8 + import { ExtensionDesc } from '/@/api/device/model/modelOfMatterModel';
9 9
10 10 const show = ref(false);
11 11
12 12 const props = withDefaults(
13 13 defineProps<{
14   - value?: object;
  14 + value?: ExtensionDesc;
15 15 disabled?: boolean;
16   - dataType?: DataTypeEnum;
17 16 }>(),
18 17 {
19   - value: () => ({}),
  18 + value: () => ({} as ExtensionDesc),
20 19 }
21 20 );
22 21
... ... @@ -24,7 +23,7 @@
24 23
25 24 const [registerForm, { setFieldsValue, getFieldsValue, setProps, validate, resetFields }] =
26 25 useForm({
27   - schemas: formSchemas,
  26 + schemas: getFormSchemas(),
28 27 showActionButtonGroup: false,
29 28 });
30 29
... ... @@ -39,7 +38,6 @@
39 38 const handleSubmit = async () => {
40 39 await validate();
41 40 const value = getFieldsValue();
42   - Reflect.deleteProperty(value, FormFieldsEnum.OBJECT_MODEL_TYPE);
43 41 emit('update:value', value);
44 42 show.value = false;
45 43 };
... ... @@ -47,22 +45,15 @@
47 45 watch(show, async (value) => {
48 46 if (value) {
49 47 await nextTick();
50   - setFieldsValue({ [FormFieldsEnum.OBJECT_MODEL_TYPE]: props.dataType });
  48 + setFieldsValue({ ...props.value });
51 49 }
52 50 });
53   -
54   - watch(
55   - () => props.value,
56   - (value) => {
57   - setFieldsValue(value);
58   - }
59   - );
60 51 </script>
61 52
62 53 <template>
63 54 <section>
64 55 <Button type="link" @click="handleClick"><PlusCircleOutlined />新增扩展描述</Button>
65   - <BasicModal title="扩展描述" v-model:visible="show" @ok="handleSubmit">
  56 + <BasicModal title="扩展描述" v-model:visible="show" @ok="handleSubmit" :show-ok-btn="!disabled">
66 57 <BasicForm class="extension-form" @register="registerForm" />
67 58 </BasicModal>
68 59 </section>
... ...
  1 +<script setup lang="ts">
  2 + import { BasicModal, useModalInner } from '/@/components/Modal';
  3 + import { ref, unref } from 'vue';
  4 + import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  5 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  6 + import { DataTypeForm } from '../DataTypeForm';
  7 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  8 + import { useMessage } from '/@/hooks/web/useMessage';
  9 +
  10 + const props = withDefaults(
  11 + defineProps<{ value?: StructJSON[]; dataType?: DataTypeEnum[]; mode?: DataActionModeEnum }>(),
  12 + {
  13 + value: () => [],
  14 + dataType: () => Object.values(DataTypeEnum),
  15 + }
  16 + );
  17 +
  18 + const dataTypeFormRef = ref<InstanceType<typeof DataTypeForm>>();
  19 +
  20 + const emits = defineEmits(['register', 'complete']);
  21 +
  22 + const currentMode = ref(DataActionModeEnum.CREATE);
  23 + const [registerModal] = useModalInner(({ mode, record }: ModalParamsType<StructJSON>) => {
  24 + unref(dataTypeFormRef)?.resetFieldsValue?.();
  25 + currentMode.value = mode;
  26 + if (mode === DataActionModeEnum.UPDATE) {
  27 + unref(dataTypeFormRef)?.setFieldsValue?.(record);
  28 + }
  29 + });
  30 +
  31 + const { createMessage } = useMessage();
  32 +
  33 + const handleValidateHasSameIdentifier = (formValue?: StructJSON) => {
  34 + const { identifier } = formValue || {};
  35 + if (
  36 + unref(currentMode) === DataActionModeEnum.CREATE &&
  37 + props.value.filter((item) => item.identifier === identifier).length >= 1
  38 + ) {
  39 + const message = '存在一致的标识符';
  40 + createMessage.warn(message);
  41 + return Promise.reject(message);
  42 + }
  43 +
  44 + return Promise.resolve();
  45 + };
  46 +
  47 + const handleOk = async () => {
  48 + await unref(dataTypeFormRef)?.validate?.();
  49 + const value = unref(dataTypeFormRef)?.getFieldsValue?.();
  50 + await handleValidateHasSameIdentifier(value);
  51 + emits('complete', value);
  52 + };
  53 +</script>
  54 +
  55 +<template>
  56 + <BasicModal
  57 + @register="registerModal"
  58 + title="新增参数"
  59 + @ok="handleOk"
  60 + :width="480"
  61 + :show-ok-btn="mode !== DataActionModeEnum.READ"
  62 + >
  63 + <DataTypeForm ref="dataTypeFormRef" :data-type="dataType" show-remark :mode="mode" />
  64 + </BasicModal>
  65 +</template>
... ...
  1 +import { FormFieldsEnum, FormFieldsNameEnum } from '../config';
  2 +import { findDictItemByCode } from '/@/api/system/dict';
  3 +import { FormSchema } from '/@/components/Form';
  4 +import { DictEnum } from '/@/enums/dictEnum';
  5 +import { DataTypeEnum, FunctionTypeEnum, FunctionTypeNameEnum } from '/@/enums/objectModelEnum';
  6 +import { isNullOrUnDef } from '/@/utils/is';
  7 +
  8 +export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => {
  9 + value = value || {};
  10 + const { min, max } = value;
  11 + if (min > max) {
  12 + return Promise.reject('最大值小于最小值');
  13 + }
  14 + return Promise.resolve();
  15 +};
  16 +
  17 +export const getFormSchemas = (): FormSchema[] => {
  18 + return [
  19 + {
  20 + field: FormFieldsEnum.FUNCTION_TYPE,
  21 + label: FormFieldsNameEnum.FUNCTION_TYPE,
  22 + component: 'Segmented',
  23 + defaultValue: FunctionTypeEnum.PROPERTIES,
  24 + required: true,
  25 + componentProps: ({ formActionType }) => {
  26 + return {
  27 + options: Object.keys(FunctionTypeEnum).map((key) => ({
  28 + title: FunctionTypeNameEnum[key],
  29 + value: FunctionTypeEnum[key],
  30 + })),
  31 + onChange() {
  32 + const { setFieldsValue, clearValidate } = formActionType;
  33 + setFieldsValue({
  34 + [FormFieldsEnum.INPUT_DATA]: [],
  35 + [FormFieldsEnum.OUTPUT_DATA]: [],
  36 + [FormFieldsEnum.STRUCT_DATA]: [],
  37 + [FormFieldsEnum.SERVICE_COMMAND]: null,
  38 + });
  39 + clearValidate();
  40 + },
  41 + };
  42 + },
  43 + },
  44 + {
  45 + field: FormFieldsEnum.FUNCTION_NAME,
  46 + label: FormFieldsNameEnum.FUNCTION_NAME,
  47 + component: 'Input',
  48 + required: true,
  49 + dynamicRules: ({ values }) => {
  50 + return [
  51 + { required: true, message: '请输入功能名称' },
  52 + {
  53 + validator: () => {
  54 + const reg = /[,,]+/;
  55 + if (reg.test(values?.[FormFieldsEnum.FUNCTION_NAME])) {
  56 + return Promise.reject(`${FormFieldsNameEnum.FUNCTION_NAME}不能包含逗号`);
  57 + }
  58 + return Promise.resolve();
  59 + },
  60 + },
  61 + ];
  62 + },
  63 + componentProps: {
  64 + placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`,
  65 + },
  66 + },
  67 + {
  68 + field: FormFieldsEnum.IDENTIFIER,
  69 + label: FormFieldsNameEnum.IDENTIFIER,
  70 + required: true,
  71 + component: 'Input',
  72 + componentProps: {
  73 + maxLength: 128,
  74 + placeholder: '请输入标识符',
  75 + },
  76 + dynamicRules: ({ values }) => {
  77 + return [
  78 + { required: true, message: '请输入标识符' },
  79 + {
  80 + validator: () => {
  81 + const reg = /[,,]+/;
  82 + if (reg.test(values?.[FormFieldsEnum.IDENTIFIER])) {
  83 + return Promise.reject(`${FormFieldsNameEnum.IDENTIFIER}不能包含逗号`);
  84 + }
  85 + return Promise.resolve();
  86 + },
  87 + },
  88 + ];
  89 + },
  90 + },
  91 + {
  92 + field: FormFieldsEnum.DATA_TYPE,
  93 + label: FormFieldsNameEnum.DATA_TYPE,
  94 + required: true,
  95 + component: 'ApiSelect',
  96 + defaultValue: DataTypeEnum.NUMBER_INT,
  97 + ifShow: ({ model }) =>
  98 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  99 + model[FormFieldsEnum.FUNCTION_TYPE]
  100 + ),
  101 + componentProps: () => {
  102 + return {
  103 + placeholder: '请选择数据类型',
  104 + api: async (params: Recordable) => {
  105 + const result = await findDictItemByCode(params);
  106 + return result;
  107 + },
  108 + params: {
  109 + dictCode: DictEnum.DATA_TYPE,
  110 + },
  111 + labelField: 'itemText',
  112 + valueField: 'itemValue',
  113 + getPopupContainer: () => document.body,
  114 + };
  115 + },
  116 + },
  117 + {
  118 + field: FormFieldsEnum.VALUE_RANGE,
  119 + label: FormFieldsNameEnum.VALUE_RANGE,
  120 + component: 'CustomMinMaxInput',
  121 + valueField: 'value',
  122 + changeEvent: 'update:value',
  123 + ifShow: ({ model }) =>
  124 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  125 + model[FormFieldsEnum.FUNCTION_TYPE]
  126 + ) &&
  127 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  128 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  129 + rules: [{ validator: validateValueRange }],
  130 + },
  131 + {
  132 + field: FormFieldsEnum.STEP,
  133 + label: FormFieldsNameEnum.STEP,
  134 + component: 'InputNumber',
  135 + componentProps: {
  136 + maxLength: 255,
  137 + placeholder: '请输入步长',
  138 + min: 1,
  139 + formatter: (value: number | string) => {
  140 + return value ? Math.floor(Number(value)) : value;
  141 + },
  142 + },
  143 + ifShow: ({ model }) =>
  144 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  145 + model[FormFieldsEnum.FUNCTION_TYPE]
  146 + ) &&
  147 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  148 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  149 + dynamicRules: ({ model }) => {
  150 + const valueRange = model[FormFieldsEnum.VALUE_RANGE] || {};
  151 + const { min, max } = valueRange;
  152 + const step = model[FormFieldsEnum.STEP];
  153 + return [
  154 + {
  155 + validator: () => {
  156 + if ([min, max].every(isNullOrUnDef)) return Promise.resolve();
  157 + if (step > max - min) {
  158 + return Promise.reject('步长不能大于取值范围的差值');
  159 + }
  160 + return Promise.resolve();
  161 + },
  162 + },
  163 + ];
  164 + },
  165 + },
  166 + {
  167 + field: FormFieldsEnum.UNIT_NAME,
  168 + label: FormFieldsNameEnum.UNIT_NAME,
  169 + component: 'Input',
  170 + show: false,
  171 + },
  172 + {
  173 + field: FormFieldsEnum.UNIT,
  174 + label: FormFieldsNameEnum.UNIT,
  175 + component: 'ApiSelect',
  176 + componentProps: ({ formActionType }) => {
  177 + const { setFieldsValue } = formActionType;
  178 + return {
  179 + placeholder: '请选择单位',
  180 + api: async (params: Recordable) => {
  181 + const list = await findDictItemByCode(params);
  182 + list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`));
  183 + return list;
  184 + },
  185 + params: {
  186 + dictCode: DictEnum.ATTRIBUTE_UNIT,
  187 + },
  188 + labelInValue: true,
  189 + labelField: 'itemText',
  190 + valueField: 'itemValue',
  191 + onChange(_, record: Record<'label' | 'value', string>) {
  192 + if (record) {
  193 + const { label } = record;
  194 + setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label });
  195 + }
  196 + },
  197 + getPopupContainer: () => document.body,
  198 + showSearch: true,
  199 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => {
  200 + let { label, value } = option;
  201 + label = label.toLowerCase();
  202 + value = value.toLowerCase();
  203 + inputValue = inputValue.toLowerCase();
  204 + return label.includes(inputValue) || value.includes(inputValue);
  205 + },
  206 + };
  207 + },
  208 + ifShow: ({ model }) =>
  209 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  210 + model[FormFieldsEnum.FUNCTION_TYPE]
  211 + ) &&
  212 + (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT ||
  213 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE),
  214 + },
  215 + {
  216 + field: FormFieldsEnum.BOOL_CLOSE,
  217 + component: 'Input',
  218 + required: true,
  219 + label: FormFieldsNameEnum.BOOL_CLOSE,
  220 + componentProps: {
  221 + placeholder: '如:关',
  222 + },
  223 + defaultValue: '关',
  224 + ifShow: ({ model }) =>
  225 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  226 + model[FormFieldsEnum.FUNCTION_TYPE]
  227 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
  228 + dynamicRules: ({ model }) => {
  229 + const close = model[FormFieldsEnum.BOOL_CLOSE];
  230 + const open = model[FormFieldsEnum.BOOL_OPEN];
  231 + return [
  232 + {
  233 + required: true,
  234 + message: `布尔值不能为空`,
  235 + },
  236 + {
  237 + validator() {
  238 + if (open === close) return Promise.reject('布尔值不能相同');
  239 + return Promise.resolve();
  240 + },
  241 + },
  242 + ];
  243 + },
  244 + },
  245 + {
  246 + field: FormFieldsEnum.BOOL_OPEN,
  247 + component: 'Input',
  248 + required: true,
  249 + label: FormFieldsNameEnum.BOOL_OPEN,
  250 + componentProps: {
  251 + placeholder: '如:开',
  252 + },
  253 + defaultValue: '开',
  254 + ifShow: ({ model }) =>
  255 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  256 + model[FormFieldsEnum.FUNCTION_TYPE]
  257 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL,
  258 + dynamicRules: ({ model }) => {
  259 + const close = model[FormFieldsEnum.BOOL_CLOSE];
  260 + const open = model[FormFieldsEnum.BOOL_OPEN];
  261 + return [
  262 + {
  263 + required: true,
  264 + message: `布尔值不能为空`,
  265 + },
  266 + {
  267 + validator() {
  268 + if (open === close) return Promise.reject('布尔值不能相同');
  269 + return Promise.resolve();
  270 + },
  271 + },
  272 + ];
  273 + },
  274 + },
  275 + {
  276 + field: FormFieldsEnum.LENGTH,
  277 + component: 'Input',
  278 + required: true,
  279 + label: FormFieldsNameEnum.LENGTH,
  280 + defaultValue: '10240',
  281 + colProps: {
  282 + span: 8,
  283 + },
  284 + componentProps: {
  285 + placeholder: '请输入数据长度',
  286 + },
  287 + renderComponentContent: () => {
  288 + return {
  289 + suffix: () => '字节',
  290 + };
  291 + },
  292 + ifShow: ({ model }) =>
  293 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  294 + model[FormFieldsEnum.FUNCTION_TYPE]
  295 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRING,
  296 + },
  297 + {
  298 + field: FormFieldsEnum.ENUMS_DATA,
  299 + label: FormFieldsNameEnum.ENUMS_DATA,
  300 + component: 'Input',
  301 + slot: FormFieldsEnum.ENUMS_DATA,
  302 + changeEvent: 'update:value',
  303 + valueField: 'value',
  304 + ifShow: ({ model }) =>
  305 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  306 + model[FormFieldsEnum.FUNCTION_TYPE]
  307 + ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.ENUM,
  308 + },
  309 + {
  310 + field: FormFieldsEnum.STRUCT_DATA,
  311 + label: FormFieldsNameEnum.STRUCT_DATA,
  312 + component: 'Input',
  313 + slot: FormFieldsEnum.STRUCT_DATA,
  314 + changeEvent: 'update:value',
  315 + valueField: 'value',
  316 + required: true,
  317 + ifShow: ({ model }) =>
  318 + model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRUCT &&
  319 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  320 + model[FormFieldsEnum.FUNCTION_TYPE]
  321 + ),
  322 + },
  323 + ];
  324 +};
... ...
  1 +export { default as StructFormItem } from './index.vue';
... ...
  1 +<script setup lang="ts">
  2 + import { Button, Divider } from 'ant-design-vue';
  3 + import { cloneDeep } from 'lodash';
  4 + import { toRaw, unref } from 'vue';
  5 + import CreateStructModal from './CreateStructModal.vue';
  6 + import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  7 + import { useModal } from '/@/components/Modal';
  8 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  9 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  10 +
  11 + const props = withDefaults(
  12 + defineProps<{
  13 + buttonName?: string;
  14 + disabled?: boolean;
  15 + value?: StructJSON[];
  16 + dataType?: DataTypeEnum[];
  17 + mode?: DataActionModeEnum;
  18 + }>(),
  19 + {
  20 + buttonName: '+ 新增参数',
  21 + value: () => [],
  22 + dataType: () => Object.values(DataTypeEnum),
  23 + }
  24 + );
  25 +
  26 + const emits = defineEmits(['update:value']);
  27 +
  28 + const [registerModal, { openModal, closeModal }] = useModal();
  29 +
  30 + const handleOpenCreateModal = () => {
  31 + openModal(true, {
  32 + mode: DataActionModeEnum.CREATE,
  33 + } as ModalParamsType);
  34 + };
  35 +
  36 + const handleEdit = (item: StructJSON) => {
  37 + openModal(true, {
  38 + mode: DataActionModeEnum.UPDATE,
  39 + record: cloneDeep(item),
  40 + } as ModalParamsType);
  41 + };
  42 +
  43 + const handleDelete = (item: StructJSON) => {
  44 + const index = props.value.findIndex((temp) => item.identifier === temp.identifier);
  45 +
  46 + if (~index) {
  47 + const _values = cloneDeep(props.value);
  48 + _values.splice(index, 1);
  49 + emits('update:value', _values);
  50 + }
  51 + };
  52 +
  53 + const handleEditComplete = (value: StructJSON) => {
  54 + const _value = cloneDeep(toRaw(unref(props.value)));
  55 + const index = _value.findIndex((item) => item.identifier === value.identifier);
  56 + ~index ? _value.splice(index, 1, value) : _value.push(value);
  57 + emits('update:value', _value);
  58 + closeModal();
  59 + };
  60 +</script>
  61 +
  62 +<template>
  63 + <section>
  64 + <main>
  65 + <div
  66 + v-for="item in value"
  67 + :key="item.identifier"
  68 + class="flex items-center justify-between px-2 py-1 mb-2 bg-blue-50 text-gray-600"
  69 + >
  70 + <div>
  71 + <span>参数名称:</span>
  72 + <span class="ml-1">{{ item.functionName }}</span>
  73 + </div>
  74 + <div>
  75 + <Button type="link" @click="handleEdit(item)">
  76 + {{ mode === DataActionModeEnum.READ ? '查看' : '编辑' }}
  77 + </Button>
  78 + <Divider type="vertical" />
  79 + <Button
  80 + type="link"
  81 + @click="handleDelete(item)"
  82 + :disabled="mode === DataActionModeEnum.READ"
  83 + >
  84 + 删除
  85 + </Button>
  86 + </div>
  87 + </div>
  88 + </main>
  89 + <Button type="link" @click="handleOpenCreateModal" :disabled="mode === DataActionModeEnum.READ">
  90 + {{ buttonName }}
  91 + </Button>
  92 + <CreateStructModal
  93 + :value="value"
  94 + @register="registerModal"
  95 + @complete="handleEditComplete"
  96 + :dataType="dataType"
  97 + :mode="mode"
  98 + />
  99 + </section>
  100 +</template>
  101 +./index.config
... ...
  1 +import { unref } from 'vue';
  2 +import { createHexCommandRuleValidator } from '.';
  3 +import { createFunctionNameFormItem, createIdentifierFormItem } from './DataTypeForm/config';
  4 +import { useObjectModelFormContext } from './useObjectModelFormContext';
  5 +import { findDictItemByCode } from '/@/api/system/dict';
  6 +import { FormSchema } from '/@/components/Form';
  7 +import {
  8 + ServiceCallTypeEnum,
  9 + ServiceCallTypeNameEnum,
  10 + TransportTypeEnum,
  11 +} from '/@/enums/deviceEnum';
  12 +import { DictEnum } from '/@/enums/dictEnum';
  13 +import {
  14 + ObjectModelAccessModeEnum,
  15 + FunctionTypeEnum,
  16 + FunctionTypeNameEnum,
  17 + ObjectEventTypeEnum,
  18 +} from '/@/enums/objectModelEnum';
  19 +import { DataActionModeEnum } from '/@/enums/toolEnum';
  20 +
  21 +export enum FormFieldsEnum {
  22 + FUNCTION_TYPE = 'functionType',
  23 + FUNCTION_NAME = 'functionName',
  24 + IDENTIFIER = 'identifier',
  25 + DATA_TYPE = 'dataType',
  26 + ACCESS_MODE = 'accessMode',
  27 + VALUE_RANGE = 'valueRange',
  28 + STEP = 'step',
  29 + UNIT = 'unit',
  30 + UNIT_NAME = 'unitName',
  31 + REMARK = 'remark',
  32 + BOOL_CLOSE = 'boolClose',
  33 + BOOL_OPEN = 'boolOpen',
  34 + LENGTH = 'length',
  35 + EXTENSION_DESC = 'extensionDesc',
  36 + ENUMS_DATA = 'enumsData',
  37 + STRUCT_DATA = 'structData',
  38 +
  39 + CALL_TYPE = 'callType',
  40 + SERVICE_COMMAND = 'serviceCommand',
  41 + INPUT_DATA = 'inputData',
  42 + OUTPUT_DATA = 'outputData',
  43 +
  44 + EVENT_TYPE = 'eventType',
  45 +
  46 + DATA_TYPE_FORM = 'dataTypeForm',
  47 +}
  48 +
  49 +export enum FormFieldsNameEnum {
  50 + FUNCTION_TYPE = '功能类型',
  51 + FUNCTION_NAME = '功能名称',
  52 + IDENTIFIER = '功能标识符',
  53 + DATA_TYPE = '数据类型',
  54 + ACCESS_MODE = '读写类型',
  55 + VALUE_RANGE = '取值范围',
  56 + STEP = '步长',
  57 + UNIT = '单位',
  58 + UNIT_NAME = '单位名称',
  59 + REMARK = '备注',
  60 + BOOL_CLOSE = '0 - 关',
  61 + BOOL_OPEN = '1 - 开',
  62 + LENGTH = '数据长度(字节)',
  63 + EXTENSION_DESC = '拓展描述符',
  64 + ENUMS_DATA = '枚举项',
  65 + STRUCT_DATA = 'JSON对象',
  66 +
  67 + CALL_TYPE = '调用方式',
  68 + SERVICE_COMMAND = '输入参数',
  69 + INPUT_DATA = '输入参数',
  70 + OUTPUT_DATA = '输出参数',
  71 +
  72 + EVENT_TYPE = '事件类型',
  73 +}
  74 +
  75 +export const getFormSchemas = ({
  76 + transportType,
  77 +}: {
  78 + transportType?: string;
  79 + mode?: DataActionModeEnum;
  80 +}): FormSchema[] => {
  81 + return [
  82 + {
  83 + field: FormFieldsEnum.FUNCTION_TYPE,
  84 + label: FormFieldsNameEnum.FUNCTION_TYPE,
  85 + component: 'Segmented',
  86 + defaultValue: FunctionTypeEnum.PROPERTIES,
  87 + required: true,
  88 + dynamicDisabled: () => {
  89 + const { getModalMode } = useObjectModelFormContext();
  90 + return unref(getModalMode) !== DataActionModeEnum.CREATE;
  91 + },
  92 + componentProps: ({ formActionType }) => {
  93 + return {
  94 + options: Object.keys(FunctionTypeEnum).map((key) => ({
  95 + title: FunctionTypeNameEnum[key],
  96 + value: FunctionTypeEnum[key],
  97 + })),
  98 + onChange() {
  99 + const { setFieldsValue, clearValidate } = formActionType;
  100 + setFieldsValue({
  101 + [FormFieldsEnum.INPUT_DATA]: [],
  102 + [FormFieldsEnum.OUTPUT_DATA]: [],
  103 + [FormFieldsEnum.STRUCT_DATA]: [],
  104 + [FormFieldsEnum.SERVICE_COMMAND]: null,
  105 + });
  106 + clearValidate();
  107 + },
  108 + };
  109 + },
  110 + },
  111 + {
  112 + field: FormFieldsEnum.DATA_TYPE_FORM,
  113 + component: 'Input',
  114 + label: '',
  115 + ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.PROPERTIES,
  116 + colSlot: FormFieldsEnum.DATA_TYPE_FORM,
  117 + },
  118 + createFunctionNameFormItem({
  119 + ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES,
  120 + }),
  121 + createIdentifierFormItem({
  122 + ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES,
  123 + }),
  124 + {
  125 + field: FormFieldsEnum.EXTENSION_DESC,
  126 + component: 'Input',
  127 + label: FormFieldsNameEnum.EXTENSION_DESC,
  128 + slot: FormFieldsEnum.EXTENSION_DESC,
  129 + ifShow: ({ model }) =>
  130 + transportType === TransportTypeEnum.TCP &&
  131 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  132 + model[FormFieldsEnum.FUNCTION_TYPE]
  133 + ),
  134 + },
  135 + {
  136 + field: FormFieldsEnum.ACCESS_MODE,
  137 + component: 'ApiRadioGroup',
  138 + label: FormFieldsNameEnum.ACCESS_MODE,
  139 + required: true,
  140 + ifShow: ({ model }) =>
  141 + ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  142 + model[FormFieldsEnum.FUNCTION_TYPE]
  143 + ),
  144 + defaultValue: ObjectModelAccessModeEnum.READ_AND_WRITE,
  145 + componentProps: {
  146 + placeholder: '请选择读写类型',
  147 + api: findDictItemByCode,
  148 + params: {
  149 + dictCode: DictEnum.READ_WRITE_TYP,
  150 + },
  151 + labelField: 'itemText',
  152 + valueField: 'itemValue',
  153 + },
  154 + },
  155 +
  156 + // 服务
  157 + {
  158 + field: FormFieldsEnum.CALL_TYPE,
  159 + label: FormFieldsNameEnum.CALL_TYPE,
  160 + component: 'RadioGroup',
  161 + defaultValue: ServiceCallTypeEnum.ASYNC,
  162 + required: true,
  163 + ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.SERVICE,
  164 + componentProps: () => {
  165 + return {
  166 + options: Object.keys(ServiceCallTypeEnum).map((value) => ({
  167 + label: ServiceCallTypeNameEnum[value],
  168 + value,
  169 + })),
  170 + };
  171 + },
  172 + },
  173 + {
  174 + field: FormFieldsEnum.SERVICE_COMMAND,
  175 + label: FormFieldsNameEnum.SERVICE_COMMAND,
  176 + component: 'Input',
  177 + required: true,
  178 + rules: [{ message: '输入参数为必填项', required: true }, ...createHexCommandRuleValidator()],
  179 + ifShow: ({ model }) =>
  180 + model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.SERVICE &&
  181 + transportType === TransportTypeEnum.TCP,
  182 + componentProps: () => {
  183 + return {
  184 + placeholder: '请输入ASCII或HEX服务命令',
  185 + };
  186 + },
  187 + },
  188 + {
  189 + field: FormFieldsEnum.INPUT_DATA,
  190 + label: FormFieldsNameEnum.INPUT_DATA,
  191 + component: 'Input',
  192 + slot: FormFieldsEnum.INPUT_DATA,
  193 + changeEvent: 'update:value',
  194 + valueField: 'value',
  195 + required: true,
  196 + ifShow: ({ model }) =>
  197 + model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.SERVICE &&
  198 + transportType !== TransportTypeEnum.TCP,
  199 + },
  200 +
  201 + // 事件
  202 + {
  203 + field: FormFieldsEnum.EVENT_TYPE,
  204 + label: FormFieldsNameEnum.EVENT_TYPE,
  205 + required: true,
  206 + component: 'ApiRadioGroup',
  207 + defaultValue: ObjectEventTypeEnum.INFO,
  208 + ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.EVENTS,
  209 + componentProps: () => {
  210 + return {
  211 + placeholder: '请选择事件类型',
  212 + api: findDictItemByCode,
  213 + params: {
  214 + dictCode: DictEnum.EVENT_TYPE,
  215 + },
  216 + labelField: 'itemText',
  217 + valueField: 'itemValue',
  218 + getPopupContainer: () => document.body,
  219 + };
  220 + },
  221 + },
  222 + {
  223 + field: FormFieldsEnum.OUTPUT_DATA,
  224 + label: FormFieldsNameEnum.OUTPUT_DATA,
  225 + component: 'Input',
  226 + slot: FormFieldsEnum.OUTPUT_DATA,
  227 + changeEvent: 'update:value',
  228 + valueField: 'value',
  229 + ifShow: ({ model }) =>
  230 + [FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes(
  231 + model[FormFieldsEnum.FUNCTION_TYPE]
  232 + ),
  233 + },
  234 +
  235 + {
  236 + field: FormFieldsEnum.REMARK,
  237 + label: FormFieldsNameEnum.REMARK,
  238 + component: 'InputTextArea',
  239 + componentProps: {
  240 + rows: 4,
  241 + maxLength: 100,
  242 + placeholder: '请输入描述',
  243 + },
  244 + },
  245 + ];
  246 +};
... ...
  1 +import { Rule } from '/@/components/Form';
  2 +
  3 +export { default as ObjectModelForm } from './index.vue';
  4 +
  5 +export function createHexCommandRuleValidator(): Rule[] {
  6 + return [
  7 + {
  8 + message: '请输入ASCII或HEX服务命令(0~9/A~F)',
  9 + validator(_rule, value, _callback) {
  10 + const reg = /^[\s0-9a-fA-F]+$/;
  11 +
  12 + if (reg.test(value)) return Promise.resolve();
  13 + return Promise.reject('请输入ASCII或HEX服务命令(0~9/A~F)');
  14 + },
  15 + },
  16 + ];
  17 +}
... ...