ソースを参照

fix: 修改问题

jiaxing.liao 1 ヶ月 前
コミット
d8bb40438a

+ 1 - 0
package.json

@@ -37,6 +37,7 @@
     "klona": "^2.0.6",
     "lodash-es": "^4.17.22",
     "monaco-editor": "^0.54.0",
+    "nanoid": "^5.1.11",
     "normalize.css": "^8.0.1",
     "pinia": "^3.0.3",
     "qrcode": "^1.5.4",

+ 10 - 0
pnpm-lock.yaml

@@ -56,6 +56,9 @@ importers:
       monaco-editor:
         specifier: ^0.54.0
         version: 0.54.0
+      nanoid:
+        specifier: ^5.1.11
+        version: 5.1.11
       normalize.css:
         specifier: ^8.0.1
         version: 8.0.1
@@ -2825,6 +2828,11 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  nanoid@5.1.11:
+    resolution: {integrity: sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==}
+    engines: {node: ^18 || >=20}
+    hasBin: true
+
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
@@ -6938,6 +6946,8 @@ snapshots:
 
   nanoid@3.3.11: {}
 
+  nanoid@5.1.11: {}
+
   natural-compare@1.4.0: {}
 
   needle@3.3.1:

+ 1 - 0
src/renderer/src/lvgl-widgets/type.d.ts

@@ -1,4 +1,5 @@
 import { CSSProperties } from 'vue'
