Commit 67d1de3831df2c6d6446ccfdcb6e177bf6b4a6c6

Authored by 张 峰林
Committed by xp.Huang
1 parent b1aeeabc

fix: 看板组件图标能使用自定义图标

@@ -71,9 +71,12 @@ @@ -71,9 +71,12 @@
71 return false; 71 return false;
72 } 72 }
73 } 73 }
74 -  
75 if (file.size > props.maxSize) { 74 if (file.size > props.maxSize) {
76 - createMessage.warning(`文件大小超过${Math.floor(props.maxSize / 1024 / 1024)}mb`); 75 + createMessage.warning(
  76 + `文件大小超过${Math.floor(
  77 + props.maxSize > 1024 * 1024 ? props.maxSize / 1024 / 1024 : props.maxSize / 1024
  78 + )}${props.maxSize > 1024 * 1024 * 1 ? 'mb' : 'kb'}`
  79 + );
77 return false; 80 return false;
78 } 81 }
79 handleUpload(file); 82 handleUpload(file);
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 auth: 'api:yt:sceneLinkage:update', 30 auth: 'api:yt:sceneLinkage:update',
31 icon: 'clarity:note-edit-line', 31 icon: 'clarity:note-edit-line',
32 onClick: handleEdit.bind(null, record), 32 onClick: handleEdit.bind(null, record),
33 - ifShow: record.status !== 1 && record.isEdge !== 1, 33 + ifShow: record.status !== 1,
34 }, 34 },
35 ]" 35 ]"
36 :drop-down-actions="[ 36 :drop-down-actions="[
@@ -39,7 +39,7 @@ @@ -39,7 +39,7 @@
39 auth: 'api:yt:sceneLinkage:delete', 39 auth: 'api:yt:sceneLinkage:delete',
40 icon: 'ant-design:delete-outlined', 40 icon: 'ant-design:delete-outlined',
41 color: 'error', 41 color: 'error',
42 - ifShow: record.status !== 1 && record.isEdge !== 1, 42 + ifShow: record.status !== 1,
43 popConfirm: { 43 popConfirm: {
44 title: '是否确认删除', 44 title: '是否确认删除',
45 confirm: handleDeleteOrBatchDelete.bind(null, record), 45 confirm: handleDeleteOrBatchDelete.bind(null, record),
@@ -56,7 +56,6 @@ @@ -56,7 +56,6 @@
56 :loading="record.pendingStatus" 56 :loading="record.pendingStatus"
57 checkedChildren="启用" 57 checkedChildren="启用"
58 unCheckedChildren="禁用" 58 unCheckedChildren="禁用"
59 - :disabled="record.isEdge === 1"  
60 @change="(checked:boolean)=>statusChange(checked,record)" 59 @change="(checked:boolean)=>statusChange(checked,record)"
61 /> 60 />
62 </Authority> 61 </Authority>
@@ -56,7 +56,7 @@ @@ -56,7 +56,7 @@
56 </script> 56 </script>
57 57
58 <template> 58 <template>
59 - <BasicModal @register="register" title="组件设置" @ok="handleOk"> 59 + <BasicModal @register="register" title="组件设置" @ok="handleOk" :width="700">
60 <!-- --> 60 <!-- -->
61 <component ref="settingFormEl" :is="getSettingComponent" /> 61 <component ref="settingFormEl" :is="getSettingComponent" />
62 </BasicModal> 62 </BasicModal>
@@ -29,6 +29,8 @@ @@ -29,6 +29,8 @@
29 import { MessageAlert } from './components/MessageAlert'; 29 import { MessageAlert } from './components/MessageAlert';
30 import { createSelectWidgetKeysContext, createSelectWidgetModeContext } from './useContext'; 30 import { createSelectWidgetKeysContext, createSelectWidgetModeContext } from './useContext';
31 import { useGetCategoryByComponentKey } from '../packages/hook/useGetCategoryByComponentKey'; 31 import { useGetCategoryByComponentKey } from '../packages/hook/useGetCategoryByComponentKey';
  32 + import { deleteFilePath } from '/@/api/oss/ossFileUploader';
  33 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
32 34
33 const props = defineProps<{ 35 const props = defineProps<{
34 layout: Layout[]; 36 layout: Layout[];
@@ -227,6 +229,20 @@ @@ -227,6 +229,20 @@
227 return `${category} / ${componentConfig.title}`; 229 return `${category} / ${componentConfig.title}`;
228 }); 230 });
229 231
  232 + const countElementOccurrences = (arr) => {
  233 + const countMap = {};
  234 +
  235 + arr.forEach((element) => {
  236 + if (countMap[element]) {
  237 + countMap[element]++;
  238 + } else {
  239 + countMap[element] = 1;
  240 + }
  241 + });
  242 +
  243 + return countMap;
  244 + };
  245 +
230 const handleSubmit = async () => { 246 const handleSubmit = async () => {
231 const validateResult = await validate(); 247 const validateResult = await validate();
232 if (validateResult && !validateResult.flag) { 248 if (validateResult && !validateResult.flag) {
@@ -241,6 +257,59 @@ @@ -241,6 +257,59 @@
241 } 257 }
242 } 258 }
243 const value = getFormValues(); 259 const value = getFormValues();
  260 + const { record } = value || {};
  261 + try {
  262 + 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 + });
  271 + // 取当前修改过后的自定义图片url
  272 + const dataSourceUrl = record.dataSource?.map(
  273 + (item) => item.componentInfo.customIcon?.[0].url
  274 + );
  275 +
  276 + // 当前自定义组件取出要进行删除的图标url
  277 + const dataSourceDeleteUrl = unref(currentRecordIconUrl).filter(
  278 + (item) => !dataSourceUrl?.includes(item)
  279 + );
  280 +
  281 + //查询外部所有组件的自定义图标的url
  282 + const oldDataSource = props.layout;
  283 + const customIconUrls = ref<any>([]);
  284 + oldDataSource?.forEach((item: any) => {
  285 + item.dataSource?.forEach((dataSource) => {
  286 + if (dataSource.componentInfo?.customIcon) {
  287 + dataSource.componentInfo?.customIcon.forEach((icon: FileItem) => {
  288 + customIconUrls.value.push(icon.url);
  289 + });
  290 + }
  291 + });
  292 + });
  293 + // const dataSourceDeleteUrl = record.dataSource?.map((item) => item.componentInfo.deleteUrl);
  294 +
  295 + if (unref(customIconUrls) && unref(customIconUrls).length && dataSourceDeleteUrl?.length) {
  296 + // 判断外部所有组件是否有dataSourceDeleteUrl使用中的url
  297 + const deletePromise = unref(customIconUrls)?.filter((item) =>
  298 + dataSourceDeleteUrl?.includes(item)
  299 + );
  300 + const deleteUrlInfo = countElementOccurrences(deletePromise);
  301 + const deleteUrl = deletePromise?.filter((item) => deleteUrlInfo?.[item] == 1);
  302 + Promise.all(
  303 + deleteUrl.map((item) => {
  304 + deleteFilePath(item);
  305 + })
  306 + );
  307 + }
  308 + } catch (err) {
  309 + // eslint-disable-next-line no-console
  310 + console.log(err);
  311 + }
  312 +
