feat: 接入编辑器和导入导出
This commit is contained in:
@@ -37,3 +37,5 @@ __screenshots__/
|
||||
|
||||
# Vite
|
||||
*.timestamp-*-*.mjs
|
||||
|
||||
AGENTS.md
|
||||
|
||||
@@ -1,48 +1,93 @@
|
||||
# my-v0
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
Vue 3 + Vite 大屏低代码编辑器前端
|
||||
|
||||
## Recommended IDE Setup
|
||||
## 开发
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
Node `^22.18.0 || >=24.12.0`,包管理器用 pnpm。
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
pnpm build # vue-tsc --build + vite build
|
||||
pnpm type-check
|
||||
pnpm preview
|
||||
pnpm lint # oxlint + eslint(都会 --fix)
|
||||
pnpm format # prettier --write src/
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
## 项目目标
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
||||
### 一、编辑器核心架构
|
||||
|
||||
- ~~项目初始化与基础布局~~
|
||||
- ~~工具栏实现~~
|
||||
- ~~物料面板(可扩展架构)~~
|
||||
- ~~图层面板~~
|
||||
- ~~插件化注册机制~~
|
||||
- ~~物料 DSL 设计~~
|
||||
- ~~拖拽渲染流程~~
|
||||
- ~~节点拖动与缩放~~
|
||||
- ~~框选与多选操作~~
|
||||
- ~~画布标尺接入~~
|
||||
- ~~图层排序与画布层级联动~~
|
||||
- ~~页面 DSL 设计与落地~~
|
||||
- ~~属性面板~~
|
||||
- ~~右键菜单~~
|
||||
- ~~组合式函数拆分~~
|
||||
- ~~撤销重做基础能力~~
|
||||
- ~~批处理与历史记录管理~~
|
||||
- ~~ECharts 图表物料接入~~
|
||||
|
||||
### 二、代码与数据能力
|
||||
|
||||
- ~~网页代码编辑器接入~~
|
||||
- ~~页面 Schema 导入导出与实时编辑~~
|
||||
- 静态数据源
|
||||
- API 数据源
|
||||
- 数据源管理界面
|
||||
- API 数据源调试
|
||||
- 加载、刷新与请求复用
|
||||
|
||||
### 三、运行时与预览能力
|
||||
|
||||
- 大屏自适应预览
|
||||
- 页面持久化与独立访问
|
||||
- 运行时上下文
|
||||
|
||||
### 四、事件系统
|
||||
|
||||
- 事件调度机制
|
||||
- 事件配置面板
|
||||
- 跨组件事件联动
|
||||
- 事件函数编辑体验优化
|
||||
- 代码沙箱
|
||||
- 物料可触发事件声明
|
||||
|
||||
### 五、AI Agent 核心能力
|
||||
|
||||
- 大模型接入与调试
|
||||
- 提示词工程
|
||||
- 上下文工程
|
||||
- 结构化输出
|
||||
- 工具调用
|
||||
- Agent Loop
|
||||
- 模型记忆
|
||||
- RAG 与文档处理
|
||||
- Agentic RAG
|
||||
- Skill 体系
|
||||
- MCP
|
||||
- 流程编排(LangGraph)
|
||||
- 状态管理与人工介入
|
||||
- 子图与节点重试
|
||||
- Responses API
|
||||
|
||||
### 六、全栈应用落地
|
||||
|
||||
- 项目整体搭建
|
||||
- 最小会话构建
|
||||
- AI 能力与设计器深度融合
|
||||
- 完整业务场景落地
|
||||
|
||||
Vendored
+3
@@ -11,15 +11,18 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
MonacoEditor: typeof import('./src/components/MonacoEditor/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"echarts": "^6.1.0",
|
||||
"element-plus": "^2.14.4",
|
||||
"monaco-editor": "0.55.1",
|
||||
"pinia": "^4.0.2",
|
||||
"postcss": "^8.5.26",
|
||||
"tailwindcss": "^4.3.3",
|
||||
|
||||
Generated
+31
@@ -17,6 +17,9 @@ importers:
|
||||
element-plus:
|
||||
specifier: ^2.14.4
|
||||
version: 2.14.4(vue@3.5.41(typescript@6.0.3))
|
||||
monaco-editor:
|
||||
specifier: 0.55.1
|
||||
version: 0.55.1
|
||||
pinia:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.3(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3))
|
||||
@@ -750,6 +753,9 @@ packages:
|
||||
'@types/sortablejs@1.15.9':
|
||||
resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
@@ -1079,6 +1085,9 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dompurify@3.2.7:
|
||||
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
|
||||
|
||||
echarts@6.1.0:
|
||||
resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==}
|
||||
|
||||
@@ -1545,6 +1554,11 @@ packages:
|
||||
magic-string@1.2.0:
|
||||
resolution: {integrity: sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==}
|
||||
|
||||
marked@14.0.0:
|
||||
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
@@ -1567,6 +1581,9 @@ packages:
|
||||
mlly@1.8.2:
|
||||
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
|
||||
|
||||
moveable@0.53.0:
|
||||
resolution: {integrity: sha512-71jS9zIoQzMhnNvduhg4tUEdm23+fO/40FN7muVMbZvVwbTku2MIxxLhnU4qFvxI4oVxn75l79SbtgjuA+s7Pw==}
|
||||
|
||||
@@ -2658,6 +2675,9 @@ snapshots:
|
||||
|
||||
'@types/sortablejs@1.15.9': {}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0))(typescript@6.0.3)':
|
||||
@@ -3063,6 +3083,10 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
dompurify@3.2.7:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
echarts@6.1.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
@@ -3470,6 +3494,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
||||
memoize-one@6.0.0: {}
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
@@ -3492,6 +3518,11 @@ snapshots:
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.6.4
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
dependencies:
|
||||
dompurify: 3.2.7
|
||||
marked: 14.0.0
|
||||
|
||||
moveable@0.53.0:
|
||||
dependencies:
|
||||
'@daybrush/utils': 1.13.0
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<!-- 代码编辑器 -->
|
||||
<script setup lang="ts">
|
||||
import { editor } from 'monaco-editor'
|
||||
import 'monaco-editor/min/vs/editor/editor.main.css'
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
||||
|
||||
defineOptions({
|
||||
name: 'MonacoEditor',
|
||||
})
|
||||
|
||||
const props = defineProps<{ lang: string }>()
|
||||
const modelValue = defineModel({ type: String, default: '' })
|
||||
|
||||
// 编辑器 DOM 元素的引用
|
||||
const editorEL = ref()
|
||||
|
||||
// 因为 monaco-editor 运行需要消耗大量的资源,所以需要在webWorker中运行
|
||||
window.MonacoEnvironment = {
|
||||
getWorker(_, label) {
|
||||
if (label === 'json') {
|
||||
return new JsonWorker()
|
||||
}
|
||||
if (label === 'typescript' || label === 'javascript') {
|
||||
return new TsWorker()
|
||||
}
|
||||
return new EditorWorker()
|
||||
},
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const instance = editor.create(editorEL.value, {
|
||||
value: modelValue.value,
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
tabSize: 2,
|
||||
language: props.lang || 'json',
|
||||
automaticLayout: true,
|
||||
})
|
||||
|
||||
// 监听内容变化
|
||||
instance.onDidChangeModelContent(() => {
|
||||
modelValue.value = instance.getValue()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
instance.dispose()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="editorEL" class="w-full h-full min-h-0 flex-1"></div>
|
||||
</template>
|
||||
@@ -83,7 +83,7 @@ const onCommand = (command: keyof typeof commendMap) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="canvasRootRef" class="h-full w-full min-h-0 min-w-0 overflow-hidden">
|
||||
<div ref="canvasRootRef" class="relative h-full w-full min-h-0 min-w-0 overflow-hidden isolate">
|
||||
<SketchRuler
|
||||
v-if="viewportSize.width && viewportSize.height"
|
||||
:palette="palette"
|
||||
|
||||
@@ -24,7 +24,7 @@ const layerWidth = computed(() => (panelVisible.value.layer ? '260px' : '0'))
|
||||
<div class="h-screen select-none">
|
||||
<header class="flex items-center justify-between h-14 border-b border-white/20 px-5">
|
||||
<div class="w-75"><ToolbarLeft /></div>
|
||||
<div class="">2</div>
|
||||
<div class="">工作区</div>
|
||||
<div class="w-75"><ToolbarRight /></div>
|
||||
</header>
|
||||
<main class="h-[calc(100%-56px)] flex">
|
||||
|
||||
@@ -24,14 +24,14 @@ const nodeList = computed(() => [...nodes.value].reverse())
|
||||
<div
|
||||
v-for="node in nodeList"
|
||||
:key="node.id"
|
||||
class="flex items-center justify-between p-1 border border-white/20"
|
||||
class="flex items-center justify-between p-1 border border-white/20 text-(--el-text-color-regular) cursor-pointer"
|
||||
:class="{
|
||||
'bg-white/20': selectedNodeIds.includes(node.id),
|
||||
'bg-white/20 text-white!': selectedNodeIds.includes(node.id),
|
||||
}"
|
||||
@click="editorStore.selectNode(node.id)"
|
||||
>
|
||||
<span>{{ node.name }}</span>
|
||||
<span><Icon /></span>
|
||||
<span><Icon icon="famicons:menu-outline" /></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ function onStart(event: DragEvent) {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="h-30 p-2.5 flex flex-col border border-white/20"
|
||||
class="h-30 p-2.5 flex flex-col border border-white/20 rounded cursor-pointer text-(--el-text-color-regular) hover:text-white hover:bg-white/10"
|
||||
draggable="true"
|
||||
@dragstart="onStart"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ const currentMaterials = computed(() => {
|
||||
<div
|
||||
v-for="value in groups"
|
||||
:key="value.key"
|
||||
class="cursor-pointer flex flex-col items-center justify-center text-sm h-12"
|
||||
class="cursor-pointer flex flex-col items-center justify-center text-sm text-(--el-text-color-regular) h-12"
|
||||
:class="{ 'bg-white/10 text-white': activeGroup === value.key }"
|
||||
@click="activeGroup = value.key"
|
||||
>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { getValue } from '@/utils'
|
||||
import { ElInput, ElInputNumber, ElColorPicker, ElCheckbox, ElSelect } from 'element-plus'
|
||||
import { useUndoRedo } from '@/composables/useUndoRedo'
|
||||
// 动态 :is 不会走 unplugin 按需样式,需要手动引入
|
||||
import 'element-plus/es/components/select/style/css'
|
||||
import 'element-plus/es/components/input/style/css'
|
||||
import 'element-plus/es/components/checkbox/style/css'
|
||||
|
||||
defineProps(['setters', 'formData'])
|
||||
|
||||
@@ -32,5 +36,3 @@ const componentMap: Record<string, any> = {
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getMaterialSetters } from '@/materials'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import FormCreate from './FormCreate.vue'
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'NodeProperty',
|
||||
@@ -41,10 +42,36 @@ const layoutSetters = [
|
||||
type: 'number',
|
||||
},
|
||||
]
|
||||
|
||||
const visible = ref(false)
|
||||
const jsonContent = ref('')
|
||||
// 显示 JSON 编辑器
|
||||
const showJson = () => {
|
||||
jsonContent.value = JSON.stringify(selectedNode.value, null, 2)
|
||||
console.log('jsonContent.value', jsonContent.value)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 确认编辑 JSON
|
||||
const onConfirm = () => {
|
||||
// 拿到新的 JSON 内容,更新到节点中
|
||||
const newNode = JSON.parse(jsonContent.value)
|
||||
editorStore.updateNode(selectedNode.value?.id || '', {
|
||||
...newNode,
|
||||
// 这两个属性不允许被修改
|
||||
id: selectedNode.value?.id || '',
|
||||
type: selectedNode.value?.type || '',
|
||||
})
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-2">
|
||||
<div class="flex items-center justify-between font-bold p-1">
|
||||
<span>{{ selectedNode?.name }}</span>
|
||||
<span class="cursor-pointer" @click="showJson"><Icon icon="si:json-duotone" /></span>
|
||||
</div>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item title="布局属性" name="layout">
|
||||
<FormCreate :setters="layoutSetters" :form-data="selectedNode" />
|
||||
@@ -53,5 +80,22 @@ const layoutSetters = [
|
||||
<FormCreate :setters="setters" :form-data="selectedNode" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<el-drawer v-model="visible" title="编辑 JSON" class="json-drawer" destroy-on-close>
|
||||
<MonacoEditor v-model="jsonContent" lang="json" />
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onConfirm">确认</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.json-drawer .el-drawer__body) {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,99 @@
|
||||
<!-- 右侧工具栏 -->
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const editorStore = useEditorStore()
|
||||
const { page } = storeToRefs(editorStore)
|
||||
|
||||
const visible = ref(false)
|
||||
const jsonContent = ref('')
|
||||
|
||||
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
|
||||
|
||||
// 显示 JSON 编辑器
|
||||
const showJson = () => {
|
||||
visible.value = true
|
||||
jsonContent.value = JSON.stringify(page.value, null, 2)
|
||||
}
|
||||
|
||||
// 确认编辑 JSON
|
||||
const onConfirm = () => {
|
||||
const newPage = JSON.parse(jsonContent.value)
|
||||
editorStore.setPage(newPage)
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 导出文件
|
||||
const onExport = () => {
|
||||
const json = JSON.stringify(page.value, null, 2)
|
||||
const blob = new Blob([json], { type: 'application/json;charset=utf-8' })
|
||||
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'design.json'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
// 导入文件
|
||||
const onImport = () => {
|
||||
inputRef.value?.click()
|
||||
}
|
||||
|
||||
// 文件选择变化处理
|
||||
const onFileChange = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
const file = target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
// 读取文件完成后的回调
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result as string
|
||||
try {
|
||||
const newPage = JSON.parse(content)
|
||||
editorStore.setPage(newPage)
|
||||
ElMessage.success('导入成功')
|
||||
} catch (error) {
|
||||
console.error('导入的文件不是有效的 JSON 文件', error)
|
||||
ElMessage.error('导入的文件不是有效的 JSON 文件')
|
||||
}
|
||||
}
|
||||
// 读取文件内容
|
||||
reader.readAsText(file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-end gap-5">
|
||||
<span class="border p-1 rounded-lg"><Icon icon="icon-park-outline:preview-open"></Icon></span>
|
||||
<span class="border p-1 rounded-lg"><Icon icon="si:json-line"></Icon></span>
|
||||
<span class="border p-1 rounded-lg" @click="showJson"><Icon icon="si:json-line"></Icon></span>
|
||||
<span class="border p-1 rounded-lg"><Icon icon="hugeicons:internet"></Icon></span>
|
||||
<span class="border p-1 rounded-lg"><Icon icon="lets-icons:import"></Icon></span>
|
||||
<span class="border p-1 rounded-lg"><Icon icon="lets-icons:export"></Icon></span>
|
||||
<span class="border p-1 rounded-lg" @click="onImport" title="导入">
|
||||
<Icon icon="lets-icons:import"> </Icon>
|
||||
</span>
|
||||
<span class="border p-1 rounded-lg" @click="onExport" title="导出">
|
||||
<Icon icon="lets-icons:export"> </Icon>
|
||||
</span>
|
||||
|
||||
<input ref="inputRef" type="file" class="hidden" @change="onFileChange" />
|
||||
|
||||
<el-drawer v-model="visible" title="编辑 JSON" class="json-drawer" destroy-on-close>
|
||||
<MonacoEditor v-model="jsonContent" lang="json" />
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="onConfirm">确认</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -163,10 +163,10 @@ export const areaMaterial: MaterialDefinition = {
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
color: '#22d3ee',
|
||||
color: '#ffffff',
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#22d3ee',
|
||||
color: '#ffffff',
|
||||
},
|
||||
areaStyle: {
|
||||
color: 'rgba(34, 211, 238, 0.25)',
|
||||
|
||||
@@ -152,7 +152,7 @@ export const barMaterial: MaterialDefinition = {
|
||||
y: 'sales',
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#22d3ee',
|
||||
color: '#ffffff',
|
||||
borderRadius: [4, 4, 0, 0],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -59,7 +59,7 @@ export const pieMaterial: MaterialDefinition = {
|
||||
},
|
||||
props: {
|
||||
option: {
|
||||
color: ['#22d3ee', '#a78bfa', '#f59e0b', '#34d399', '#fb7185'],
|
||||
color: ['#ffffff', '#a78bfa', '#f59e0b', '#34d399', '#fb7185'],
|
||||
legend: {
|
||||
top: 38,
|
||||
left: 'center',
|
||||
|
||||
@@ -27,10 +27,10 @@ const textMaterial: MaterialDefinition = {
|
||||
height: 50,
|
||||
},
|
||||
style: {
|
||||
color: 'red',
|
||||
color: '#fff',
|
||||
},
|
||||
props: {
|
||||
content: '文本内容123123',
|
||||
content: '文本内容',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
nodes: [],
|
||||
})
|
||||
|
||||
// 设置页面数据
|
||||
const setPage = (newPage: PageSchema) => {
|
||||
Object.assign(page.value, newPage)
|
||||
}
|
||||
|
||||
const canvas = toRef(page.value, 'canvas')
|
||||
|
||||
// 画布上的节点数据
|
||||
@@ -108,6 +113,13 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
applyChange(node, 'locked', !node.locked)
|
||||
}
|
||||
|
||||
//
|
||||
function updateNode(nodeId: string, newNode: MaterialSchema) {
|
||||
// 返回新的节点数组,替换掉原来的节点数组
|
||||
const newNodes = nodes.value.map((node) => (node.id === nodeId ? newNode : node))
|
||||
setNodes(newNodes)
|
||||
}
|
||||
|
||||
return {
|
||||
panelVisible,
|
||||
page,
|
||||
@@ -126,5 +138,7 @@ export const useEditorStore = defineStore('editor', () => {
|
||||
moveTop,
|
||||
moveBottom,
|
||||
toggleLock,
|
||||
updateNode,
|
||||
setPage,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user