Просмотр исходного кода

Merge branch 'feature/zutai-status' into feature/maotu

Zimo 5 дней назад
Родитель
Сommit
e1f5869a84

+ 66 - 0
src/components/custom-components/status-vue/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="status-display" :style="statusStyle" :title="displayText">
+    {{ displayText }}
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { IStatusColorRule } from '@/components/mt-edit/store/types'
+import { computed, type PropType } from 'vue'
+
+const props = defineProps({
+  value: {
+    type: [String, Number, Boolean] as PropType<string | number | boolean>,
+    default: ''
+  },
+  colorRules: {
+    type: Array as PropType<IStatusColorRule[]>,
+    default: () => []
+  },
+  defaultColor: {
+    type: String,
+    default: '#909399'
+  },
+  fontColor: {
+    type: String,
+    default: '#ffffff'
+  },
+  fontSize: {
+    type: Number,
+    default: 14
+  },
+  borderRadius: {
+    type: Number,
+    default: 36
+  }
+})
+
+const matchedRule = computed(() =>
+  props.colorRules.find((rule) => String(rule.value) === String(props.value))
+)
+
+const displayText = computed(() => matchedRule.value?.label || '')
+
+const statusStyle = computed(() => ({
+  backgroundColor: matchedRule.value?.color || props.defaultColor,
+  borderRadius: `${Math.max(props.borderRadius, 0)}px`,
+  color: props.fontColor,
+  fontSize: `${Math.max(props.fontSize, 0)}px`
+}))
+</script>
+
+<style scoped>
+.status-display {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  padding: 0 10px;
+  overflow: hidden;
+  line-height: 1;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  box-sizing: border-box;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 3 - 2
src/components/mt-edit/components/layout/main-panel/index.vue

@@ -247,6 +247,7 @@ const onDrop = (e: DragEvent | TouchEvent, isTouch?: boolean) => {
   const is_vertical_line = deep_find_cfg.id === 'sys-line'
   // 竖线
   const is_horizontal_line = deep_find_cfg.id === 'sys-line-vertical'
+  const is_status = deep_find_cfg.id === 'status-vue'
   //根据配置创建图形
   const { realityX, realityY } = getRealityXY(e, canvasAreaRef.value?.getBoundingClientRect())
   const create_item: IDoneJson = {
@@ -256,8 +257,8 @@ const onDrop = (e: DragEvent | TouchEvent, isTouch?: boolean) => {
     binfo: {
       left: alignToGrid(realityX / globalStore.canvasCfg.scale, grid_align_size.value),
       top: alignToGrid(realityY / globalStore.canvasCfg.scale, grid_align_size.value),
-      width: is_vertical_line ? 100 : is_horizontal_line ? 0 : 50,
-      height: is_horizontal_line ? 100 : is_vertical_line ? 0 : 50,
+      width: is_vertical_line ? 100 : is_horizontal_line ? 0 : is_status ? 36 : 50,
+      height: is_horizontal_line ? 100 : is_vertical_line ? 0 : is_status ? 36 : 50,
       angle: 0
     },
     resize: is_line ? false : true,

+ 4 - 0
src/components/mt-edit/components/layout/right-aside/select-item-props-setting.vue

@@ -45,6 +45,9 @@
       <div v-else-if="attr_item.type === 'jsonEdit' && !attr_item.disabled">
         <json-edit v-model:contentObj="attr_item.val" />
       </div>
+      <status-color-rules-editor
+        v-else-if="attr_item.type === 'statusColorRules' && !attr_item.disabled"
+        v-model="attr_item.val" />
     </el-form-item>
   </div>
 </template>
@@ -60,6 +63,7 @@ import {
   ElColorPicker
 } from 'element-plus'
 import JsonEdit from './json-edit.vue'
+import StatusColorRulesEditor from './status-color-rules-editor.vue'
 type SelectItemPropsSettingProps = {
   itemId?: string
   propsInfo: ILeftAsideConfigItemPublicProps | undefined

+ 85 - 0
src/components/mt-edit/components/layout/right-aside/status-color-rules-editor.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="status-color-rules-editor">
+    <div v-for="(rule, index) in modelValue" :key="index" class="status-color-rule">
+      <el-input
+        :model-value="rule.value"
+        placeholder="匹配值"
+        @update:model-value="updateRule(index, 'value', $event)" />
+      <el-input
+        :model-value="rule.label"
+        placeholder="显示文字(可选)"
+        @update:model-value="updateRule(index, 'label', $event)" />
+      <div class="status-color-rule__actions">
+        <el-color-picker
+          :model-value="rule.color"
+          @update:model-value="updateRule(index, 'color', $event || '#909399')" />
+        <el-button type="danger" text @click="removeRule(index)">删除</el-button>
+      </div>
+    </div>
+    <el-button class="w-1/1" plain type="primary" @click="addRule">新增状态</el-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { IStatusColorRule } from '@/components/mt-edit/store/types'
+import { ElButton, ElColorPicker, ElInput } from 'element-plus'
+
+const props = withDefaults(
+  defineProps<{
+    modelValue?: IStatusColorRule[]
+  }>(),
+  {
+    modelValue: () => []
+  }
+)
+
+const emit = defineEmits<{
+  'update:modelValue': [value: IStatusColorRule[]]
+}>()
+
+const updateRule = (index: number, key: keyof IStatusColorRule, value: string) => {
+  const nextRules = props.modelValue.map((rule, ruleIndex) =>
+    ruleIndex === index ? { ...rule, [key]: value } : rule
+  )
+  emit('update:modelValue', nextRules)
+}
+
+const addRule = () => {
+  emit('update:modelValue', [
+    ...props.modelValue,
+    {
+      value: '',
+      label: '',
+      color: '#409eff'
+    }
+  ])
+}
+
+const removeRule = (index: number) => {
+  emit(
+    'update:modelValue',
+    props.modelValue.filter((_, ruleIndex) => ruleIndex !== index)
+  )
+}
+</script>
+
+<style scoped>
+.status-color-rules-editor {
+  width: 100%;
+}
+
+.status-color-rule {
+  display: grid;
+  padding: 8px;
+  margin-bottom: 8px;
+  background: var(--el-fill-color-light);
+  border-radius: 4px;
+  gap: 6px;
+}
+
+.status-color-rule__actions {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+</style>

+ 4 - 0
src/components/mt-edit/components/render-core/index.vue

@@ -92,6 +92,7 @@ import NowTimeVue from '@/components/custom-components/now-time-vue/index.vue'
 import KvVue from '@/components/custom-components/kv-vue/index.vue'
 import SysButtonVue from '@/components/custom-components/sys-button-vue/index.vue'
 import WireframeVue from '@/components/custom-components/wireframe-vue/index.vue'
+import StatusVue from '@/components/custom-components/status-vue/index.vue'
 import { ElPopover } from 'element-plus'
 const instance = getCurrentInstance()
 const now_include_keys = Object.keys(instance?.appContext?.components as any)
@@ -113,6 +114,9 @@ if (!now_include_keys.includes('sys-button-vue')) {
 if (!now_include_keys.includes('wireframe-vue')) {
   instance?.appContext.app.component('wireframe-vue', WireframeVue)
 }
+if (!now_include_keys.includes('status-vue')) {
+  instance?.appContext.app.component('status-vue', StatusVue)
+}
 type RenderCoreProps = {
   doneJson: IDoneJson[]
   canvasCfg: IGlobalStoreCanvasCfg

+ 52 - 0
src/components/mt-edit/store/config.ts

@@ -326,6 +326,58 @@ const sysComponentItems: ILeftAsideConfigItem[] = [
       repeat: 'infinite'
     }
   },
+  {
+    id: 'status-vue',
+    title: '状态显示',
+    type: 'vue',
+    thumbnail: `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0Ij48cmVjdCB4PSI2IiB5PSIxOCIgd2lkdGg9IjUyIiBoZWlnaHQ9IjI4IiByeD0iMTQiIGZpbGw9IiM2N2MyM2EiLz48Y2lyY2xlIGN4PSIyMCIgY3k9IjMyIiByPSI2IiBmaWxsPSJ3aGl0ZSIvPjxwYXRoIGQ9Ik0zMSAyOGgxOE0zMSAzNmgxMyIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4=`,
+    props: {
+      value: {
+        title: '状态值',
+        type: 'input',
+        val: '1'
+      },
+      colorRules: {
+        title: '状态颜色',
+        type: 'statusColorRules',
+        bindable: false,
+        val: [
+          { value: '1', label: '正常', color: '#67c23a' },
+          { value: '0', label: '异常', color: '#f56c6c' }
+        ]
+      },
+      defaultColor: {
+        title: '默认颜色',
+        type: 'color',
+        bindable: false,
+        val: '#909399'
+      },
+      fontColor: {
+        title: '文字颜色',
+        type: 'color',
+        bindable: false,
+        val: '#ffffff'
+      },
+      fontSize: {
+        title: '文字大小',
+        type: 'number',
+        bindable: false,
+        val: 14
+      },
+      borderRadius: {
+        title: '圆角',
+        type: 'number',
+        bindable: false,
+        val: 36
+      }
+    },
+    common_animations: {
+      val: '',
+      delay: 'delay-0s',
+      speed: 'slow',
+      repeat: 'infinite'
+    }
+  },
   {
     id: 'wireframe-vue',
     title: '线框',

+ 7 - 0
src/components/mt-edit/store/types.ts

@@ -6,7 +6,13 @@ export type ILeftAsideConfigItemPublicPropsType =
   | 'switch'
   | 'number'
   | 'jsonEdit'
+  | 'statusColorRules'
   | 'textArea'
+export interface IStatusColorRule {
+  value: string
+  label?: string
+  color: string
+}
 // 开放注册配置
 export type ILeftAsideConfigItemPublicProps = Record<
   string,
@@ -18,6 +24,7 @@ export type ILeftAsideConfigItemPublicProps = Record<
     showAlpha?: boolean //颜色选择器是否支持透明度
     colorFormat?: string //颜色选择器的输出格式
     disabled?: boolean //如果禁用了将不会显示到右侧属性面板里,但是仍然可以通过代码修改属性
+    bindable?: boolean //是否允许在设备绑定面板中选择,默认允许
   }
 >
 export type ILeftAsideConfigItemPublicType = 'svg' | 'vue' | 'img' | 'custom-svg'

+ 1 - 1
src/views/maotu/components/DeviceBindPanel.vue

@@ -124,7 +124,7 @@ const rules: Record<string, FormItemRule | FormItemRule[]> = {
 
 const nodePropOptions = computed(() => {
   return Object.entries(props.item.props || {})
-    .filter(([, prop]) => !prop.disabled)
+    .filter(([, prop]) => !prop.disabled && prop.bindable !== false)
     .map(([key, prop]) => ({
       label: prop.title,
       value: `props.${key}.val`

+ 9 - 2
src/views/maotu/preview.vue

@@ -67,6 +67,7 @@ const getDeviceBindItems = () => {
       .filter((bind) => bind.deviceId && bind.deviceProp && bind.nodeProp)
       .map((bind) => ({
         itemId: item.id,
+        itemTag: item.tag,
         deviceId: bind.deviceId!,
         deviceProp: bind.deviceProp!,
         nodeProp: bind.nodeProp!
@@ -101,10 +102,13 @@ const refreshDeviceBindValues = async () => {
       })
     )
     const valueMap = new Map<string, unknown>()
+    const rawValueMap = new Map<string, unknown>()
     deviceDataList.forEach(({ deviceId, data }) => {
       data.forEach((item) => {
         if (item.identifier) {
-          valueMap.set(`${deviceId}:${item.identifier}`, formatDevicePointValue(item))
+          const key = `${deviceId}:${item.identifier}`
+          valueMap.set(key, formatDevicePointValue(item))
+          rawValueMap.set(key, item.value)
         }
       })
     })
@@ -115,7 +119,10 @@ const refreshDeviceBindValues = async () => {
         .map((item) => ({
           id: item.itemId,
           key: item.nodeProp,
-          val: valueMap.get(`${item.deviceId}:${item.deviceProp}`)
+          val:
+            item.itemTag === 'status-vue'
+              ? rawValueMap.get(`${item.deviceId}:${item.deviceProp}`)
+              : valueMap.get(`${item.deviceId}:${item.deviceProp}`)
         }))
     )
   } finally {