244 try { 313 try {
245 loading.value = true; 314 loading.value = true;
246 unref(currentMode) === DataActionModeEnum.UPDATE 315 unref(currentMode) === DataActionModeEnum.UPDATE
@@ -3,15 +3,68 @@ @@ -3,15 +3,68 @@
3 import { useForm, BasicForm } from '/@/components/Form'; 3 import { useForm, BasicForm } from '/@/components/Form';
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
  6 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 + import { createImgPreview } from '/@/components/Preview';
  8 + import { upload } from '/@/api/oss/ossFileUploader';
6 9
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 10 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 11 schemas: [
9 { 12 {
  13 + field: ComponentConfigFieldEnum.FONT_SIZE,
  14 + label: '文本字体大小',
  15 + component: 'InputNumber',
  16 + defaultValue: 14,
  17 + componentProps: {
  18 + min: 0,
  19 + max: 100,
  20 + formatter: (e) => {
  21 + const value = e?.toString().replace(/^0/g, '');
  22 + if (value) {
  23 + return value.replace(/^0/g, '');
  24 + } else {
  25 + return 0;
  26 + }
  27 + },
  28 + },
  29 + },
  30 + {
  31 + field: ComponentConfigFieldEnum.PASS_WORD,
  32 + label: '操作密码',
  33 + component: 'InputPassword',
  34 + defaultValue: '',
  35 + },
  36 + {
  37 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  38 + label: '显示设备名称',
  39 + component: 'Checkbox',
  40 + defaultValue: option.showDeviceName,
  41 + },
  42 + {
  43 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  44 + label: '图标类型',
  45 + component: 'RadioGroup',
  46 + defaultValue: 'default',
  47 + componentProps: ({ formModel }) => {
  48 + return {
  49 + options: [
  50 + { label: '系统默认', value: 'default' },
  51 + { label: '自定义', value: 'custom' },
  52 + ],
  53 + onChange() {
  54 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  55 + },
  56 + };
  57 + },
  58 + },
  59 + {
10 field: ComponentConfigFieldEnum.ICON_COLOR, 60 field: ComponentConfigFieldEnum.ICON_COLOR,
11 label: '图标颜色', 61 label: '图标颜色',
12 component: 'ColorPicker', 62 component: 'ColorPicker',
13 changeEvent: 'update:value', 63 changeEvent: 'update:value',
14 defaultValue: option.iconColor, 64 defaultValue: option.iconColor,
  65 + ifShow: ({ model }) => {
  66 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  67 + },
15 }, 68 },
16 { 69 {
17 field: ComponentConfigFieldEnum.ICON, 70 field: ComponentConfigFieldEnum.ICON,
@@ -19,6 +72,9 @@ @@ -19,6 +72,9 @@
19 component: 'IconDrawer', 72 component: 'IconDrawer',
20 changeEvent: 'update:value', 73 changeEvent: 'update:value',
21 defaultValue: option.icon, 74 defaultValue: option.icon,
  75 + ifShow: ({ model }) => {
  76 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  77 + },
22 componentProps({ formModel }) { 78 componentProps({ formModel }) {
23 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR]; 79 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
24 return { 80 return {
@@ -27,34 +83,51 @@ @@ -27,34 +83,51 @@
27 }, 83 },
28 }, 84 },
29 { 85 {
30 - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,  
31 - label: '显示设备名称',  
32 - component: 'Checkbox',  
33 - defaultValue: option.showDeviceName,  
34 - },  
35 - {  
36 - field: ComponentConfigFieldEnum.FONT_SIZE,  
37 - label: '文本字体大小',  
38 - component: 'InputNumber',  
39 - defaultValue: 14,  
40 - componentProps: {  
41 - min: 0,  
42 - max: 100,  
43 - formatter: (e) => {  
44 - const value = e?.toString().replace(/^0/g, '');  
45 - if (value) {  
46 - return value.replace(/^0/g, '');  
47 - } else {  
48 - return 0;  
49 - }  
50 - }, 86 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  87 + label: '图标',
  88 + component: 'ApiUpload',
  89 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  90 + changeEvent: 'update:fileList',
  91 + valueField: 'fileList',
  92 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  93 + componentProps: ({ formModel }) => {
  94 + return {
  95 + listType: 'picture-card',
  96 + maxFileLimit: 1,
  97 + maxSize: 50 * 1024,
  98 + accept: '.svg',
  99 + api: async (file: File) => {
  100 + try {
  101 + const formData = new FormData();
  102 + const { name } = file;
  103 + formData.set('file', file);
  104 + const { fileStaticUri, fileName } = await upload(formData);
  105 + return {
  106 + uid: fileStaticUri,
  107 + name: name || fileName,
  108 + url: fileStaticUri,
  109 + } as FileItem;
  110 + } catch (error) {
  111 + return {};
  112 + }
  113 + },
  114 + // showUploadList: true,
  115 + onDownload() {},
  116 + onPreview: (fileList: FileItem) => {
  117 + createImgPreview({ imageList: [fileList.url!] });
  118 + },
  119 +
  120 + onDelete(url: string) {
  121 + formModel.deleteUrl = url!;
  122 + },
  123 + };
51 }, 124 },
52 }, 125 },
53 { 126 {
54 - field: ComponentConfigFieldEnum.PASS_WORD,  
55 - label: '操作密码',  
56 - component: 'InputPassword',  
57 - defaultValue: '', 127 + field: 'deleteUrl',
  128 + label: '',
  129 + component: 'Input',
  130 + show: false,
58 }, 131 },
59 ], 132 ],
60 showActionButtonGroup: false, 133 showActionButtonGroup: false,
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 fontSize: persetFontSize, 31 fontSize: persetFontSize,
32 password: persetPassword, 32 password: persetPassword,
33 } = persetOption || {}; 33 } = persetOption || {};
34 - const { icon, iconColor, fontSize, password } = componentInfo || {}; 34 + const { icon, iconColor, fontSize, password, customIcon, defaultCustom } = componentInfo || {};
35 35
36 const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute); 36 const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
37 return { 37 return {
@@ -41,6 +41,8 @@ @@ -41,6 +41,8 @@
41 fontSize: fontSize || persetFontSize || 14, 41 fontSize: fontSize || persetFontSize || 14,
42 password: password || persetPassword, 42 password: password || persetPassword,
43 commandType, 43 commandType,
  44 + defaultCustom: defaultCustom || 'default',
  45 + customIcon: customIcon || [],
44 }; 46 };
45 }); 47 });
46 48
@@ -81,10 +83,17 @@ @@ -81,10 +83,17 @@
81 <main class="w-full h-full flex justify-around items-center" :style="getScale"> 83 <main class="w-full h-full flex justify-around items-center" :style="getScale">
82 <div class="flex flex-col justify-center items-center"> 84 <div class="flex flex-col justify-center items-center">
83 <SvgIcon 85 <SvgIcon
84 - :name="getDesign.icon" 86 + v-if="getDesign.defaultCustom !== 'custom'"
  87 + :name="getDesign.icon!"
