|
@@ -14,20 +14,29 @@ import type { Method } from '@/types/method'
|
|
|
import type { BaseWidget } from '@/types/baseWidget'
|
|
import type { BaseWidget } from '@/types/baseWidget'
|
|
|
import type { Screen } from '@/types/screen'
|
|
import type { Screen } from '@/types/screen'
|
|
|
import type { Page } from '@/types/page'
|
|
import type { Page } from '@/types/page'
|
|
|
|
|
+import type { IComponentModelConfig } from '@/lvgl-widgets/type'
|
|
|
|
|
+import type { WidgetNameIndexes } from '@/utils/widgetName'
|
|
|
|
|
|
|
|
import { computed, ref, watch } from 'vue'
|
|
import { computed, ref, watch } from 'vue'
|
|
|
import { defineStore } from 'pinia'
|
|
import { defineStore } from 'pinia'
|
|
|
import { klona } from 'klona'
|
|
import { klona } from 'klona'
|
|
|
-import { createBin, createScreen } from '@/model'
|
|
|
|
|
|
|
+import { createBin, createScreen, createWidget } from '@/model'
|
|
|
import { useRecentProject } from './recentProject'
|
|
import { useRecentProject } from './recentProject'
|
|
|
import { v4 } from 'uuid'
|
|
import { v4 } from 'uuid'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
import { ComponentArray } from '@/lvgl-widgets'
|
|
import { ComponentArray } from '@/lvgl-widgets'
|
|
|
import { bfsWalk } from 'simple-mind-map/src/utils'
|
|
import { bfsWalk } from 'simple-mind-map/src/utils'
|
|
|
import { useHistory } from '@/hooks/useHistory'
|
|
import { useHistory } from '@/hooks/useHistory'
|
|
|
import { DEFAULT_THEME_KEY } from '@/constants'
|
|
import { DEFAULT_THEME_KEY } from '@/constants'
|
|
|
|
|
+import {
|
|
|
|
|
+ collectDuplicateProjectNames,
|
|
|
|
|
+ collectProjectNamedItems,
|
|
|
|
|
+ createProjectWidgetName,
|
|
|
|
|
+ normalizeProjectWidgetNameIndexes,
|
|
|
|
|
+ walkWidgetTree
|
|
|
|
|
+} from '@/utils/widgetName'
|
|
|
|
|
|
|
|
export interface IProject {
|
|
export interface IProject {
|
|
|
version: string
|
|
version: string
|
|
@@ -46,6 +55,7 @@ export interface IProject {
|
|
|
languages: Language[]
|
|
languages: Language[]
|
|
|
methods: Method[]
|
|
methods: Method[]
|
|
|
screens: Screen[]
|
|
screens: Screen[]
|
|
|
|
|
+ widgetNameIndexes?: WidgetNameIndexes
|
|
|
currentLanguage?: string
|
|
currentLanguage?: string
|
|
|
currentTheme?: string
|
|
currentTheme?: string
|
|
|
}
|
|
}
|
|
@@ -274,6 +284,99 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
project.value.resources.fonts ||= []
|
|
project.value.resources.fonts ||= []
|
|
|
project.value.resources.others ||= []
|
|
project.value.resources.others ||= []
|
|
|
project.value.resources.bezierAnimations ||= []
|
|
project.value.resources.bezierAnimations ||= []
|
|
|
|
|
+ project.value.widgets ||= []
|
|
|
|
|
+ project.value.widgetNameIndexes ||= {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const createUniqueProjectName = (base: string, currentId?: string) => {
|
|
|
|
|
+ const normalizedBase = base.trim() || 'name'
|
|
|
|
|
+ const usedNames = new Set(
|
|
|
|
|
+ collectProjectNamedItems(project.value)
|
|
|
|
|
+ .filter((item) => item.id !== currentId)
|
|
|
|
|
+ .map((item) => item.name.trim())
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ let index = 1
|
|
|
|
|
+ let name = `${normalizedBase}_${index}`
|
|
|
|
|
+ while (usedNames.has(name)) {
|
|
|
|
|
+ index += 1
|
|
|
|
|
+ name = `${normalizedBase}_${index}`
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return name
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const escapeHtml = (value: string) => {
|
|
|
|
|
+ return value.replace(/[&<>"']/g, (char) => {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ '&': '&',
|
|
|
|
|
+ '<': '<',
|
|
|
|
|
+ '>': '>',
|
|
|
|
|
+ '"': '"',
|
|
|
|
|
+ "'": '''
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return map[char]
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const resetWidgetTreeIdentity = (widget: BaseWidget) => {
|
|
|
|
|
+ walkWidgetTree(widget, (child) => {
|
|
|
|
|
+ child.id = v4()
|
|
|
|
|
+ child.name = createProjectWidgetName(project.value, child)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ return widget
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const createProjectWidget = (
|
|
|
|
|
+ schema: IComponentModelConfig | BaseWidget,
|
|
|
|
|
+ index = 1,
|
|
|
|
|
+ isCustom?: boolean
|
|
|
|
|
+ ) => {
|
|
|
|
|
+ return resetWidgetTreeIdentity(createWidget(schema, index, isCustom))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const cloneWidgetTreeWithNewIdentity = (widget: BaseWidget) => {
|
|
|
|
|
+ return resetWidgetTreeIdentity(klona(widget))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const confirmDuplicateNamesBeforeSave = async () => {
|
|
|
|
|
+ const duplicateNames = collectDuplicateProjectNames(project.value)
|
|
|
|
|
+ if (!duplicateNames.length) return true
|
|
|
|
|
+
|
|
|
|
|
+ const details = duplicateNames
|
|
|
|
|
+ .slice(0, 20)
|
|
|
|
|
+ .map(({ name, items }) => {
|
|
|
|
|
+ const itemDetails = items
|
|
|
|
|
+ .map((item) => {
|
|
|
|
|
+ return `<div class="mt-4px">- ${escapeHtml(item.kind)} / ${escapeHtml(item.type)} / ${escapeHtml(item.path)} / ${escapeHtml(item.id)}</div>`
|
|
|
|
|
+ })
|
|
|
|
|
+ .join('')
|
|
|
|
|
+
|
|
|
|
|
+ return `<div class="mb-10px"><div><b>${escapeHtml(name)}</b></div>${itemDetails}</div>`
|
|
|
|
|
+ })
|
|
|
|
|
+ .join('')
|
|
|
|
|
+ const more =
|
|
|
|
|
+ duplicateNames.length > 20
|
|
|
|
|
+ ? `<div>还有 ${duplicateNames.length - 20} 组重复名称未展示。</div>`
|
|
|
|
|
+ : ''
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm(
|
|
|
|
|
+ `<div>项目中存在同名 name,请确认是否继续保存。点击确认将继续保存,点击取消可返回修改。</div><div class="mt-10px max-h-360px overflow-auto text-left">${details}${more}</div>`,
|
|
|
|
|
+ 'name 重复',
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ confirmButtonText: '继续保存',
|
|
|
|
|
+ cancelButtonText: '返回修改',
|
|
|
|
|
+ dangerouslyUseHTMLString: true
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+ return true
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const removeThemeStyles = (themeKey: string) => {
|
|
const removeThemeStyles = (themeKey: string) => {
|
|
@@ -357,7 +460,8 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
animations: [],
|
|
animations: [],
|
|
|
languages: [],
|
|
languages: [],
|
|
|
methods: [],
|
|
methods: [],
|
|
|
- screens: []
|
|
|
|
|
|
|
+ screens: [],
|
|
|
|
|
+ widgetNameIndexes: {}
|
|
|
}
|
|
}
|
|
|
// 2、构建屏幕信息
|
|
// 2、构建屏幕信息
|
|
|
activePageId.value = ''
|
|
activePageId.value = ''
|
|
@@ -367,6 +471,7 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
meta.screens.forEach((screen, index) => {
|
|
meta.screens.forEach((screen, index) => {
|
|
|
const newScreen = createScreen(screen)
|
|
const newScreen = createScreen(screen)
|
|
|
newScreen.name += `_${index + 1}`
|
|
newScreen.name += `_${index + 1}`
|
|
|
|
|
+ newScreen.pages[0].name = createUniqueProjectName('new_page')
|
|
|
project.value?.screens.push(newScreen)
|
|
project.value?.screens.push(newScreen)
|
|
|
openPageIds.value.push(newScreen.pages[0].id)
|
|
openPageIds.value.push(newScreen.pages[0].id)
|
|
|
})
|
|
})
|
|
@@ -448,6 +553,7 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
project.value = newProject
|
|
project.value = newProject
|
|
|
normalizeResources()
|
|
normalizeResources()
|
|
|
normalizeThemes()
|
|
normalizeThemes()
|
|
|
|
|
+ normalizeProjectWidgetNameIndexes(project.value)
|
|
|
globalStyle.value = style
|
|
globalStyle.value = style
|
|
|
currentMaxScreen.value = null
|
|
currentMaxScreen.value = null
|
|
|
const projectPath = project.value.meta.path + '\\' + project.value.meta.name
|
|
const projectPath = project.value.meta.path + '\\' + project.value.meta.name
|
|
@@ -502,6 +608,7 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
for (let i = oldScreenCount; i < newScreenCount; i += 1) {
|
|
for (let i = oldScreenCount; i < newScreenCount; i += 1) {
|
|
|
const newScreen = createScreen(meta.screens[i])
|
|
const newScreen = createScreen(meta.screens[i])
|
|
|
newScreen.name = `screen_${i + 1}`
|
|
newScreen.name = `screen_${i + 1}`
|
|
|
|
|
+ newScreen.pages[0].name = createUniqueProjectName('new_page')
|
|
|
project.value.screens.push(newScreen)
|
|
project.value.screens.push(newScreen)
|
|
|
openPageIds.value[i] = newScreen.pages[0].id
|
|
openPageIds.value[i] = newScreen.pages[0].id
|
|
|
}
|
|
}
|
|
@@ -522,7 +629,9 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
screen.name = `screen_${index + 1}`
|
|
screen.name = `screen_${index + 1}`
|
|
|
|
|
|
|
|
if (!screen.pages.length) {
|
|
if (!screen.pages.length) {
|
|
|
- screen.pages.push(createScreen(screenMeta).pages[0])
|
|
|
|
|
|
|
+ const newPage = createScreen(screenMeta).pages[0]
|
|
|
|
|
+ newPage.name = createUniqueProjectName('new_page')
|
|
|
|
|
+ screen.pages.push(newPage)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!openPageIds.value[index] || !screen.pages.some((page) => page.id === openPageIds.value[index])) {
|
|
if (!openPageIds.value[index] || !screen.pages.some((page) => page.id === openPageIds.value[index])) {
|
|
@@ -560,6 +669,9 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
ElMessage.error(t('projectNotExist'))
|
|
ElMessage.error(t('projectNotExist'))
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
+ normalizeProjectWidgetNameIndexes(project.value)
|
|
|
|
|
+ if (!(await confirmDuplicateNamesBeforeSave())) return
|
|
|
|
|
+
|
|
|
// 1、保存项目
|
|
// 1、保存项目
|
|
|
await window.electron.ipcRenderer.invoke(
|
|
await window.electron.ipcRenderer.invoke(
|
|
|
'write-file',
|
|
'write-file',
|
|
@@ -674,6 +786,10 @@ export const useProjectStore = defineStore('project', () => {
|
|
|
removeThemeStyles,
|
|
removeThemeStyles,
|
|
|
renameThemeStyles,
|
|
renameThemeStyles,
|
|
|
editProject,
|
|
editProject,
|
|
|
|
|
+ createProjectWidget,
|
|
|
|
|
+ cloneWidgetTreeWithNewIdentity,
|
|
|
|
|
+ resetWidgetTreeIdentity,
|
|
|
|
|
+ createUniqueProjectName,
|
|
|
|
|
|
|
|
// 历史记录
|
|
// 历史记录
|
|
|
history,
|
|
history,
|