23
This commit is contained in:
202
draggable-panels/src/fauto/stores/designStore.ts
Normal file
202
draggable-panels/src/fauto/stores/designStore.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
|
||||
// 设计组件实例
|
||||
export interface DesignComponentInstance {
|
||||
id: string // 实例唯一ID
|
||||
componentId: string // 设计组件类型ID
|
||||
name: string // 显示名称
|
||||
props: Record<string, any> // 属性值
|
||||
}
|
||||
|
||||
// 设计组件定义
|
||||
export interface DesignComponentMeta {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
props: Record<string, any>
|
||||
}
|
||||
|
||||
// 生成唯一ID
|
||||
const generateId = () => Math.random().toString(36).substring(2, 9)
|
||||
|
||||
export const useDesignStore = defineStore('design', () => {
|
||||
// 设计中心已添加的组件实例列表
|
||||
const components = ref<DesignComponentInstance[]>([])
|
||||
|
||||
// 当前选中的组件实例ID
|
||||
const selectedId = ref<string | null>(null)
|
||||
|
||||
// 设计组件元数据缓存
|
||||
const componentMetas = ref<DesignComponentMeta[]>([])
|
||||
|
||||
// 自动扫描所有设计组件的 .json 配置文件
|
||||
const designComponentMetaModules = import.meta.glob('../designComponents/*/index.json', { eager: true })
|
||||
|
||||
// 是否已加载
|
||||
const isLoaded = ref(false)
|
||||
|
||||
// 当前选中的组件实例
|
||||
const selectedComponent = computed(() => {
|
||||
if (!selectedId.value) return null
|
||||
return components.value.find(c => c.id === selectedId.value) || null
|
||||
})
|
||||
|
||||
// 当前选中组件的元数据(属性定义)
|
||||
const selectedComponentMeta = computed(() => {
|
||||
if (!selectedComponent.value) return null
|
||||
return componentMetas.value.find(m => m.id === selectedComponent.value!.componentId) || null
|
||||
})
|
||||
|
||||
// 加载设计组件元数据(从本地文件自动扫描)
|
||||
const loadComponentMetas = async () => {
|
||||
try {
|
||||
const metas: DesignComponentMeta[] = []
|
||||
|
||||
for (const path in designComponentMetaModules) {
|
||||
// 从路径中提取组件 ID,例如 '../designComponents/TextInput/index.json' => 'TextInput'
|
||||
const match = path.match(/\/designComponents\/(.+)\/index\.json$/)
|
||||
if (!match) continue
|
||||
|
||||
const id = match[1]
|
||||
const mod = designComponentMetaModules[path] as any
|
||||
const config = mod.default || mod
|
||||
|
||||
metas.push({
|
||||
id,
|
||||
name: config.name,
|
||||
description: config.description,
|
||||
props: config.props || {}
|
||||
})
|
||||
}
|
||||
|
||||
componentMetas.value = metas
|
||||
} catch (error) {
|
||||
console.error('加载设计组件元数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载设计中心状态
|
||||
const loadState = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/design-state')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.components) {
|
||||
components.value = data.components
|
||||
}
|
||||
if (data.selectedId) {
|
||||
selectedId.value = data.selectedId
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('使用默认设计状态')
|
||||
}
|
||||
isLoaded.value = true
|
||||
}
|
||||
|
||||
// 保存设计中心状态
|
||||
const saveState = async () => {
|
||||
try {
|
||||
await fetch('/api/design-state', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
components: components.value,
|
||||
selectedId: selectedId.value,
|
||||
lastUpdated: new Date().toISOString()
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存设计状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听变化自动保存
|
||||
watch([components, selectedId], () => {
|
||||
if (isLoaded.value) {
|
||||
saveState()
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 添加设计组件到设计中心
|
||||
const addComponent = (componentId: string) => {
|
||||
const meta = componentMetas.value.find(m => m.id === componentId)
|
||||
if (!meta) {
|
||||
console.warn('未找到设计组件:', componentId)
|
||||
return
|
||||
}
|
||||
|
||||
// 计算同类型组件的数量
|
||||
const sameTypeCount = components.value.filter(c => c.componentId === componentId).length
|
||||
|
||||
const instance: DesignComponentInstance = {
|
||||
id: generateId(),
|
||||
componentId: componentId,
|
||||
name: `${meta.name} ${sameTypeCount + 1}`,
|
||||
props: JSON.parse(JSON.stringify(meta.props))
|
||||
}
|
||||
|
||||
components.value.push(instance)
|
||||
selectedId.value = instance.id
|
||||
}
|
||||
|
||||
// 移除设计组件
|
||||
const removeComponent = (instanceId: string) => {
|
||||
const index = components.value.findIndex(c => c.id === instanceId)
|
||||
if (index > -1) {
|
||||
components.value.splice(index, 1)
|
||||
if (selectedId.value === instanceId) {
|
||||
selectedId.value = components.value.length > 0 ? components.value[0].id : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选中设计组件
|
||||
const selectComponent = (instanceId: string | null) => {
|
||||
selectedId.value = instanceId
|
||||
}
|
||||
|
||||
// 更新组件属性
|
||||
const updateComponentProps = (instanceId: string, key: string, value: any) => {
|
||||
const component = components.value.find(c => c.id === instanceId)
|
||||
if (component) {
|
||||
component.props[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 重新排序组件(拖拽后)
|
||||
const reorderComponents = (newOrder: DesignComponentInstance[]) => {
|
||||
components.value = newOrder
|
||||
}
|
||||
|
||||
// 获取组件元数据
|
||||
const getComponentMeta = (componentId: string) => {
|
||||
return componentMetas.value.find(m => m.id === componentId)
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = async () => {
|
||||
await loadComponentMetas()
|
||||
await loadState()
|
||||
}
|
||||
|
||||
return {
|
||||
components,
|
||||
selectedId,
|
||||
selectedComponent,
|
||||
selectedComponentMeta,
|
||||
componentMetas,
|
||||
isLoaded,
|
||||
init,
|
||||
loadComponentMetas,
|
||||
loadState,
|
||||
saveState,
|
||||
addComponent,
|
||||
removeComponent,
|
||||
selectComponent,
|
||||
updateComponentProps,
|
||||
reorderComponents,
|
||||
getComponentMeta
|
||||
}
|
||||
})
|
||||
263
draggable-panels/src/fauto/stores/panelStore.ts
Normal file
263
draggable-panels/src/fauto/stores/panelStore.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import type { LayoutConfig, TabItem, Panel } from '../types'
|
||||
import { materialList, getMaterialInfo } from '../materials'
|
||||
|
||||
// 生成唯一ID
|
||||
const generateId = () => Math.random().toString(36).substring(2, 9)
|
||||
|
||||
// 默认配置
|
||||
const getDefaultLayout = (): LayoutConfig => ({
|
||||
leftPanel: {
|
||||
id: 'left',
|
||||
tabs: [],
|
||||
activeTabId: null
|
||||
},
|
||||
centerPanel: {
|
||||
id: 'center',
|
||||
tabs: [],
|
||||
activeTabId: null
|
||||
},
|
||||
rightPanel: {
|
||||
id: 'right',
|
||||
tabs: [],
|
||||
activeTabId: null
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化每个面板的activeTabId
|
||||
const initActiveTabIds = (layout: LayoutConfig): LayoutConfig => {
|
||||
const panels: (keyof LayoutConfig)[] = ['leftPanel', 'centerPanel', 'rightPanel']
|
||||
panels.forEach(key => {
|
||||
const panel = layout[key]
|
||||
if (panel.tabs.length > 0 && !panel.activeTabId) {
|
||||
panel.activeTabId = panel.tabs[0].id
|
||||
}
|
||||
})
|
||||
return layout
|
||||
}
|
||||
|
||||
export const usePanelStore = defineStore('panel', () => {
|
||||
// 布局配置
|
||||
const layout = ref<LayoutConfig>(initActiveTabIds(getDefaultLayout()))
|
||||
|
||||
// 物料组件状态独立存储 (materialId -> state)
|
||||
const materialStates = ref<Record<string, Record<string, any>>>({})
|
||||
|
||||
// 是否已加载配置
|
||||
const isLoaded = ref(false)
|
||||
|
||||
// 获取所有已打开的物料组件ID
|
||||
const openedMaterialIds = computed(() => {
|
||||
const allTabs = [
|
||||
...layout.value.leftPanel.tabs,
|
||||
...layout.value.centerPanel.tabs,
|
||||
...layout.value.rightPanel.tabs
|
||||
]
|
||||
return allTabs
|
||||
.filter(tab => tab.materialId)
|
||||
.map(tab => tab.materialId as string)
|
||||
})
|
||||
|
||||
// 获取可用的物料组件列表(未打开的)
|
||||
const availableMaterials = computed(() => {
|
||||
return materialList.filter(m => !openedMaterialIds.value.includes(m.id))
|
||||
})
|
||||
|
||||
// 检查物料组件是否已打开
|
||||
const isMaterialOpened = (materialId: string) => {
|
||||
return openedMaterialIds.value.includes(materialId)
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
// 加载布局配置
|
||||
const response = await fetch('/api/config')
|
||||
if (response.ok) {
|
||||
const config = await response.json()
|
||||
if (config && config.layout) {
|
||||
layout.value = initActiveTabIds(config.layout)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载物料组件状态
|
||||
const statesResponse = await fetch('/api/material-states')
|
||||
if (statesResponse.ok) {
|
||||
const states = await statesResponse.json()
|
||||
if (states && typeof states === 'object') {
|
||||
materialStates.value = states
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('使用默认配置')
|
||||
}
|
||||
isLoaded.value = true
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
try {
|
||||
await fetch('/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
layout: layout.value,
|
||||
lastUpdated: new Date().toISOString()
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存物料组件状态
|
||||
const saveMaterialStates = async () => {
|
||||
try {
|
||||
await fetch('/api/material-states', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(materialStates.value)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('保存物料状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取物料组件状态
|
||||
const getMaterialState = (materialId: string): Record<string, any> | undefined => {
|
||||
return materialStates.value[materialId]
|
||||
}
|
||||
|
||||
// 更新物料组件状态
|
||||
const updateMaterialState = (materialId: string, state: Record<string, any>) => {
|
||||
materialStates.value[materialId] = { ...materialStates.value[materialId], ...state }
|
||||
saveMaterialStates()
|
||||
}
|
||||
|
||||
// 监听变化并自动保存
|
||||
watch(layout, () => {
|
||||
if (isLoaded.value) {
|
||||
saveConfig()
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 获取面板
|
||||
const getPanel = (panelId: string): Panel | undefined => {
|
||||
const panels = [layout.value.leftPanel, layout.value.centerPanel, layout.value.rightPanel]
|
||||
return panels.find(p => p.id === panelId)
|
||||
}
|
||||
|
||||
// 添加新Tab
|
||||
const addTab = (panelId: string, tab?: Partial<TabItem>) => {
|
||||
const panel = getPanel(panelId)
|
||||
if (panel) {
|
||||
// 检查物料组件是否已打开
|
||||
if (tab?.materialId && isMaterialOpened(tab.materialId)) {
|
||||
console.warn('该物料组件已打开')
|
||||
return
|
||||
}
|
||||
const newTab: TabItem = {
|
||||
id: generateId(),
|
||||
title: tab?.title || `新窗口 ${panel.tabs.length + 1}`,
|
||||
content: tab?.content || '新窗口内容',
|
||||
materialId: tab?.materialId
|
||||
}
|
||||
panel.tabs.push(newTab)
|
||||
panel.activeTabId = newTab.id
|
||||
}
|
||||
}
|
||||
|
||||
// 打开物料组件
|
||||
const openMaterial = (materialId: string, panelId: string = 'center') => {
|
||||
if (isMaterialOpened(materialId)) {
|
||||
console.warn('该物料组件已打开')
|
||||
return
|
||||
}
|
||||
const materialInfo = getMaterialInfo(materialId)
|
||||
if (materialInfo) {
|
||||
addTab(panelId, {
|
||||
title: materialInfo.name,
|
||||
materialId: materialId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭Tab
|
||||
const closeTab = (panelId: string, tabId: string) => {
|
||||
const panel = getPanel(panelId)
|
||||
if (panel) {
|
||||
const index = panel.tabs.findIndex(t => t.id === tabId)
|
||||
if (index > -1) {
|
||||
// 物料组件状态已经在 materialStates 中独立存储,不需要额外处理
|
||||
panel.tabs.splice(index, 1)
|
||||
// 更新激活的Tab
|
||||
if (panel.activeTabId === tabId) {
|
||||
panel.activeTabId = panel.tabs.length > 0
|
||||
? panel.tabs[Math.max(0, index - 1)].id
|
||||
: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置激活的Tab
|
||||
const setActiveTab = (panelId: string, tabId: string) => {
|
||||
const panel = getPanel(panelId)
|
||||
if (panel) {
|
||||
panel.activeTabId = tabId
|
||||
}
|
||||
}
|
||||
|
||||
// 移动Tab到另一个面板
|
||||
const moveTab = (fromPanelId: string, toPanelId: string, tabId: string, toIndex?: number) => {
|
||||
const fromPanel = getPanel(fromPanelId)
|
||||
const toPanel = getPanel(toPanelId)
|
||||
|
||||
if (fromPanel && toPanel) {
|
||||
const tabIndex = fromPanel.tabs.findIndex(t => t.id === tabId)
|
||||
if (tabIndex > -1) {
|
||||
const [tab] = fromPanel.tabs.splice(tabIndex, 1)
|
||||
|
||||
if (toIndex !== undefined) {
|
||||
toPanel.tabs.splice(toIndex, 0, tab)
|
||||
} else {
|
||||
toPanel.tabs.push(tab)
|
||||
}
|
||||
|
||||
// 更新激活状态
|
||||
toPanel.activeTabId = tab.id
|
||||
|
||||
if (fromPanel.activeTabId === tabId && fromPanel.tabs.length > 0) {
|
||||
fromPanel.activeTabId = fromPanel.tabs[0].id
|
||||
} else if (fromPanel.tabs.length === 0) {
|
||||
fromPanel.activeTabId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在默认面板添加新窗口
|
||||
const addNewWindow = () => {
|
||||
addTab('center')
|
||||
}
|
||||
|
||||
return {
|
||||
layout,
|
||||
materialStates,
|
||||
isLoaded,
|
||||
openedMaterialIds,
|
||||
availableMaterials,
|
||||
isMaterialOpened,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
getMaterialState,
|
||||
updateMaterialState,
|
||||
addTab,
|
||||
openMaterial,
|
||||
closeTab,
|
||||
setActiveTab,
|
||||
moveTab,
|
||||
addNewWindow
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user