85 prefix="iconfont" 88 prefix="iconfont"
86 - :style="{ color: getDesign.iconColor }"  
87 :size="getRatio ? getRatio * 60 : 60" 89 :size="getRatio ? getRatio * 60 : 60"
  90 + :style="{ color: getDesign.iconColor }"
  91 + />
  92 + <img
  93 + v-else
  94 + :src="getDesign.customIcon[0]?.url"
  95 + :style="{ width: getRatio ? getRatio * 60 + 'px' : '60px' }"
  96 + :alt="getDesign.customIcon[0]?.name"
88 /> 97 />
89 <span 98 <span
90 class="mt-3 truncate text-gray-500 text-center" 99 class="mt-3 truncate text-gray-500 text-center"
@@ -3,30 +3,13 @@ @@ -3,30 +3,13 @@
3 import { useForm, BasicForm } from '/@/components/Form'; 3 import { useForm, BasicForm } from '/@/components/Form';
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
  6 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 + import { createImgPreview } from '/@/components/Preview';
  8 + import { upload } from '/@/api/oss/ossFileUploader';
6 9
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 10 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 11 schemas: [
9 { 12 {
10 - field: ComponentConfigFieldEnum.ICON_COLOR,  
11 - label: '图标颜色',  
12 - component: 'ColorPicker',  
13 - changeEvent: 'update:value',  
14 - defaultValue: option.iconColor,  
15 - },  
16 - {  
17 - field: ComponentConfigFieldEnum.ICON,  
18 - label: '图标',  
19 - component: 'IconDrawer',  
20 - changeEvent: 'update:value',  
21 - defaultValue: option.icon,  
22 - componentProps({ formModel }) {  
23 - const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];  
24 - return {  
25 - color,  
26 - };  
27 - },  
28 - },  
29 - {  
30 field: ComponentConfigFieldEnum.FONT_SIZE, 13 field: ComponentConfigFieldEnum.FONT_SIZE,
31 label: '文本字体大小', 14 label: '文本字体大小',
32 component: 'InputNumber', 15 component: 'InputNumber',
@@ -45,16 +28,106 @@ @@ -45,16 +28,106 @@
45 }, 28 },
46 }, 29 },
47 { 30 {
  31 + field: ComponentConfigFieldEnum.PASS_WORD,
  32 + label: '操作密码',
  33 + component: 'InputPassword',
  34 + defaultValue: '',
  35 + },
  36 + {
48 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME, 37 field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
49 label: '显示设备名称', 38 label: '显示设备名称',
50 component: 'Checkbox', 39 component: 'Checkbox',
51 defaultValue: option.showDeviceName, 40 defaultValue: option.showDeviceName,
52 }, 41 },
53 { 42 {
54 - field: ComponentConfigFieldEnum.PASS_WORD,  
55 - label: '操作密码',  
56 - component: 'InputPassword',  
57 - defaultValue: '', 43 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  44 + label: '图标类型',
  45 + component: 'RadioGroup',
  46 + defaultValue: 'default',
  47 + componentProps: ({ formModel }) => {
  48 + return {
  49 + options: [
  50 + { label: '系统默认', value: 'default' },
  51 + { label: '自定义', value: 'custom' },
  52 + ],
  53 + onChange() {
  54 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  55 + },
  56 + };
  57 + },
  58 + },
  59 + {
  60 + field: ComponentConfigFieldEnum.ICON_COLOR,
  61 + label: '图标颜色',
  62 + component: 'ColorPicker',
  63 + changeEvent: 'update:value',
  64 + defaultValue: option.iconColor,
  65 + ifShow: ({ model }) => {
  66 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  67 + },
  68 + },
  69 + {
  70 + field: ComponentConfigFieldEnum.ICON,
  71 + label: '图标',
  72 + component: 'IconDrawer',
  73 + changeEvent: 'update:value',
  74 + defaultValue: option.icon,
  75 + ifShow: ({ model }) => {
  76 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  77 + },
  78 + componentProps({ formModel }) {
  79 + const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
  80 + return {
  81 + color,
  82 + };
  83 + },
  84 + },
  85 + {
  86 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  87 + label: '图标',
  88 + component: 'ApiUpload',
  89 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  90 + changeEvent: 'update:fileList',
  91 + valueField: 'fileList',
  92 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  93 + componentProps: ({ formModel }) => {
  94 + return {
  95 + listType: 'picture-card',
  96 + maxSize: 50 * 1024,
  97 + maxFileLimit: 1,
  98 + accept: '.svg',
  99 + api: async (file: File) => {
  100 + try {
  101 + const formData = new FormData();
  102 + const { name } = file;
  103 + formData.set('file', file);
  104 + const { fileStaticUri, fileName } = await upload(formData);
  105 + return {
  106 + uid: fileStaticUri,
  107 + name: name || fileName,
  108 + url: fileStaticUri,
  109 + } as FileItem;
  110 + } catch (error) {
  111 + return {};
  112 + }
  113 + },
  114 + // showUploadList: true,
  115 + onDownload() {},
  116 + onPreview: (fileList: FileItem) => {
  117 + createImgPreview({ imageList: [fileList.url!] });
  118 + },
  119 +
  120 + onDelete(url: string) {
  121 + formModel.deleteUrl = url!;
  122 + },
  123 + };
  124 + },
  125 + },
  126 + {
  127 + field: 'deleteUrl',
  128 + label: '',
  129 + component: 'Input',
  130 + show: false,
58 }, 131 },
59 ], 132 ],
60 showActionButtonGroup: false, 133 showActionButtonGroup: false,
@@ -38,8 +38,17 @@ @@ -38,8 +38,17 @@
38 } = persetOption || {}; 38 } = persetOption || {};
39 return { 39 return {
40 dataSource: dataSource.map((item) => { 40 dataSource: dataSource.map((item) => {
41 - const { fontColor, icon, iconColor, unit, showDeviceName, password, fontSize } =  
42 - item.componentInfo; 41 + const {
  42 + fontColor,
  43 + icon,
  44 + iconColor,
  45 + unit,
  46 + showDeviceName,
  47 + password,
  48 + fontSize,
  49 + customIcon,
  50 + defaultCustom,
  51 + } = item.componentInfo;
43 const { 52 const {
44 attribute, 53 attribute,
45 attributeRename, 54 attributeRename,
@@ -76,6 +85,8 @@ @@ -76,6 +85,8 @@
76 closeCommand, 85 closeCommand,
77 openService, 86 openService,
78 closeService, 87 closeService,
  88 + defaultCustom: defaultCustom || 'default',
  89 + customIcon: customIcon || [],
79 } as SwitchItemType; 90 } as SwitchItemType;
80 }), 91 }),
81 }; 92 };
@@ -128,11 +139,24 @@ @@ -128,11 +139,24 @@
128 :key="item.id" 139 :key="item.id"
129 class="flex justify-between items-center w-full px-4" 140 class="flex justify-between items-center w-full px-4"
130 > 141 >
131 - <SvgIcon 142 + <!-- <SvgIcon
132 :name="item.icon!" 143 :name="item.icon!"
133 prefix="iconfont" 144 prefix="iconfont"
134 :size="getRatio ? 30 * getRatio : 30" 145 :size="getRatio ? 30 * getRatio : 30"
135 :style="{ color: item.iconColor }" 146 :style="{ color: item.iconColor }"
  147 + /> -->
  148 + <SvgIcon
  149 + v-if="item.defaultCustom !== 'custom'"
  150 + :name="item.icon!"
  151 + prefix="iconfont"
  152 + :size="getRatio ? getRatio * 30 : 30"
  153 + :style="{ color: item.iconColor }"
  154 + />
  155 + <img
  156 + v-else
  157 + :src="item.customIcon[0]?.url"
  158 + :style="{ width: getRatio ? getRatio * 30 + 'px' : '30px' }"
  159 + :alt="item.customIcon[0]?.name"