+import type { FormItemRule } from 'element-plus'
 import type { VariableType } from '@/types/variables'
 
 type PartItem = {

+ 8 - 1
src/renderer/src/model/index.ts

@@ -9,6 +9,7 @@ import { v4 } from 'uuid'
 import { klona } from 'klona'
 import { BaseWidget } from '@/types/baseWidget'
 import { getUUID } from '@/utils'
+import { createWidgetName } from '@/utils/widgetName'
 import { DEFAULT_THEME_KEY } from '@/constants'
 import LvglWidgets from '@/lvgl-widgets'
 import { bfsWalk } from 'simple-mind-map/src/utils'
@@ -175,17 +176,23 @@ export const createWidget = (
   // 自定义组件
   if (isCustom) {
     const newComp = klona(schema as BaseWidget)
+    const typeCounts: Record<string, number> = {}
     bfsWalk(newComp, (child) => {
+      if (child?.type) {
+        typeCounts[child.type] = (typeCounts[child.type] || 0) + 1
+        child.name = createWidgetName(child as BaseWidget, typeCounts[child.type])
+      }
       child.id = v4()
     })
     // todo:处理关联性问题
     newComp.id = v4()
+    newComp.name = createWidgetName(newComp as BaseWidget, 1)
     return newComp as BaseWidget
   }
   const { defaultSchema, hasChildren, parts } = schema
   const componentSchema: BaseWidget = {
     id: v4(),
-    name: defaultSchema?.name + '_' + index,
+    name: createWidgetName(schema as IComponentModelConfig, index),
     type: schema.key,
     isCopy: false,
     copyFrom: '',

+ 5 - 5
src/renderer/src/store/modules/action.ts

@@ -2,9 +2,10 @@ import { ref } from 'vue'
 import { defineStore } from 'pinia'
 
 import { bfsWalk } from 'simple-mind-map/src/utils'
-import { moveToPosition, getAddWidgetIndex } from '@/utils'
+import { moveToPosition } from '@/utils'
 import { klona } from 'klona'
 import { v4 } from 'uuid'
+import { createWidgetNameGenerator } from '@/utils/widgetName'
 
 import { useKeyPress } from 'vue-hooks-plus'
 import { useProjectStore } from '@/store/modules/project'
@@ -407,22 +408,21 @@ export const useActionStore = defineStore('action', () => {
       ? target?.children || projectStore.activePage?.children || []
       : projectStore.activePage?.children || []
     const newArr: BaseWidget[] = []
+    const generateWidgetName = createWidgetNameGenerator(projectStore.activePage as Page)
 
     clipboard.value.forEach((obj) => {
       obj.props.x += 10
       obj.props.y += 10
-      const index = getAddWidgetIndex(projectStore.activePage as Page, obj.type)
       const newWidget = klona({
         ...obj,
         // 最后一个_加索引
-        name: `${obj.name.split('_')[0]}_${index}`,
+        name: generateWidgetName(obj),
         id: v4()
       })
       // 修改子节点ID
       bfsWalk(newWidget, (child) => {
         if (child?.id) {
-          const index = getAddWidgetIndex(projectStore.activePage as Page, child.type)
-          child.name = `${child.name.split('_')[0]}_${index}`
+          child.name = generateWidgetName(child)
           child.id = v4()
         }
       })

+ 70 - 0
src/renderer/src/utils/widgetName.ts

@@ -0,0 +1,70 @@
+import type { IComponentModelConfig } from '@/lvgl-widgets/type'
+import type { BaseWidget } from '@/types/baseWidget'
+import type { Page } from '@/types/page'
+
+import componentMap from '@/lvgl-widgets'
+import { customAlphabet } from 'nanoid'
+import { bfsWalk } from 'simple-mind-map/src/utils'
+
+type WidgetNameSource =
+  | Pick<BaseWidget, 'type' | 'name'>
+  | Pick<IComponentModelConfig, 'key' | 'defaultSchema'>
+
+const createNameSuffix = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 5)
+const widgetNameSuffixPattern = /_\d+(?:_[0-9A-Za-z]+)?$/
+
+const getWidgetType = (source: WidgetNameSource) => {
+  return 'key' in source ? source.key : source.type
+}
+
+const getRawWidgetName = (source: WidgetNameSource) => {
+  return 'defaultSchema' in source ? source.defaultSchema?.name : source.name
+}
+
+export const getWidgetNameBase = (source: WidgetNameSource) => {
+  const widgetType = getWidgetType(source)
+  const defaultName = widgetType ? componentMap[widgetType]?.defaultSchema?.name : ''
+  const rawName = String(defaultName || getRawWidgetName(source) || '')
+  const normalizedBaseName = rawName.replace(widgetNameSuffixPattern, '')
+
+  return normalizedBaseName || 'widget'
+}
+
+export const createWidgetName = (source: WidgetNameSource, index: number) => {
+  return `${getWidgetNameBase(source)}_${index}_${createNameSuffix()}`
+}
+
+const collectWidgetTypeCounts = (page?: Page) => {
+  const counts: Record<string, number> = {}
+  if (!page) return counts
+
+  bfsWalk(page, (widget) => {
+    if (!widget?.type) return
+    counts[widget.type] = (counts[widget.type] || 0) + 1
+  })
+
+  return counts
+}
+
+export const createWidgetNameGenerator = (page?: Page) => {
+  const widgetTypeCounts = collectWidgetTypeCounts(page)
+
+  return (source: WidgetNameSource) => {
+    const widgetType = getWidgetType(source) || 'widget'
+    const nextIndex = (widgetTypeCounts[widgetType] || 0) + 1
+    widgetTypeCounts[widgetType] = nextIndex
+
+    return createWidgetName(source, nextIndex)
+  }
+}
+
+export const isDuplicateWidgetName = (
+  widgets: Array<{ id: string; name: string }>,
+  name: string,
+  currentId?: string
+) => {
+  const normalizedName = name.trim()
+  if (!normalizedName) return false
+
+  return widgets.some((widget) => widget.id !== currentId && widget.name === normalizedName)
+}

+ 37 - 1
src/renderer/src/views/designer/config/property/CusFormItem.vue

@@ -1,4 +1,4 @@
-<template>
+<template>
   <el-col
     v-if="!schema?.hide && schema.valueType !== 'dependency'"
     :span="componentProps?.span ?? 24"
@@ -9,6 +9,7 @@
       :label="schema?.label"
       :label-width="schema.label ? (schema?.labelWidth ?? '50px') : '0px'"
       :label-position="schema?.labelPosition"
+      :rules="formItemRules"
     >
       <VariableBindWrapper
         v-model="value"
@@ -279,6 +280,7 @@
 <script setup lang="tsx">
 import type { ComponentSchema } from '@/lvgl-widgets/type'
 import type { CollapseModelValue } from 'element-plus'
+import type { FormItemRule } from 'element-plus'
 
 import { computed, defineComponent, nextTick, ref } from 'vue'
 import { get, set } from 'lodash-es'
@@ -312,6 +314,8 @@ import StyleOther from './components/StyleOther.vue'
 import LanguageSelectModal from './components/LanguageSelectModal.vue'
 import { LuLanguages } from 'vue-icons-plus/lu'
 import VariableBindWrapper from './components/VariableBindWrapper.vue'
+import { useAllWidgets } from '@/hooks/useAllWidgets'
+import { isDuplicateWidgetName } from '@/utils/widgetName'
 
 defineOptions({
   name: 'CusFormItem'
@@ -330,6 +334,7 @@ const props = defineProps<{
 }>()
 const key = v4()
 const projectStore = useProjectStore()
+const { allWidgets } = useAllWidgets()
 const textInputRef = ref()
 const languageModalRef = ref<InstanceType<typeof LanguageSelectModal>>()
 const textSelection = ref({
@@ -342,6 +347,36 @@ const componentProps = computed(() => {
 })
 const expandStyle = ref(true)
 
+const formItemRules = computed<FormItemRule[]>(() => {
+  const rules = [...(props.schema.rules || [])]
+  const isWidgetNameField =
+    props.schema.field === 'name' && !!props.formData?.id && props.formData?.type !== 'page'
+
+  if (!isWidgetNameField) {
+    return rules
+  }
+
+  rules.unshift({
+    validator: (_rule, fieldValue, callback) => {
+      const nextName = String(fieldValue || '').trim()
+      if (!nextName) {
+        callback(new Error('请输入名称'))
+        return
+      }
+
+      if (isDuplicateWidgetName(allWidgets.value, nextName, props.formData?.id)) {
+        callback(new Error('名称重复'))
+        return
+      }
+
+      callback()
+    },
+    trigger: ['blur', 'change']
+  })
+
+  return rules
+})
+
 // 绑定数据
 const value = computed({
   get() {
@@ -526,3 +561,4 @@ const dependencyFormItems = computed(() => {
   margin-bottom: 8px;
 }
 </style>
+