Commit ab84b840002464fcdc7a318c127871d2c911e17e

Authored by loveumiko
1 parent 594b8ae0

perf: 看板优化选择结构体时新增结构体组件

... ... @@ -124,6 +124,10 @@
124 124
125 125 const handleCopy = async (record: DataSourceType) => {
126 126 const { key } = props.componentConfig || {};
  127 + if (key === 'ComponentStructural' && props.dataSource.length >= 1) {
  128 + createMessage.warning('结构体组件绑定的数据源不能超过1条');
  129 + return;
  130 + }
127 131 if (key == 'HumidityComponent2' && props.dataSource.length >= 6) {
128 132 createMessage.warning('绑定的数据源不能超过6条~');
129 133 return;
... ... @@ -145,8 +149,13 @@
145 149 ]);
146 150 };
147 151
148   - const handleSetting = (record: DataSourceType) => {
149   - openModal(true, { mode: DataActionModeEnum.UPDATE, record: record } as ModalParamsType);
  152 + const handleSetting = (record: DataSourceType, index: number) => {
  153 + const values = getFormValues();
  154 +
  155 + openModal(true, {
  156 + mode: DataActionModeEnum.UPDATE,
  157 + record: { ...record, ...values[index] },
  158 + } as ModalParamsType);
150 159 };
151 160
152 161 const handleDelete = (record: DataSourceType) => {
... ... @@ -208,7 +217,7 @@
208 217 </Tooltip>
209 218 <Tooltip title="设置" v-if="hasSettingDesignIcon">
210 219 <SettingOutlined
211   - @click="handleSetting(item)"
  220 + @click="handleSetting(item, index)"
212 221 class="cursor-pointer text-lg !leading-32px"
213 222 />
214 223 </Tooltip>
... ...
... ... @@ -5,8 +5,12 @@
5 5 import { BasicModal, useModalInner } from '/@/components/Modal';
6 6 import { ConfigType, CreateComponentType } from '../../../packages/index.type';
7 7 import { ref } from 'vue';
8   - import { unref } from 'vue';
  8 + import { unref, nextTick } from 'vue';
9 9 import { ModalParamsType } from '/#/utils';
  10 + import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
  11 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  12 + import { createImgPreview } from '/@/components/Preview';
  13 + import { upload } from '/@/api/oss/ossFileUploader';
10 14
11 15 const props = defineProps<{
12 16 selectWidgetKeys: SelectedWidgetKeys;
... ... @@ -30,9 +34,94 @@
30 34 const currentEditRecord = ref<DataSourceType>({} as DataSourceType);
31 35
32 36 const [register, { closeModal }] = useModalInner((data: ModalParamsType<DataSourceType>) => {
33   - const { record } = data;
  37 + const { record }: any = data;
34 38 currentEditRecord.value = record;
35   - setFormValues(record.componentInfo || {});
  39 + if (record.structural?.length) {
  40 + record.structural.forEach((item, index) => {
  41 + nextTick(() => {
  42 + unref(settingFormEl)?.appendSchemaByField?.({
  43 + field: item + 'StructuralIcon',
  44 + label: record.structuralLabel[index] + '图标',
  45 + // labelWidth: '300',
  46 + component: 'IconDrawer',
  47 + changeEvent: 'update:value',
  48 + defaultValue: 'shuiwen',
  49 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom',
  50 + componentProps({ formModel }) {
  51 + const color = formModel[item + 'StructuralIconColor'];
  52 + return {
  53 + color,
  54 + };
  55 + },
  56 + });
  57 + unref(settingFormEl)?.appendSchemaByField?.({
  58 + field: item + 'StructuralIconColor',
  59 + label: record.structuralLabel[index] + '图标颜色',
  60 + component: 'ColorPicker',
  61 + changeEvent: 'update:value',
  62 + defaultValue: '#367bff',
  63 + ifShow: ({ model }) => {
  64 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  65 + },
  66 + });
  67 + unref(settingFormEl)?.appendSchemaByField?.({
  68 + field: item + 'StructuralCustomIcon',
  69 + label: record.structuralLabel[index] + '图标',
  70 + component: 'ApiUpload',
  71 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  72 + changeEvent: 'update:fileList',
  73 + valueField: 'fileList',
  74 + required: true,
  75 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  76 + componentProps: ({ formModel, formActionType }) => {
  77 + const { setFieldsValue } = formActionType;
  78 + return {
  79 + maxSize: 50 * 1024,
  80 + listType: 'picture-card',
  81 + maxFileLimit: 1,
  82 + accept: '.svg',
  83 + api: async (file: File) => {
  84 + try {
  85 + const formData = new FormData();
  86 + const { name } = file;
  87 + formData.set('file', file);
  88 + const { fileStaticUri, fileName } = await upload(formData);
  89 + return {
  90 + uid: fileStaticUri,
  91 + name: name || fileName,
  92 + url: fileStaticUri,
  93 + } as FileItem;
  94 + } catch (error) {
  95 + return {};
  96 + }
  97 + },
  98 + onDownload() {},
  99 + onPreview: (fileList: FileItem) => {
  100 + createImgPreview({ imageList: [fileList.url!] });
  101 + },
  102 +
  103 + onDelete(url: string) {
  104 + // 去重 取要删除的自定义图标
  105 + setFieldsValue({
  106 + structuralDeleteUrl: [
  107 + ...new Set([
  108 + ...formModel.structuralDeleteUrl
  109 + .split(',')
  110 + ?.filter((item) => Boolean(item)),
  111 + url!,
  112 + ]),
  113 + ].join(','),
  114 + });
  115 + },
  116 + };
  117 + },
  118 + });
  119 + });
  120 + });
  121 + }
  122 + nextTick(() => {
  123 + setFormValues(record.componentInfo || {});
  124 + });
36 125 });
37 126
38 127 const getFormValues = () => {
... ... @@ -43,9 +132,25 @@
43 132 unref(settingFormEl)?.setFormValues(data || {});
44 133 };
45 134
46   - const handleOk = () => {
  135 + const handleOk = async () => {
47 136 const { uuid } = unref(currentEditRecord);
  137 + await unref(settingFormEl)?.validate?.();
48 138 emit('ok', { uuid, componentInfo: getFormValues() } as DataSourceType);
  139 + handleCancel();
  140 + };
  141 +
  142 + const handleCancel = () => {
  143 + const { structural } = unref(currentEditRecord) || {};
  144 + if (structural?.length) {
  145 + structural.forEach((item) => {
  146 + unref(settingFormEl)?.removeSchemaByFiled?.([
  147 + item + 'StructuralIcon',
  148 + item + 'StructuralIconColor',
  149 + item + 'StructuralCustomIcon',
  150 + // item + 'StructuralDeleteUrl',
  151 + ]);
  152 + });
  153 + }
49 154 closeModal();
50 155 };
51 156
... ... @@ -56,7 +161,13 @@
56 161 </script>
57 162
58 163 <template>
59   - <BasicModal @register="register" title="组件设置" @ok="handleOk" :width="700">
  164 + <BasicModal
  165 + @register="register"
  166 + title="组件设置"
  167 + @ok="handleOk"
  168 + :width="700"
  169 + @cancel="handleCancel"
  170 + >
60 171 <!-- -->
61 172 <component ref="settingFormEl" :is="getSettingComponent" />
62 173 </BasicModal>
... ...
1 1 import { ValidateErrorEntity } from 'ant-design-vue/es/form/interface';
2 2 import { DataSource } from '../palette/types';
  3 +import { FormSchema } from '/@/components/Form';
3 4
4 5 export interface SelectedWidgetKeys {
5 6 categoryKey: string;
... ... @@ -21,5 +22,11 @@ export interface PublicFormInstaceType {
21 22 getFormValues: () => Recordable;
22 23 setFormValues: (data: Recordable) => void;
23 24 resetFormValues: () => Promise<void>;
  25 + appendSchemaByField?: (
  26 + schema: FormSchema,
  27 + prefixField?: string | undefined,
  28 + first?: boolean | undefined
  29 + ) => Promise<void>;
  30 + removeSchemaByFiled?: (field: string | string[]) => Promise<void>;
24 31 validate?: () => Promise<{ flag: boolean; errors: ValidateErrorEntity }>;
25 32 }
... ...
... ... @@ -122,6 +122,11 @@
122 122
123 123 const handleNewRecord = () => {
124 124 const { componentKey } = unref(selectWidgetKeys);
  125 +
  126 + if (componentKey === 'ComponentStructural' && unref(dataSource).length >= 1) {
  127 + createMessage.warning('结构体组件绑定的数据源不能超过1条');
  128 + return;
  129 + }
125 130 if (componentKey === 'HumidityComponent2' && unref(dataSource).length >= 6) {
126 131 createMessage.warning('绑定的数据源不能超过6条');
127 132 return;
... ... @@ -258,22 +263,48 @@
258 263 }
259 264 const value = getFormValues();
260 265 const { record } = value || {};
  266 + const { componentKey } = unref(selectWidgetKeys);
261 267 try {
262 268 const currentRecordIconUrl = ref<any>([]);
263   - // 判断当前自定义组件表单以前的自定义图片呢url
264   - currentRecord.value?.dataSource.forEach((item) => {
265   - if (item.componentInfo?.customIcon) {
266   - item.componentInfo?.customIcon?.forEach((icon: FileItem) => {
267   - currentRecordIconUrl.value.push(icon.url);
268   - });
269   - }
270   - });
  269 + // 判断当前自定义组件表单以前自定义图片的url
  270 + if (unref(currentRecord)?.dataSource) {
  271 + currentRecord.value?.dataSource?.forEach((item) => {
  272 + if (item.componentInfo?.customIcon || componentKey !== 'ComponentStructural') {
  273 + item.componentInfo?.customIcon?.forEach((icon: FileItem) => {
  274 + currentRecordIconUrl.value.push(icon.url);
  275 + });
  276 + }
  277 + // if (componentKey === 'ComponentStructural' && item?.StructuralDeleteUrl) {
  278 + // currentRecordIconUrl.value.push(...item?.StructuralDeleteUrl?.split(','));
  279 +
  280 + // }
  281 + for (const item1 in item.componentInfo) {
  282 + if (item1.includes('StructuralCustomIcon')) {
  283 + currentRecordIconUrl.value.push(item.componentInfo[item1]?.[0].url);
  284 + }
  285 + }
  286 + });
  287 + }
271 288 // 取当前修改过后的自定义图片url
272   - const dataSourceUrl = record.dataSource?.map(
273   - (item) => item.componentInfo.customIcon?.[0].url
274   - );
275 289
276   - // 当前自定义组件取出要进行删除的图标url
  290 + const dataSourceUrl =
  291 + componentKey !== 'ComponentStructural'
  292 + ? record.dataSource?.map((item) => item.componentInfo.customIcon?.[0].url)
  293 + : [];
  294 +
  295 + if (componentKey === 'ComponentStructural') {
  296 + record.dataSource?.forEach((item) => {
  297 + for (const item1 in item.componentInfo) {
  298 + if (item1.includes('StructuralCustomIcon')) {
  299 + dataSourceUrl?.push(item.componentInfo[item1]?.[0].url);
  300 + }
  301 + }
  302 + });
  303 + }
  304 +
  305 + // StructuralCustomIcon
  306 +
  307 + // 当前自定义组件取出要进行删除的图标url ->判断当前组件的自定义图标url是跟以前不一样 取出不一样的以前自定义组件的url进行删除
277 308 const dataSourceDeleteUrl = unref(currentRecordIconUrl).filter(
278 309 (item) => !dataSourceUrl?.includes(item)
279 310 );
... ... @@ -283,11 +314,18 @@
283 314 const customIconUrls = ref<any>([]);
284 315 oldDataSource?.forEach((item: any) => {
285 316 item.dataSource?.forEach((dataSource) => {
286   - if (dataSource.componentInfo?.customIcon) {
287   - dataSource.componentInfo?.customIcon?.forEach((icon: FileItem) => {
  317 + if (dataSource.componentInfo?.customIcon || componentKey !== 'ComponentStructural') {
  318 + dataSource?.componentInfo?.customIcon?.forEach((icon: FileItem) => {
288 319 customIconUrls.value.push(icon.url);
289 320 });
290 321 }
  322 + if (componentKey === 'ComponentStructural') {
  323 + for (const structural in dataSource?.componentInfo) {
  324 + if (structural?.includes('StructuralCustomIcon')) {
  325 + customIconUrls.value.push(dataSource?.componentInfo[structural]?.[0].url);
  326 + }
  327 + }
  328 + }
291 329 });
292 330 });
293 331 // const dataSourceDeleteUrl = record.dataSource?.map((item) => item.componentInfo.deleteUrl);
... ...
  1 +import cloneDeep from 'lodash-es/cloneDeep';
  2 +import { ComponentStructuralConfig } from '.';
  3 +import {
  4 + ConfigType,
  5 + CreateComponentType,
  6 + PublicComponentOptions,
  7 + PublicPresetOptions,
  8 +} from '/@/views/visual/packages/index.type';
  9 +import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
  10 +import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
  11 +
  12 +export const option: PublicPresetOptions = {
  13 + [ComponentConfigFieldEnum.UNIT]: '℃',
  14 + [ComponentConfigFieldEnum.ICON]: 'shuiwen',
  15 + [ComponentConfigFieldEnum.ICON_COLOR]: '#367bff',
  16 + [ComponentConfigFieldEnum.FONT_COLOR]: '#000',
  17 + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false,
  18 + [ComponentConfigFieldEnum.VALUE_SIZE]: 20,
  19 + [ComponentConfigFieldEnum.FONT_SIZE]: 14,
  20 +};
  21 +
  22 +export default class Config extends PublicConfigClass implements CreateComponentType {
  23 + public key: string = ComponentStructuralConfig.key;
  24 +
  25 + public attr = { ...componentInitConfig };
  26 +
  27 + public componentConfig: ConfigType = cloneDeep(ComponentStructuralConfig);
  28 +
  29 + public persetOption = cloneDeep(option);
  30 +
  31 + public option: PublicComponentOptions;
  32 +
  33 + constructor(option: PublicComponentOptions) {
  34 + super();
  35 + this.option = { ...option };
  36 + }
  37 +}
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
  3 + import { useForm, BasicForm } from '/@/components/Form';
  4 + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  5 + import { option } from './config';
  6 +
  7 + const [
  8 + register,
  9 + {
  10 + getFieldsValue,
  11 + setFieldsValue,
  12 + resetFields,
  13 + appendSchemaByField,
  14 + removeSchemaByFiled,
  15 + validate,
  16 + },
  17 + ] = useForm({
  18 + schemas: [
  19 + {
  20 + field: ComponentConfigFieldEnum.FONT_COLOR,
  21 + label: '数值字体颜色',
  22 + component: 'ColorPicker',
  23 + changeEvent: 'update:value',
  24 + valueField: 'value',
  25 + defaultValue: option.fontColor,
  26 + },
  27 + // {
  28 + // field: ComponentConfigFieldEnum.UNIT,
  29 + // label: '数值单位',
  30 + // component: 'Input',
  31 + // defaultValue: option.unit,
  32 + // componentProps: {
  33 + // placeholder: '请输入数值单位',
  34 + // },
  35 + // },
  36 +
  37 + {
  38 + field: ComponentConfigFieldEnum.VALUE_SIZE,
  39 + label: '数值字体大小',
  40 + component: 'InputNumber',
  41 + defaultValue: 20,
  42 + componentProps: {
  43 + min: 0,
  44 + max: 100,
  45 + formatter: (e) => {
  46 + const value = e?.toString().replace(/^0/g, '');
  47 + if (value) {
  48 + return value.replace(/^0/g, '');
  49 + } else {
  50 + return 0;
  51 + }
  52 + },
  53 + },
  54 + },
  55 + {
  56 + field: ComponentConfigFieldEnum.FONT_SIZE,
  57 + label: '文本字体大小',
  58 + component: 'InputNumber',
  59 + defaultValue: 14,
  60 + componentProps: {
  61 + min: 0,
  62 + max: 100,
  63 + formatter: (e) => {
  64 + const value = e?.toString().replace(/^0/g, '');
  65 + if (value) {
  66 + return value.replace(/^0/g, '');
  67 + } else {
  68 + return 0;
  69 + }
  70 + },
  71 + },
  72 + },
  73 + {
  74 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  75 + label: '显示设备名称',
  76 + component: 'Checkbox',
  77 + defaultValue: option.showDeviceName,
  78 + },
  79 + {
  80 + field: ComponentConfigFieldEnum.SHOW_TIME,
  81 + label: '显示时间',
  82 + component: 'Checkbox',
  83 + defaultValue: option.showDeviceName,
  84 + },
  85 + {
  86 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  87 + label: '图标类型',
  88 + component: 'RadioGroup',
  89 + defaultValue: 'default',
  90 + componentProps: ({ formModel }) => {
  91 + return {
  92 + options: [
  93 + { label: '系统默认', value: 'default' },
  94 + { label: '自定义', value: 'custom' },
  95 + ],
  96 + onChange() {
  97 + for (const item in formModel) {
  98 + if (item.includes('CustomIcon')) {
  99 + formModel[item] = [];
  100 + }
  101 + }
  102 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  103 + },
  104 + };
  105 + },
  106 + },
  107 + {
  108 + field: 'structuralDeleteUrl',
  109 + label: '删除图标存储地址',
  110 + component: 'Input',
  111 + defaultValue: '123',
  112 + show: false,
  113 + },
  114 + ],
  115 + showActionButtonGroup: false,
  116 + labelWidth: 120,
  117 + baseColProps: {
  118 + span: 12,
  119 + },
  120 + });
  121 +
  122 + const getFormValues = () => {
  123 + return getFieldsValue();
  124 + };
  125 +
  126 + const setFormValues = (data: Recordable) => {
  127 + return setFieldsValue(data);
  128 + };
  129 +
  130 + defineExpose({
  131 + getFormValues,
  132 + setFormValues,
  133 + resetFormValues: resetFields,
  134 + appendSchemaByField,
  135 + removeSchemaByFiled,
  136 + validate,
  137 + } as PublicFormInstaceType);
  138 +</script>
  139 +
  140 +<template>
  141 + <BasicForm @register="register" />
  142 +</template>
  143 +
  144 +<style lang="less" scoped>
  145 + :deep(.ant-form-item-label) {
  146 + label {
  147 + span {
  148 + max-width: 120px !important;
  149 + text-overflow: ellipsis !important;
  150 + overflow: hidden;
  151 + vertical-align: middle;
  152 + }
  153 + }
  154 +
  155 + :deep(.ant-form-item-label) {
  156 + span {
  157 + max-width: 120px !important;
  158 + text-overflow: ellipsis !important;
  159 + overflow: hidden;
  160 + vertical-align: middle;
  161 + }
  162 + }
  163 + }
  164 +</style>
... ...
  1 +<script lang="ts" setup>
  2 + import { commonDataSourceSchemas } from '../../../config/common.config';
  3 + import { BasicForm, useForm } from '/@/components/Form';
  4 + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
  5 +
  6 + // const props = defineProps<{
  7 + // values: PublicComponentDataSourceProps;
  8 + // }>();
  9 +
  10 + const [register, { getFieldsValue, setFieldsValue, validate, resetFields, appendSchemaByField }] =
  11 + useForm({
  12 + labelWidth: 0,
  13 + showActionButtonGroup: false,
  14 + layout: 'horizontal',
  15 + labelCol: { span: 0 },
  16 + schemas: commonDataSourceSchemas(),
  17 + });
  18 +
  19 + const getFormValues = () => {
  20 + return getFieldsValue();
  21 + };
  22 +
  23 + const setFormValues = (record: Recordable) => {
  24 + return setFieldsValue(record);
  25 + };
  26 +
  27 + defineExpose({
  28 + getFormValues,
  29 + setFormValues,
  30 + validate,
  31 + resetFormValues: resetFields,
  32 + appendSchemaByField,
  33 + } as PublicFormInstaceType);
  34 +</script>
  35 +
  36 +<template>
  37 + <BasicForm @register="register" />
  38 +</template>
... ...
  1 +import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
  2 +import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
  3 +
  4 +const componentKeys = useComponentKeys('ComponentStructural');
  5 +export const ComponentStructuralConfig: ConfigType = {
  6 + ...componentKeys,
  7 + title: '结构体组件',
  8 + package: PackagesCategoryEnum.TEXT,
  9 +};
... ...
  1 +<script lang="ts" setup>
  2 + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  3 + import { option } from './config';
  4 + import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
  5 + import { ref, unref } from 'vue';
  6 + import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
  7 + import { SvgIcon } from '/@/components/Icon';
  8 + import { computed } from 'vue';
  9 + import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
  10 + import { useDataFetch } from '../../../hook/socket/useSocket';
  11 + import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useThingsModelValueTransform } from '/@/views/visual/palette/hooks/useThingsModelValueTransform';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  14 + import { isObject } from '/@/utils/is';
  15 +
  16 + const props = defineProps<{
  17 + config: ComponentPropsConfigType<typeof option>;
  18 + }>();
  19 +
  20 + const currentValue = ref<string | number>(123);
  21 +
  22 + const structuralData = ref<
  23 + {
  24 + label: string;
  25 + value: string | number;
  26 + icon: any;
  27 + iconColor: string;
  28 + }[]
  29 + >([{ label: '温度', value: 20, icon: 'shuiwen', iconColor: '#367bff' }]);
  30 +
  31 + const time = ref<Nullable<number>>(null);
  32 +
  33 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  34 +
  35 + const getThingModelTsl = computed(() => {
  36 + const { option } = props.config;
  37 +
  38 + const { deviceProfileId, attribute } = option;
  39 + return getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  40 + });
  41 +
  42 + const getDesign = computed(() => {
  43 + const { persetOption = {}, option } = props.config;
  44 + const {
  45 + iconColor: persetIconColor,
  46 + unit: perseUnit,
  47 + icon: persetIcon,
  48 + fontColor: persetFontColor,
  49 + valueSize: persetValueSize,
  50 + fontSize: persetFontSize,
  51 + showTime: persetShowTime,
  52 + } = persetOption;
  53 +
  54 + const { componentInfo, attributeRename, structural, structuralLabel } = option;
  55 + const { functionName } = unref(getThingModelTsl) || {};
  56 +
  57 + const {
  58 + icon,
  59 + iconColor,
  60 + fontColor,
  61 + showTime,
  62 + unit,
  63 + valueSize,
  64 + fontSize,
  65 + customIcon,
  66 + defaultCustom,
  67 + } = componentInfo || {};
  68 + return {
  69 + iconColor: iconColor || persetIconColor,
  70 + unit: unit ?? perseUnit,
  71 + icon: icon || persetIcon,
  72 + fontColor: fontColor || persetFontColor,
  73 + attribute: attributeRename || functionName,
  74 + valueSize: valueSize || persetValueSize || 20,
  75 + fontSize: fontSize || persetFontSize || 14,
  76 + showTime: showTime || persetShowTime,
  77 + defaultCustom: defaultCustom || 'default',
  78 + customIcon: customIcon || [],
  79 + structural: structural || [],
  80 + structuralLabel: structuralLabel || [],
  81 + componentInfo,
  82 + };
  83 + });
  84 +
  85 + const updateFn: DataFetchUpdateFn = (message, attribute) => {
  86 + const { data = {} } = message;
  87 + const [latest] = data[attribute] || [];
  88 + if (!latest.length) return;
  89 + const [timespan, value] = latest;
  90 +
  91 + const { structuralLabel, structural, componentInfo } = unref(getDesign) || {};
  92 + const { defaultCustom } = componentInfo || {};
  93 +
  94 + time.value = timespan;
  95 + if (structural?.length && structuralLabel?.length) {
  96 + const values = JSON.parse(value);
  97 + const newValues = isObject(values)
  98 + ? defaultCustom === 'custom'
  99 + ? structural.map((item, index) => {
  100 + const option = {
  101 + label: structuralLabel[index],
  102 + value: values[item],
  103 + icon: componentInfo[item + 'StructuralCustomIcon'],
  104 + // iconColor: componentInfo[item + 'CustomIconColor'],
  105 + };
  106 + return option;
  107 + })
  108 + : structural.map((item, index) => {
  109 + const option = {
  110 + label: structuralLabel[index],
  111 + value: values[item],
  112 + icon: componentInfo[item + 'StructuralIcon'],
  113 + iconColor: componentInfo[item + 'StructuralIconColor'],
  114 + };
  115 + return option;
  116 + })
  117 + : [];
  118 + structuralData.value = newValues;
  119 + return;
  120 + }
  121 +
  122 + currentValue.value = useThingsModelValueTransform(
  123 + value,
  124 + unref(getThingModelTsl)?.specs?.dataType
  125 + );
  126 + };
  127 +
  128 + useDataFetch(props, updateFn);
  129 +
  130 + const { getScale, getRatio } = useComponentScale(props);
  131 +</script>
  132 +
  133 +<template>
  134 + <main
  135 + :style="getScale"
  136 + class="w-full h-full flex flex-col justify-center items-center"
  137 + :class="!getDesign.showTime && 'p-5'"
  138 + >
  139 + <DeviceName :config="config" />
  140 + <!-- <div class="flex-1 flex justify-center items-center flex-col w-full">
  141 + <SvgIcon
  142 + v-if="getDesign.defaultCustom !== 'custom'"
  143 + :name="getDesign.icon!"
  144 + prefix="iconfont"
  145 + :size="getRatio ? getRatio * 70 : 70"
  146 + :style="{ color: getDesign.iconColor }"
  147 + />
  148 + <img
  149 + v-else
  150 + :src="getDesign.customIcon[0]?.url"
  151 + :style="{ width: getRatio ? getRatio * 70 + 'px' : '70px' }"
  152 + :alt="getDesign.customIcon[0]?.name"
  153 + />
  154 + <h1
  155 + class="font-bold m-2 truncate w-full text-center"
  156 + :style="{
  157 + color: getDesign.fontColor,
  158 + fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
  159 + }"
  160 + >
  161 + <span> {{ currentValue || 0 }}</span>
  162 + <span>{{ getDesign.unit }} </span>
  163 + </h1>
  164 + <div
  165 + class="text-gray-500 truncate"
  166 + :style="{
  167 + fontSize: (getRatio ? getRatio * getDesign.fontSize : getDesign.fontSize) + 'px',
  168 + }"
  169 + >{{ getDesign.attribute || '温度' }}</div
  170 + >
  171 + </div> -->
  172 + <div class="flex flex-1 justify-center items-center w-full flex-wrap">
  173 + <div
  174 + v-for="item in structuralData"
  175 + :key="item.label"
  176 + class="flex-1 flex justify-center items-center flex-col w-full"
  177 + >
  178 + <SvgIcon
  179 + v-if="getDesign.defaultCustom !== 'custom'"
  180 + :name="item.icon || 'shuiwen'"
  181 + prefix="iconfont"
  182 + :size="getRatio ? (getRatio * 70) / structuralData?.length || 1 : 70"
  183 + :style="{ color: item.iconColor || '#367bff' }"
  184 + />
  185 + <img
  186 + v-else
  187 + :src="item.icon?.[0]?.url!"
  188 + :style="{
  189 + width: getRatio ? (getRatio * 70) / structuralData?.length + 'px' : '70px',
  190 + height: getRatio ? (getRatio * 70) / structuralData?.length + 'px' : '70px',
  191 + }"
  192 + :alt="item.icon?.[0]?.name!"
  193 + />
  194 + <h1
  195 + class="font-bold m-2 truncate w-full text-center"
  196 + :style="{
  197 + color: getDesign.fontColor,
  198 + fontSize:
  199 + (getRatio
  200 + ? (getRatio * getDesign.valueSize) / structuralData?.length || 1
  201 + : getDesign.valueSize) + 'px',
  202 + }"
  203 + >
  204 + <span> {{ item.value || 0 }}</span>
  205 + <span>{{ getDesign.unit }} </span>
  206 + </h1>
  207 + <div
  208 + class="text-gray-500 truncate text-center"
  209 + :style="{
  210 + fontSize: (getRatio ? getRatio * getDesign.fontSize : getDesign.fontSize) + 'px',
  211 + width: getRatio ? getRatio * 90 + 'px' : '90px',
  212 + }"
  213 + >{{ getDesign.attribute + '-' + item.label || '温度' }}</div
  214 + >
  215 + </div>
  216 + </div>
  217 + <UpdateTime v-show="getDesign.showTime" :time="time" />
  218 + </main>
  219 +</template>
... ...
... ... @@ -2,6 +2,7 @@ import { TextComponent1Config } from './TextComponent1';
2 2 import { TextComponent2Config } from './TextComponent2';
3 3 import { TextComponent3Config } from './TextComponent3';
4 4 import { TextComponent4Config } from './TextComponent4';
  5 +import { ComponentStructuralConfig } from './ComponentStructural';
5 6 import { ValueList1Config } from './ValueList1';
6 7 import { ValueList2Config } from './ValueList2';
7 8
... ... @@ -9,6 +10,7 @@ export const TextList = [
9 10 TextComponent1Config,
10 11 TextComponent2Config,
11 12 TextComponent3Config,
  13 + ComponentStructuralConfig,
12 14 TextComponent4Config,
13 15 ValueList1Config,
14 16 ValueList2Config,
... ...
... ... @@ -43,6 +43,7 @@ export enum DataSourceField {
43 43 ATTRIBUTE_RENAME = 'attributeRename',
44 44 DEVICE_NAME = 'deviceName',
45 45 DEVICE_RENAME = 'deviceRename',
  46 + STRUCTURAL_MORPHOLOGY = 'structural morphology',
46 47
47 48 // COMMAND = 'command',
48 49
... ... @@ -290,8 +291,9 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
290 291 colProps: { span: 8 },
291 292 rules: [{ required: true, message: '请选择属性' }],
292 293 ifShow: () => category !== CategoryEnum.MAP,
293   - componentProps({ formModel }) {
  294 + componentProps({ formModel, formActionType }) {
294 295 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  296 + const { setFieldsValue } = formActionType;
295 297 return {
296 298 api: async () => {
297 299 try {
... ... @@ -334,12 +336,166 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
334 336 } catch (error) {}
335 337 return [];
336 338 },
  339 +
  340 + apiTreeSelectProps: {
  341 + params: { deviceProfileId },
  342 + },
337 343 placeholder: '请选择属性',
  344 + onChange: (value: string, option: Record<'label' | 'value' | any, string>) => {
  345 + const { detail }: any = option || {};
  346 + if (value && detail?.dataType.type === 'STRUCT') {
  347 + }
  348 + setFieldsValue({
  349 + structuralOption:
  350 + value && detail?.dataType.type === 'STRUCT'
  351 + ? JSON.stringify(detail?.dataType.specs)
  352 + : null,
  353 + });
  354 + },
338 355 ...createPickerSearch(),
339 356 };
340 357 },
341 358 },
342 359 {
  360 + field: 'structuralOption',
  361 + label: '存储结构体的数据进行选择操作',
  362 + component: 'Input',
  363 + ifShow: false,
  364 + },
  365 + {
  366 + field: 'structural',
  367 + label: '选择结构体',
  368 + colProps: { span: 8 },
  369 + ifShow: ({ model }) =>
  370 + model.structuralOption && unref(selectWidgetKeys).componentKey === 'ComponentStructural',
  371 + component: 'Select',
  372 + componentProps: ({ formModel, formActionType }) => {
  373 + const { setFieldsValue } = formActionType;
  374 + const { structuralOption } = formModel || {};
  375 + return {
  376 + mode: 'multiple',
  377 + getPopupContainer: () => document.body,
  378 + placeholder: '请选择结构体参数',
  379 + options: structuralOption
  380 + ? JSON.parse(structuralOption)?.map((item) => ({
  381 + label: item.functionName,
  382 + value: item.identifier,
  383 + }))
  384 + : [],
  385 + onChange(value, option) {
  386 + if (value?.length && option?.length) {
  387 + const array = new Map(option.map((item) => [item.value, item.label]));
  388 + const filteredLabels = value.map((value) => array.get(value) || '');
  389 + setFieldsValue({
  390 + structuralLabel: filteredLabels?.length ? filteredLabels : [],
  391 + });
  392 + }
  393 + },
  394 + };
  395 + },
  396 + },
  397 + {
  398 + field: 'structuralLabel',
  399 + label: '结构体名称',
  400 + colProps: { span: 8 },
  401 + ifShow: false,
  402 + component: 'Select',
  403 + componentProps: {
  404 + mode: 'multiple',
  405 + getPopupContainer: () => document.body,
  406 + },
  407 + },
  408 +
  409 + // {
  410 + // field: DataSourceField.ATTRIBUTE,
  411 + // label: '属性',
  412 + // component: 'ApiCascader',
  413 + // colProps: { span: 8 },
  414 + // ifShow: () => false,
  415 + // changeEvent: 'update:value',
  416 + // valueField: 'value',
  417 + // componentProps: ({ formModel }) => {
  418 + // const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  419 + // return {
  420 + // api: async () => {
  421 + // try {
  422 + // // if (deviceProfileId) {
  423 + // // const options = await getDeviceAttributes({
  424 + // // deviceProfileId,
  425 + // // });
  426 +
  427 + // // const _options = options.map((item) => {
  428 + // // item = Object.assign(item, { functionName: item.name });
  429 + // // if (item.detail.dataType.type === DataTypeEnum.STRUCT) {
  430 + // // return Object.assign(item, { specs: item.detail.dataType.specs });
  431 + // // }
  432 + // // return item;
  433 + // // });
  434 + // // return _options;
  435 + // // }
  436 + // if (deviceProfileId) {
  437 + // let option = await getDeviceAttribute({
  438 + // deviceProfileId,
  439 + // dataType:
  440 + // (isControlComponent(category!) &&
  441 + // unref(selectWidgetKeys).componentKey !==
  442 + // ControlComponentEnum.LATERAL_NUMERICAL_CONTROL) ||
  443 + // isBooleanComponent(unref(selectWidgetKeys))
  444 + // ? DataTypeEnum.BOOL
  445 + // : undefined,
  446 + // });
  447 + // if (isControlComponent(category)) {
  448 + // // 过滤只读属性
  449 + // option = option.filter(
  450 + // (item) => item.accessMode !== ObjectModelAccessModeEnum.READ
  451 + // );
  452 + // }
  453 + // // 选择控制组件4的时候只能选择属性且是(int double类型)
  454 + // if (
  455 + // unref(selectWidgetKeys).componentKey ===
  456 + // ControlComponentEnum.LATERAL_NUMERICAL_CONTROL
  457 + // ) {
  458 + // // setFieldsValue({
  459 + // // [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(),
  460 + // // });
  461 + // return option.filter(
  462 + // (item) =>
  463 + // item.detail.dataType.type === DataTypeEnum.NUMBER_INT ||
  464 + // item.detail.dataType.type == DataTypeEnum.NUMBER_DOUBLE
  465 + // );
  466 + // }
  467 + // const _options = option.map((item) => {
  468 + // item = Object.assign(item, { functionName: item.name });
  469 + // if (item.detail.dataType.type === DataTypeEnum.STRUCT) {
  470 + // return Object.assign(item, { specs: item.detail.dataType.specs });
  471 + // }
  472 + // return item;
  473 + // });
  474 + // return _options;
  475 + // }
  476 + // } catch (error) {}
  477 + // return [];
  478 + // },
  479 + // placeholder: '请选择属性',
  480 + // showSearch: {
  481 + // filter: (inputValue: string, path) => {
  482 + // return path.some(
  483 + // (option) => option.functionName.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
  484 + // );
  485 + // },
  486 + // matchInputWidth: true,
  487 + // },
  488 + // changeOnSelect: true,
  489 + // dropdownMatchSelectWidth: true,
  490 + // // getPopupContainer: () => document.body,
  491 +
  492 + // getPopupContainer: (triggerNode) => triggerNode.parentNode,
  493 +
  494 + // fieldNames: { label: 'functionName', value: 'identifier', children: 'specs' },
  495 + // };
  496 + // },
  497 + // },
  498 + {
343 499 field: DataSourceField.LONGITUDE_IDENTIFIER,
344 500 label: '经度',
345 501 component: 'ApiCascader',
... ...