136 /> 160 />
137 <div 161 <div
138 class="text-gray-500 truncate mx-2" 162 class="text-gray-500 truncate mx-2"
@@ -3,15 +3,68 @@ @@ -3,15 +3,68 @@
3 import { useForm, BasicForm } from '/@/components/Form'; 3 import { useForm, BasicForm } from '/@/components/Form';
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
  6 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 + import { createImgPreview } from '/@/components/Preview';
  8 + import { upload } from '/@/api/oss/ossFileUploader';
6 9
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 10 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 11 schemas: [
9 { 12 {
  13 + field: ComponentConfigFieldEnum.FONT_SIZE,
  14 + label: '文本字体大小',
  15 + component: 'InputNumber',
  16 + defaultValue: 14,
  17 + componentProps: {
  18 + min: 0,
  19 + max: 100,
  20 + formatter: (e) => {
  21 + const value = e?.toString().replace(/^0/g, '');
  22 + if (value) {
  23 + return value.replace(/^0/g, '');
  24 + } else {
  25 + return 0;
  26 + }
  27 + },
  28 + },
  29 + },
  30 + {
  31 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  32 + label: '显示设备名称',
  33 + component: 'Checkbox',
  34 + defaultValue: option.showDeviceName,
  35 + },
  36 + {
  37 + field: ComponentConfigFieldEnum.SHOW_TIME,
  38 + label: '显示时间',
  39 + component: 'Checkbox',
  40 + defaultValue: option.showTime,
  41 + },
  42 + {
  43 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  44 + label: '图标类型',
  45 + component: 'RadioGroup',
  46 + defaultValue: 'default',
  47 + componentProps: ({ formModel }) => {
  48 + return {
  49 + options: [
  50 + { label: '系统默认', value: 'default' },
  51 + { label: '自定义', value: 'custom' },
  52 + ],
  53 + onChange() {
  54 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  55 + },
  56 + };
  57 + },
  58 + },
  59 + {
10 field: ComponentConfigFieldEnum.ICON, 60 field: ComponentConfigFieldEnum.ICON,
11 label: '开启状态图标', 61 label: '开启状态图标',
12 component: 'IconDrawer', 62 component: 'IconDrawer',
13 changeEvent: 'update:value', 63 changeEvent: 'update:value',
14 defaultValue: option.icon, 64 defaultValue: option.icon,
  65 + ifShow: ({ model }) => {
  66 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  67 + },
15 componentProps({ formModel }) { 68 componentProps({ formModel }) {
16 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR]; 69 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
17 return { 70 return {
@@ -24,6 +77,9 @@ @@ -24,6 +77,9 @@
24 label: '开启图标颜色', 77 label: '开启图标颜色',
25 component: 'ColorPicker', 78 component: 'ColorPicker',
26 changeEvent: 'update:value', 79 changeEvent: 'update:value',
  80 + ifShow: ({ model }) => {
  81 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  82 + },
27 defaultValue: option.iconColor, 83 defaultValue: option.iconColor,
28 }, 84 },
29 { 85 {
@@ -32,6 +88,9 @@ @@ -32,6 +88,9 @@
32 component: 'IconDrawer', 88 component: 'IconDrawer',
33 changeEvent: 'update:value', 89 changeEvent: 'update:value',
34 defaultValue: option.iconClose, 90 defaultValue: option.iconClose,
  91 + ifShow: ({ model }) => {
  92 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  93 + },
35 componentProps({ formModel }) { 94 componentProps({ formModel }) {
36 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR_CLOSE]; 95 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR_CLOSE];
37 return { 96 return {
@@ -44,37 +103,84 @@ @@ -44,37 +103,84 @@
44 label: '关闭图标颜色', 103 label: '关闭图标颜色',
45 component: 'ColorPicker', 104 component: 'ColorPicker',
46 changeEvent: 'update:value', 105 changeEvent: 'update:value',
  106 + ifShow: ({ model }) => {
  107 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  108 + },
47 defaultValue: option.iconColorClose, 109 defaultValue: option.iconColorClose,
48 }, 110 },
49 { 111 {
50 - field: ComponentConfigFieldEnum.FONT_SIZE,  
51 - label: '文本字体大小',  
52 - component: 'InputNumber',  
53 - defaultValue: 14,  
54 - componentProps: {  
55 - min: 0,  
56 - max: 100,  
57 - formatter: (e) => {  
58 - const value = e?.toString().replace(/^0/g, '');  
59 - if (value) {  
60 - return value.replace(/^0/g, '');  
61 - } else {  
62 - return 0;  
63 - }  
64 - }, 112 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  113 + label: '开启状态图标',
  114 + component: 'ApiUpload',
  115 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  116 + changeEvent: 'update:fileList',
  117 + valueField: 'fileList',
  118 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  119 + componentProps: ({}) => {
  120 + return {
  121 + listType: 'picture-card',
  122 + maxSize: 50 * 1024,
  123 + maxFileLimit: 1,
  124 + accept: '.svg',
  125 + api: async (file: File) => {
  126 + try {
  127 + const formData = new FormData();
  128 + const { name } = file;
  129 + formData.set('file', file);
  130 + const { fileStaticUri, fileName } = await upload(formData);
  131 + return {
  132 + uid: fileStaticUri,
  133 + name: name || fileName,
  134 + url: fileStaticUri,
  135 + } as FileItem;
  136 + } catch (error) {
  137 + return {};
  138 + }
  139 + },
  140 + // showUploadList: true,
  141 + onDownload() {},
  142 + onPreview: (fileList: FileItem) => {
  143 + createImgPreview({ imageList: [fileList.url!] });
  144 + },
  145 + };
65 }, 146 },
66 }, 147 },
67 { 148 {
68 - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,  
69 - label: '显示设备名称',  
70 - component: 'Checkbox',  
71 - defaultValue: option.showDeviceName,  
72 - },  
73 - {  
74 - field: ComponentConfigFieldEnum.SHOW_TIME,  
75 - label: '显示时间',  
76 - component: 'Checkbox',  
77 - defaultValue: option.showTime, 149 + field: ComponentConfigFieldEnum.CUSTOM_ICON_CLOSE,
  150 + label: '关闭状态图标',
  151 + component: 'ApiUpload',
  152 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  153 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  154 + changeEvent: 'update:fileList',
  155 + valueField: 'fileList',
  156 + componentProps: ({}) => {
  157 + return {
  158 + listType: 'picture-card',
  159 + maxSize: 0 * 1024,
  160 + maxFileLimit: 1,
  161 + accept: '.svg',
  162 + api: async (file: File) => {
  163 + try {
  164 + const formData = new FormData();
  165 + const { name } = file;
  166 + formData.set('file', file);
  167 + const { fileStaticUri, fileName } = await upload(formData);
  168 + return {
  169 + uid: fileStaticUri,
  170 + name: name || fileName,
  171 + url: fileStaticUri,
  172 + } as FileItem;
  173 + } catch (error) {
  174 + return {};
  175 + }
  176 + },
  177 + // showUploadList: true,
  178 + onDownload() {},
  179 + onPreview: (fileList: FileItem) => {
  180 + createImgPreview({ imageList: [fileList.url!] });
  181 + },
  182 + };
  183 + },
78 }, 184 },
79 ], 185 ],
80 showActionButtonGroup: false, 186 showActionButtonGroup: false,
@@ -32,8 +32,19 @@ @@ -32,8 +32,19 @@
32 32
33 const { componentInfo, attributeName, attributeRename } = option; 33 const { componentInfo, attributeName, attributeRename } = option;
34 34
35 - const { icon, iconColor, fontColor, unit, iconClose, iconColorClose, showTime, fontSize } =  
36 - componentInfo || {}; 35 + const {
  36 + icon,
  37 + iconColor,
  38 + fontColor,
  39 + unit,
  40 + iconClose,
  41 + iconColorClose,
  42 + showTime,
  43 + fontSize,
  44 + customIcon,
  45 + customIconClose,
  46 + defaultCustom,
  47 + } = componentInfo || {};
37 return { 48 return {
38 iconColor: iconColor || persetIconColor, 49 iconColor: iconColor || persetIconColor,
39 unit: unit ?? perseUnit, 50 unit: unit ?? perseUnit,
@@ -44,6 +55,9 @@ @@ -44,6 +55,9 @@
44 iconColorClose: iconColorClose || persetIconColorClose, 55 iconColorClose: iconColorClose || persetIconColorClose,
45 showTime: showTime ?? persetShowTime, 56 showTime: showTime ?? persetShowTime,
46 fontSize: fontSize || persetFontSize || 14, 57 fontSize: fontSize || persetFontSize || 14,
  58 + defaultCustom: defaultCustom || 'default',
  59 + customIcon: customIcon || [],
  60 + customIconClose: customIconClose || [],
47 }; 61 };
48 }); 62 });
49 63
@@ -68,10 +82,17 @@ @@ -68,10 +82,17 @@
68 <DeviceName :config="config" /> 82 <DeviceName :config="config" />
69 <div class="flex flex-1 flex-col justify-center items-center"> 83 <div class="flex flex-1 flex-col justify-center items-center">
70 <SvgIcon 84 <SvgIcon
  85 + v-if="getDesign.defaultCustom !== 'custom'"
71 :name="isOpenClose ? getDesign.icon : getDesign.iconClose" 86 :name="isOpenClose ? getDesign.icon : getDesign.iconClose"
72 prefix="iconfont" 87 prefix="iconfont"
73 :size="getRatio ? getRatio * 70 : 70" 88 :size="getRatio ? getRatio * 70 : 70"
74 - :style="{ color: isOpenClose ? getDesign.iconColor : getDesign.iconColorClose }" 89 + :style="{ color: getDesign.iconColor }"
  90 + />
  91 + <img
  92 + v-else
  93 + :src="isOpenClose ? getDesign.customIcon[0]?.url : getDesign.customIconClose[0]?.url"
  94 + :style="{ width: getRatio ? getRatio * 70 + 'px' : '70px' }"
  95 + :alt="getDesign.customIcon[0]?.name"
75 /> 96 />
76 <div 97 <div
77 class="text-gray-500 truncate m-2" 98 class="text-gray-500 truncate m-2"
@@ -3,6 +3,9 @@ @@ -3,6 +3,9 @@
3 import { useForm, BasicForm } from '/@/components/Form'; 3 import { useForm, BasicForm } from '/@/components/Form';
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
  6 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 + import { createImgPreview } from '/@/components/Preview';
  8 + import { upload } from '/@/api/oss/ossFileUploader';
6 9
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 10 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 11 schemas: [
@@ -61,11 +64,37 @@ @@ -61,11 +64,37 @@
61 }, 64 },
62 }, 65 },
63 { 66 {
  67 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  68 + label: '显示设备名称',
  69 + component: 'Checkbox',
  70 + defaultValue: option.showDeviceName,
  71 + },
  72 + {
  73 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  74 + label: '图标类型',
  75 + component: 'RadioGroup',
  76 + defaultValue: 'default',
  77 + componentProps: ({ formModel }) => {
  78 + return {
  79 + options: [
  80 + { label: '系统默认', value: 'default' },
  81 + { label: '自定义', value: 'custom' },
  82 + ],
  83 + onChange() {
  84 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  85 + },
  86 + };
  87 + },
  88 + },
  89 + {
64 field: ComponentConfigFieldEnum.ICON_COLOR, 90 field: ComponentConfigFieldEnum.ICON_COLOR,
65 label: '图标颜色', 91 label: '图标颜色',
66 component: 'ColorPicker', 92 component: 'ColorPicker',
67 changeEvent: 'update:value', 93 changeEvent: 'update:value',
68 defaultValue: option.iconColor, 94 defaultValue: option.iconColor,
  95 + ifShow: ({ model }) => {
  96 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  97 + },
69 }, 98 },
70 { 99 {
71 field: ComponentConfigFieldEnum.ICON, 100 field: ComponentConfigFieldEnum.ICON,
@@ -73,6 +102,9 @@ @@ -73,6 +102,9 @@
73 component: 'IconDrawer', 102 component: 'IconDrawer',
74 changeEvent: 'update:value', 103 changeEvent: 'update:value',
75 defaultValue: option.icon, 104 defaultValue: option.icon,
  105 + ifShow: ({ model }) => {
  106 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  107 + },
76 componentProps({ formModel }) { 108 componentProps({ formModel }) {
77 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR]; 109 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
78 return { 110 return {
@@ -81,10 +113,50 @@ @@ -81,10 +113,50 @@
81 }, 113 },
82 }, 114 },
83 { 115 {
84 - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,  
85 - label: '显示设备名称',  
86 - component: 'Checkbox',  
87 - defaultValue: option.showDeviceName, 116 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  117 + label: '图标',
  118 + component: 'ApiUpload',
  119 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  120 + changeEvent: 'update:fileList',
  121 + valueField: 'fileList',
  122 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  123 + componentProps: ({ formModel }) => {
  124 + return {
  125 + maxSize: 50 * 1024,
  126 + listType: 'picture-card',
  127 + maxFileLimit: 1,
  128 + accept: '.svg',
  129 + api: async (file: File) => {
  130 + try {
  131 + const formData = new FormData();
  132 + const { name } = file;
  133 + formData.set('file', file);
  134 + const { fileStaticUri, fileName } = await upload(formData);
  135 + return {
  136 + uid: fileStaticUri,
  137 + name: name || fileName,
  138 + url: fileStaticUri,
  139 + } as FileItem;
  140 + } catch (error) {
  141 + return {};
  142 + }
  143 + },
  144 + // showUploadList: true,
  145 + onDownload() {},
  146 + onPreview: (fileList: FileItem) => {
  147 + createImgPreview({ imageList: [fileList.url!] });
  148 + },
  149 + onDelete(url: string) {
  150 + formModel.deleteUrl = url!;
  151 + },
  152 + };
  153 + },
  154 + },
  155 + {
  156 + field: 'deleteUrl',
  157 + label: '',
  158 + component: 'Input',
  159 + show: false,
88 }, 160 },
89 ], 161 ],
90 showActionButtonGroup: false, 162 showActionButtonGroup: false,
@@ -31,7 +31,6 @@ @@ -31,7 +31,6 @@
31 31
32 const getDesign = computed(() => { 32 const getDesign = computed(() => {
33 const { persetOption = {}, option } = props.config; 33 const { persetOption = {}, option } = props.config;
34 -  
35 const { 34 const {
36 iconColor: persetIconColor, 35 iconColor: persetIconColor,
37 unit: perseUnit, 36 unit: perseUnit,
@@ -44,7 +43,8 @@ @@ -44,7 +43,8 @@
44 const { componentInfo, attributeRename } = option; 43 const { componentInfo, attributeRename } = option;
45 const { functionName } = unref(getThingModelTsl) || {}; 44 const { functionName } = unref(getThingModelTsl) || {};
46 45
47 - const { icon, iconColor, fontColor, unit, valueSize, fontSize } = componentInfo || {}; 46 + const { icon, iconColor, fontColor, unit, valueSize, fontSize, customIcon, defaultCustom } =
  47 + componentInfo || {};
48 return { 48 return {
49 iconColor: iconColor || persetIconColor, 49 iconColor: iconColor || persetIconColor,
50 unit: unit ?? perseUnit, 50 unit: unit ?? perseUnit,
@@ -53,6 +53,8 @@ @@ -53,6 +53,8 @@
53 attribute: attributeRename || functionName, 53 attribute: attributeRename || functionName,
54 valueSize: valueSize || persetValueSize || 20, 54 valueSize: valueSize || persetValueSize || 20,
55 fontSize: fontSize || persetFontSize || 14, 55 fontSize: fontSize || persetFontSize || 14,
  56 + defaultCustom: defaultCustom || 'default',
  57 + customIcon: customIcon || [],
56 }; 58 };
57 }); 59 });
58 60
@@ -78,11 +80,18 @@ @@ -78,11 +80,18 @@
78 <DeviceName :config="config" /> 80 <DeviceName :config="config" />
79 <div class="flex-1 flex justify-center items-center flex-col w-full"> 81 <div class="flex-1 flex justify-center items-center flex-col w-full">
80 <SvgIcon 82 <SvgIcon
  83 + v-if="getDesign.defaultCustom !== 'custom'"
81 :name="getDesign.icon!" 84 :name="getDesign.icon!"
82 prefix="iconfont" 85 prefix="iconfont"
83 :size="getRatio ? getRatio * 70 : 70" 86 :size="getRatio ? getRatio * 70 : 70"
84 :style="{ color: getDesign.iconColor }" 87 :style="{ color: getDesign.iconColor }"
85 /> 88 />
  89 + <img
  90 + v-else
  91 + :src="getDesign.customIcon[0]?.url"
  92 + :style="{ width: getRatio ? getRatio * 70 + 'px' : '70px' }"
  93 + :alt="getDesign.customIcon[0]?.name"
  94 + />
86 <h1 95 <h1
87 class="font-bold m-2 truncate w-full text-center" 96 class="font-bold m-2 truncate w-full text-center"
88 :style="{ 97 :style="{
@@ -3,6 +3,9 @@ @@ -3,6 +3,9 @@
3 import { useForm, BasicForm } from '/@/components/Form'; 3 import { useForm, BasicForm } from '/@/components/Form';
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
  6 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  7 + import { createImgPreview } from '/@/components/Preview';
  8 + import { upload } from '/@/api/oss/ossFileUploader';
6 9
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 10 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 11 schemas: [
@@ -60,11 +63,37 @@ @@ -60,11 +63,37 @@
60 }, 63 },
61 }, 64 },
62 { 65 {
  66 + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
  67 + label: '显示设备名称',
  68 + component: 'Checkbox',
  69 + defaultValue: option.showDeviceName,
  70 + },
  71 + {
  72 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  73 + label: '图标类型',
  74 + component: 'RadioGroup',
  75 + defaultValue: 'default',
  76 + componentProps: ({ formModel }) => {
  77 + return {
  78 + options: [
  79 + { label: '系统默认', value: 'default' },
  80 + { label: '自定义', value: 'custom' },
  81 + ],
  82 + onChange() {
  83 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  84 + },
  85 + };
  86 + },
  87 + },
  88 + {
63 field: ComponentConfigFieldEnum.ICON_COLOR, 89 field: ComponentConfigFieldEnum.ICON_COLOR,
64 label: '图标颜色', 90 label: '图标颜色',
65 component: 'ColorPicker', 91 component: 'ColorPicker',
66 changeEvent: 'update:value', 92 changeEvent: 'update:value',
67 defaultValue: option.iconColor, 93 defaultValue: option.iconColor,
  94 + ifShow: ({ model }) => {
  95 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  96 + },
68 }, 97 },
69 { 98 {
70 field: ComponentConfigFieldEnum.ICON, 99 field: ComponentConfigFieldEnum.ICON,
@@ -72,6 +101,9 @@ @@ -72,6 +101,9 @@
72 component: 'IconDrawer', 101 component: 'IconDrawer',
73 changeEvent: 'update:value', 102 changeEvent: 'update:value',
74 defaultValue: option.icon, 103 defaultValue: option.icon,
  104 + ifShow: ({ model }) => {
  105 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  106 + },
75 componentProps({ formModel }) { 107 componentProps({ formModel }) {
76 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR]; 108 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
77 return { 109 return {
@@ -80,10 +112,50 @@ @@ -80,10 +112,50 @@
80 }, 112 },
81 }, 113 },
82 { 114 {
83 - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,  
84 - label: '显示设备名称',  
85 - component: 'Checkbox',  
86 - defaultValue: option.showDeviceName, 115 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  116 + label: '图标',
  117 + component: 'ApiUpload',
  118 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  119 + changeEvent: 'update:fileList',
  120 + valueField: 'fileList',
  121 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  122 + componentProps: ({ formModel }) => {
  123 + return {
  124 + listType: 'picture-card',
  125 + maxSize: 50 * 1024,
  126 + maxFileLimit: 1,
  127 + accept: '.svg',
  128 + api: async (file: File) => {
  129 + try {
  130 + const formData = new FormData();
  131 + const { name } = file;
  132 + formData.set('file', file);
  133 + const { fileStaticUri, fileName } = await upload(formData);
  134 + return {
  135 + uid: fileStaticUri,
  136 + name: name || fileName,
  137 + url: fileStaticUri,
  138 + } as FileItem;
  139 + } catch (error) {
  140 + return {};
  141 + }
  142 + },
  143 + // showUploadList: true,
  144 + onDownload() {},
  145 + onPreview: (fileList: FileItem) => {
  146 + createImgPreview({ imageList: [fileList.url!] });
  147 + },
  148 + onDelete(url: string) {
  149 + formModel.deleteUrl = url!;
  150 + },
  151 + };
  152 + },
  153 + },
  154 + {
  155 + field: 'deleteUrl',
  156 + label: '',
  157 + component: 'Input',
  158 + show: false,
87 }, 159 },
88 ], 160 ],
89 showActionButtonGroup: false, 161 showActionButtonGroup: false,
@@ -40,7 +40,8 @@ @@ -40,7 +40,8 @@
40 40
41 const { componentInfo, attribute, attributeRename } = option; 41 const { componentInfo, attribute, attributeRename } = option;
42 42
43 - const { icon, iconColor, fontColor, unit, valueSize, fontSize } = componentInfo || {}; 43 + const { icon, iconColor, fontColor, unit, valueSize, fontSize, customIcon, defaultCustom } =
  44 + componentInfo || {};
44 return { 45 return {
45 iconColor: iconColor || persetIconColor, 46 iconColor: iconColor || persetIconColor,
46 unit: unit ?? perseUnit, 47 unit: unit ?? perseUnit,
@@ -49,6 +50,8 @@ @@ -49,6 +50,8 @@
49 attribute: attributeRename || unref(getThingModelTsl)?.functionName || attribute, 50 attribute: attributeRename || unref(getThingModelTsl)?.functionName || attribute,
50 valueSize: valueSize || persetValueSize || 20, 51 valueSize: valueSize || persetValueSize || 20,
51 fontSize: fontSize || persetFontSize || 14, 52 fontSize: fontSize || persetFontSize || 14,
  53 + defaultCustom: defaultCustom || 'default',
  54 + customIcon: customIcon || [],
52 }; 55 };
53 }); 56 });
54 57
@@ -73,11 +76,18 @@ @@ -73,11 +76,18 @@
73 <DeviceName :config="config" /> 76 <DeviceName :config="config" />
74 <div :style="getScale" class="flex-1 flex justify-center items-center flex-col w-full"> 77 <div :style="getScale" class="flex-1 flex justify-center items-center flex-col w-full">
75 <SvgIcon 78 <SvgIcon
  79 + v-if="getDesign.defaultCustom !== 'custom'"
76 :name="getDesign.icon!" 80 :name="getDesign.icon!"
77 prefix="iconfont" 81 prefix="iconfont"
78 :size="getRatio ? getRatio * 70 : 70" 82 :size="getRatio ? getRatio * 70 : 70"
79 :style="{ color: getDesign.iconColor }" 83 :style="{ color: getDesign.iconColor }"
80 /> 84 />
  85 + <img
  86 + v-else
  87 + :src="getDesign.customIcon[0]?.url"
  88 + :style="{ width: getRatio ? getRatio * 70 + 'px' : '70px' }"
  89 + :alt="getDesign.customIcon[0]?.name"
  90 + />
81 <h1 91 <h1
82 class="my-4 font-bold !my-2 truncate w-full text-center" 92 class="my-4 font-bold !my-2 truncate w-full text-center"
83 :style="{ 93 :style="{
@@ -4,6 +4,10 @@ @@ -4,6 +4,10 @@
4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; 4 import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5 import { option } from './config'; 5 import { option } from './config';
6 6
  7 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  8 + import { createImgPreview } from '/@/components/Preview';
  9 + import { upload } from '/@/api/oss/ossFileUploader';
  10 +
7 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ 11 const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8 schemas: [ 12 schemas: [
9 { 13 {
@@ -60,11 +64,31 @@ @@ -60,11 +64,31 @@
60 }, 64 },
61 }, 65 },
62 { 66 {
  67 + field: ComponentConfigFieldEnum.DEFAULT_CUSTOM,
  68 + label: '图标类型',
  69 + component: 'RadioGroup',
  70 + defaultValue: 'default',
  71 + componentProps: ({ formModel }) => {
  72 + return {
  73 + options: [
  74 + { label: '系统默认', value: 'default' },
  75 + { label: '自定义', value: 'custom' },
  76 + ],
  77 + onChange() {
  78 + formModel[ComponentConfigFieldEnum.CUSTOM_ICON] = [];
  79 + },
  80 + };
  81 + },
  82 + },
  83 + {
63 field: ComponentConfigFieldEnum.ICON_COLOR, 84 field: ComponentConfigFieldEnum.ICON_COLOR,
64 label: '图标颜色', 85 label: '图标颜色',
65 component: 'ColorPicker', 86 component: 'ColorPicker',
66 changeEvent: 'update:value', 87 changeEvent: 'update:value',
67 defaultValue: option.iconColor, 88 defaultValue: option.iconColor,
  89 + ifShow: ({ model }) => {
  90 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  91 + },
68 }, 92 },
69 { 93 {
70 field: ComponentConfigFieldEnum.ICON, 94 field: ComponentConfigFieldEnum.ICON,
@@ -73,6 +97,9 @@ @@ -73,6 +97,9 @@
73 changeEvent: 'update:value', 97 changeEvent: 'update:value',
74 valueField: 'value', 98 valueField: 'value',
75 defaultValue: option.icon, 99 defaultValue: option.icon,
  100 + ifShow: ({ model }) => {
  101 + return model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] !== 'custom';
  102 + },
76 componentProps({ formModel }) { 103 componentProps({ formModel }) {
77 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR]; 104 const color = formModel[ComponentConfigFieldEnum.ICON_COLOR];
78 return { 105 return {
@@ -80,6 +107,51 @@ @@ -80,6 +107,51 @@
80 }; 107 };
81 }, 108 },
82 }, 109 },
  110 + {
  111 + field: ComponentConfigFieldEnum.CUSTOM_ICON,
  112 + label: '图标',
  113 + component: 'ApiUpload',
  114 + ifShow: ({ model }) => model[ComponentConfigFieldEnum.DEFAULT_CUSTOM] === 'custom',
  115 + changeEvent: 'update:fileList',
  116 + valueField: 'fileList',
  117 + helpMessage: ['支持.svg格式,建议尺寸为32*32px,大小不超过50kb '],
  118 + componentProps: ({ formModel }) => {
  119 + return {
  120 + listType: 'picture-card',
  121 + maxSize: 50 * 1024,
  122 + maxFileLimit: 1,
  123 + accept: '.svg',
  124 + api: async (file: File) => {
  125 + try {
  126 + const formData = new FormData();
  127 + const { name } = file;
  128 + formData.set('file', file);
  129 + const { fileStaticUri, fileName } = await upload(formData);
  130 + return {
  131 + uid: fileStaticUri,
  132 + name: name || fileName,
  133 + url: fileStaticUri,
  134 + } as FileItem;
  135 + } catch (error) {
  136 + return {};
  137 + }
  138 + },
  139 + onDownload() {},
  140 + onPreview: (fileList: FileItem) => {
  141 + createImgPreview({ imageList: [fileList.url!] });
  142 + },
  143 + onDelete(url: string) {
  144 + formModel.deleteUrl = url!;
  145 + },
  146 + };
  147 + },
  148 + },
  149 + {
  150 + field: 'deleteUrl',
  151 + label: '',
  152 + component: 'Input',
  153 + show: false,
  154 + },
83 ], 155 ],
84 showActionButtonGroup: false, 156 showActionButtonGroup: false,
85 labelWidth: 120, 157 labelWidth: 120,
@@ -35,7 +35,8 @@ @@ -35,7 +35,8 @@
35 35
36 return { 36 return {
37 dataSource: dataSource.map((item) => { 37 dataSource: dataSource.map((item) => {
38 - const { fontColor, icon, iconColor, unit, valueSize, fontSize } = item.componentInfo; 38 + const { fontColor, icon, iconColor, unit, valueSize, fontSize, customIcon, defaultCustom } =
  39 + item.componentInfo;
39 const { attribute, attributeRename, deviceName, deviceRename, deviceId, deviceProfileId } = 40 const { attribute, attributeRename, deviceName, deviceRename, deviceId, deviceProfileId } =
40 item; 41 item;
41 const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute); 42 const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
@@ -50,6 +51,8 @@ @@ -50,6 +51,8 @@
50 id: deviceId, 51 id: deviceId,
51 valueSize: valueSize || persetValueSize || 20, 52 valueSize: valueSize || persetValueSize || 20,
52 fontSize: fontSize || persetFontSize || 14, 53 fontSize: fontSize || persetFontSize || 14,
  54 + defaultCustom: defaultCustom || 'default',
  55 + customIcon: customIcon || [],
53 }; 56 };
54 }), 57 }),
55 }; 58 };
@@ -67,6 +70,8 @@ @@ -67,6 +70,8 @@
67 fontColor: '#357CFB', 70 fontColor: '#357CFB',
68 fontSize: 16, 71 fontSize: 16,
69 valueSize: 16, 72 valueSize: 16,
  73 + defaultCustom: 'default',
  74 + customIcon: [],
70 }, 75 },
71 { 76 {
72 id: buildUUID(), 77 id: buildUUID(),
@@ -79,6 +84,8 @@ @@ -79,6 +84,8 @@
79 fontColor: '#FFA000', 84 fontColor: '#FFA000',
80 fontSize: 16, 85 fontSize: 16,
81 valueSize: 16, 86 valueSize: 16,
  87 + defaultCustom: 'default',
  88 + customIcon: [],
82 }, 89 },
83 ]); 90 ]);
84 91
@@ -114,11 +121,24 @@ @@ -114,11 +121,24 @@
114 class="flex justify-between items-center mt-2" 121 class="flex justify-between items-center mt-2"
115 > 122 >
116 <div class="flex items-center"> 123 <div class="flex items-center">
117 - <SvgIcon 124 + <!-- <SvgIcon
118 :name="item.icon!" 125 :name="item.icon!"
119 prefix="iconfont" 126 prefix="iconfont"
120 :size="getRatio ? 30 * getRatio : 30" 127 :size="getRatio ? 30 * getRatio : 30"
121 :style="{ color: item.iconColor }" 128 :style="{ color: item.iconColor }"
  129 + /> -->
  130 + <SvgIcon
  131 + v-if="item.defaultCustom !== 'custom'"
  132 + :name="item.icon!"
  133 + prefix="iconfont"
  134 + :size="getRatio ? getRatio * 30 : 30"
  135 + :style="{ color: item.iconColor }"
  136 + />
  137 + <img
  138 + v-else
  139 + :src="item.customIcon[0]?.url"
  140 + :style="{ width: getRatio ? getRatio * 30 + 'px' : '30px' }"
  141 + :alt="item.customIcon[0]?.name"
122 /> 142 />
123 <div 143 <div
124 class="text-gray-500 ml-6" 144 class="text-gray-500 ml-6"
@@ -36,4 +36,8 @@ export enum ComponentConfigFieldEnum { @@ -36,4 +36,8 @@ export enum ComponentConfigFieldEnum {
36 MIN_NUMBER = 'minNumber', 36 MIN_NUMBER = 'minNumber',
37 MAX_NUMBER = 'maxNumber', 37 MAX_NUMBER = 'maxNumber',
38 PASS_WORD = 'password', //操作密码 38 PASS_WORD = 'password', //操作密码
  39 + DEFAULT_CUSTOM = 'defaultCustom',
  40 + DEFAULT_CUSTOM_CLOSE = 'defaultCustomClose',
  41 + CUSTOM_ICON = 'customIcon',
  42 + CUSTOM_ICON_CLOSE = 'customIconClose',
39 } 43 }
@@ -20,6 +20,8 @@ @@ -20,6 +20,8 @@
20 import { useGetComponentConfig } from '../../../packages/hook/useGetComponetConfig'; 20 import { useGetComponentConfig } from '../../../packages/hook/useGetComponetConfig';
21 import { isBoolean } from '/@/utils/is'; 21 import { isBoolean } from '/@/utils/is';
22 import { useApp } from '../../hooks/useApp'; 22 import { useApp } from '../../hooks/useApp';
  23 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  24 + import { deleteFilePath } from '/@/api/oss/ossFileUploader';
23 25
24 const props = defineProps<{ 26 const props = defineProps<{
25 sourceInfo: WidgetDataType; 27 sourceInfo: WidgetDataType;
@@ -74,8 +76,56 @@ @@ -74,8 +76,56 @@
74 emit('update', toRaw(props.sourceInfo)); 76 emit('update', toRaw(props.sourceInfo));
75 } 77 }
76 78
  79 + const countElementOccurrences = (arr) => {
  80 + const countMap = {};
  81 +
  82 + arr.forEach((element) => {
  83 + if (countMap[element]) {
  84 + countMap[element]++;
  85 + } else {
  86 + countMap[element] = 1;
  87 + }
  88 + });
  89 +
  90 + return countMap;
  91 + };
  92 +
77 async function handleDelete() { 93 async function handleDelete() {
78 try { 94 try {
  95 + const { componentData: oldDataSource } = props.rawDataSource;
  96 + const customIconUrls = ref<any>([]);
  97 + oldDataSource?.forEach((item: any) => {
  98 + item.dataSource?.forEach((dataSource) => {
  99 + if (dataSource.componentInfo?.customIcon) {
  100 + dataSource.componentInfo?.customIcon.forEach((icon: FileItem) => {
  101 + customIconUrls.value.push(icon.url);
  102 + });
  103 + }
  104 + });
  105 + });
  106 +
  107 + const { dataSource: deleteDataSource } = props.sourceInfo;
  108 + const dataSourceDeleteUrl = deleteDataSource.map(
  109 + (item) => item.componentInfo.customIcon?.[0].url
  110 + );
  111 +
  112 + if (dataSourceDeleteUrl?.length) {
  113 + // 判断外部所有组件是否有dataSourceDeleteUrl使用中的url
  114 + const deletePromise = unref(customIconUrls)?.filter((item) =>
  115 + dataSourceDeleteUrl?.includes(item)
  116 + );
  117 +
  118 + const deleteUrlInfo = countElementOccurrences(deletePromise);
  119 + const deleteUrl = deletePromise?.filter((item) => deleteUrlInfo?.[item] == 1);
  120 + Promise.all(
  121 + deleteUrl.map((item) => {
  122 + deleteFilePath(item);
  123 + })
  124 + );
  125 + }
  126 + } catch (err) {}
  127 +
  128 + try {
79 await deleteDataComponent({ dataBoardId: unref(boardId), ids: [props.sourceInfo.id] }); 129 await deleteDataComponent({ dataBoardId: unref(boardId), ids: [props.sourceInfo.id] });
80 createMessage.success('删除成功'); 130 createMessage.success('删除成功');
81 emit('ok'); 131 emit('ok');