Merge remote-tracking branch 'upstream/refactor/develop' into resolve-router-page-merge-conflict
This commit is contained in:
commit
3bc9d334f2
|
|
@ -4,40 +4,40 @@ on:
|
|||
push:
|
||||
branches: []
|
||||
pull_request:
|
||||
branches: [develop,main, refactor/develop]
|
||||
branches: [develop, main, refactor/develop, release/*]
|
||||
|
||||
jobs:
|
||||
push-check:
|
||||
runs-on: ubuntu-latest # windows-latest || macos-latest
|
||||
|
||||
runs-on: ubuntu-latest # windows-latest || macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Get changed files
|
||||
id: get_changed_files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
**.js
|
||||
**.vue
|
||||
**.jsx
|
||||
- name: Run ESLint
|
||||
run: npx eslint ${{steps.get_changed_files.outputs.all_changed_files}}
|
||||
- name: Run Build
|
||||
run: pnpm run build:plugin && pnpm run build:alpha > build-alpha.log 2>&1
|
||||
- name: Get changed files
|
||||
id: get_changed_files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: |
|
||||
**.js
|
||||
**.vue
|
||||
**.jsx
|
||||
- name: Run ESLint
|
||||
run: npx eslint ${{steps.get_changed_files.outputs.all_changed_files}}
|
||||
- name: Run Build
|
||||
run: pnpm run build:plugin && pnpm run build:alpha > build-alpha.log 2>&1
|
||||
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-alpha-log
|
||||
path: build-alpha.log
|
||||
- name: Upload build logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-alpha-log
|
||||
path: build-alpha.log
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "designer-demo",
|
||||
"private": true,
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "cross-env VITE_THEME=light vite",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-mock",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
const path = require('path')
|
||||
const { rules } = require('../../.eslintrc')
|
||||
|
||||
module.exports = {
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
project: [path.join(__dirname, './tsconfig.json') ],
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
env: {
|
||||
browser: true,
|
||||
es2015: true,
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
...rules,
|
||||
// 允许 @ts-ignore
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
// 允许非空断言
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "off"
|
||||
},
|
||||
ignorePatterns: ['test/sample/*.vue', '.eslintrc.cjs']
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# @opentiny/tiny-engine 低代码引擎区编译器
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>block-compiler</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@opentiny/vue-theme@3.14/index.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"vue": "https://unpkg.com/vue@3.4.23/dist/vue.runtime.esm-browser.js",
|
||||
"vue/server-renderer": "https://unpkg.com/@vue/server-renderer@3.4.23/dist/server-renderer.esm-browser.js",
|
||||
"vue-i18n": "https://unpkg.com/vue-i18n@9.2.0-beta.36/dist/vue-i18n.esm-browser.js",
|
||||
"vue-router": "https://unpkg.com/vue-router@4.0.16/dist/vue-router.esm-browser.js",
|
||||
"@vue/devtools-api": "https://unpkg.com/@vue/devtools-api@6.5.1/lib/esm/index.js",
|
||||
"@vueuse/core": "https://unpkg.com/@vueuse/core@9.6.0/index.mjs",
|
||||
"@vueuse/shared": "https://unpkg.com/@vueuse/shared@9.6.0/index.mjs",
|
||||
"axios": "https://unpkg.com/axios@1.0.0/dist/esm/axios.js",
|
||||
"@opentiny/tiny-engine-i18n-host": "https://unpkg.com/@opentiny/tiny-engine-i18n-host@1/dist/lowcode-design-i18n-host.es.js",
|
||||
"@opentiny/tiny-engine-builtin-component": "https://unpkg.com/@opentiny/tiny-engine-builtin-component@^2.0.0/dist/index.js",
|
||||
"pinia": "https://unpkg.com/pinia@2.0.22/dist/pinia.esm-browser.js",
|
||||
"@opentiny/vue": "https://unpkg.com/@opentiny/vue@3.14/runtime/tiny-vue.mjs",
|
||||
"@opentiny/vue-icon": "https://unpkg.com/@opentiny/vue@3.14/runtime/tiny-vue-icon.mjs",
|
||||
"@opentiny/vue-common": "https://unpkg.com/@opentiny/vue@3.14/runtime/tiny-vue-common.mjs",
|
||||
"@opentiny/vue-locale": "https://unpkg.com/@opentiny/vue@3.14/runtime/tiny-vue-locale.mjs",
|
||||
"@opentiny/vue-renderless/": "https://unpkg.com/@opentiny/vue-renderless@3.14/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/dev.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-block-compiler",
|
||||
"version": "2.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "block runtime compiler",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentiny/tiny-engine",
|
||||
"directory": "packages/block-compiler"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/opentiny/tiny-engine/issues"
|
||||
},
|
||||
"author": "OpenTiny Team",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opentiny.design/tiny-engine",
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"typescript": "~5.4.2",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-dts": "^4.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@vue/babel-plugin-jsx": "^1.2.5",
|
||||
"@vue/compiler-sfc": "^3.4.15",
|
||||
"vue": "^3.4.15"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// @ts-ignore
|
||||
import { createApp, defineAsyncComponent, h } from 'https://unpkg.com/vue@3.4.23/dist/vue.runtime.esm-browser.js'
|
||||
import { compile } from './index'
|
||||
import BlockFileName from '../test/sample/BlockFileName.vue?raw'
|
||||
import BlockHead from '../test/sample/BlockHead.vue?raw'
|
||||
import BlockMenu from '../test/sample/BlockMenu.vue?raw'
|
||||
import BlockTest from '../test/sample/BlockTest.vue?raw'
|
||||
import BlockJsxTest from '../test/sample/slotModelValueTest.vue?raw'
|
||||
|
||||
const RenderMain = {
|
||||
setup() {
|
||||
const componentMap = compile(
|
||||
[
|
||||
{
|
||||
fileName: 'BlockHead',
|
||||
sourceCode: BlockHead
|
||||
},
|
||||
{
|
||||
fileName: 'BlockFileName',
|
||||
sourceCode: BlockFileName
|
||||
},
|
||||
{
|
||||
fileName: 'BlockMenu',
|
||||
sourceCode: BlockMenu
|
||||
},
|
||||
{
|
||||
fileName: 'BlockTest',
|
||||
sourceCode: BlockTest
|
||||
},
|
||||
{
|
||||
fileName: 'BlockJsxTest',
|
||||
sourceCode: BlockJsxTest
|
||||
}
|
||||
],
|
||||
{}
|
||||
)
|
||||
|
||||
const blockComponents: { [key: string]: unknown } = {}
|
||||
|
||||
// @ts-ignore
|
||||
window.getBlockComponentBlobUrl = (name) => {
|
||||
return componentMap?.[name]?.blobURL
|
||||
}
|
||||
|
||||
for (const [fileName, value] of Object.entries(componentMap)) {
|
||||
blockComponents[fileName] = defineAsyncComponent(() => import(/* @vite-ignore */ value.blobURL))
|
||||
}
|
||||
|
||||
const css = Object.values(componentMap)
|
||||
.map((item) => item.style)
|
||||
.join('')
|
||||
|
||||
const stylesheet = document.querySelector('#block-stylesheet')
|
||||
|
||||
if (stylesheet) {
|
||||
stylesheet.remove()
|
||||
} else {
|
||||
const newStyleSheet = document.createElement('style')
|
||||
|
||||
newStyleSheet.innerHTML = css
|
||||
|
||||
document.head.appendChild(newStyleSheet)
|
||||
}
|
||||
|
||||
return () =>
|
||||
h('div', {}, [
|
||||
h(blockComponents.BlockJsxTest),
|
||||
h(blockComponents.BlockTest),
|
||||
h(blockComponents.BlockHead),
|
||||
h(blockComponents.BlockFileName),
|
||||
h('span', {}, 'testtest')
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const App = createApp(RenderMain)
|
||||
|
||||
App.mount(document.querySelector('#app')!)
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
import { compileScript, compileTemplate, compileStyle, parse, babelParse, MagicString } from '@vue/compiler-sfc'
|
||||
import type { SFCParseResult, SFCDescriptor, BindingMetadata, CompilerOptions } from '@vue/compiler-sfc'
|
||||
import { testIsJsx, transformVueJsx } from './transformJsx.ts'
|
||||
|
||||
const compileBlockStyle = (descriptor: SFCDescriptor, id: string) => {
|
||||
const cssResArr = descriptor.styles.map((style) => {
|
||||
const result = compileStyle({
|
||||
id,
|
||||
filename: descriptor.filename,
|
||||
source: style.content,
|
||||
scoped: style.scoped
|
||||
})
|
||||
|
||||
return result.code || ''
|
||||
})
|
||||
|
||||
return cssResArr.join('\n')
|
||||
}
|
||||
|
||||
const compileBlockTemplate = (descriptor: SFCDescriptor, id: string, bindingMetadata: BindingMetadata | undefined) => {
|
||||
const isJsx = testIsJsx(descriptor)
|
||||
const expressionPlugins: CompilerOptions['expressionPlugins'] = []
|
||||
|
||||
if (isJsx) {
|
||||
expressionPlugins.push('jsx')
|
||||
}
|
||||
|
||||
const compileRes = compileTemplate({
|
||||
id,
|
||||
ast: descriptor.template?.ast,
|
||||
source: descriptor.template?.content!,
|
||||
filename: descriptor.filename,
|
||||
scoped: descriptor.styles.some((styleItem) => styleItem.scoped),
|
||||
slotted: descriptor.slotted,
|
||||
compilerOptions: {
|
||||
bindingMetadata,
|
||||
expressionPlugins
|
||||
}
|
||||
})
|
||||
|
||||
const { errors } = compileRes
|
||||
let { code } = compileRes
|
||||
|
||||
if (isJsx) {
|
||||
code = transformVueJsx(code) || ''
|
||||
}
|
||||
|
||||
return { code, errors }
|
||||
}
|
||||
|
||||
interface compiledItem {
|
||||
js: string
|
||||
style: string
|
||||
blobURL: string
|
||||
}
|
||||
|
||||
export interface IResultMap {
|
||||
[key: string]: compiledItem
|
||||
}
|
||||
|
||||
const resolveRelativeImport = (code: string, globalGetterName = 'loadBlockComponent') => {
|
||||
const magicStr = new MagicString(code)
|
||||
const ast = babelParse(code, { sourceType: 'module', plugins: ['jsx'] }).program.body
|
||||
|
||||
let vueImportNode = null
|
||||
let hasDefineAsyncComponent = false
|
||||
|
||||
for (const node of ast) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
const source = node.source.value
|
||||
if (source === 'vue' && !node.specifiers.find((spec) => spec.type === 'ImportNamespaceSpecifier')) {
|
||||
vueImportNode = node
|
||||
}
|
||||
// 标识相对路径引入的 .vue 文件为区块,使用异步组件替换
|
||||
if (source.startsWith('./') && node.source.value.endsWith('.vue')) {
|
||||
hasDefineAsyncComponent = true
|
||||
const fileName = node.source.value.replace(/^(\.\/+)/, '').slice(0, -4)
|
||||
// 默认导出名
|
||||
const defaultImportId = node.specifiers.find((spec) => spec.type === 'ImportDefaultSpecifier')?.local?.name
|
||||
|
||||
// 不存在默认导出,跳过
|
||||
if (!defaultImportId) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 声明异步组件 const Block = defineAsyncComponent(() => import(getBlockUrl(Block)))
|
||||
magicStr.appendLeft(
|
||||
node.start!,
|
||||
`const ${defaultImportId} = defineAsyncComponent(() => window.${globalGetterName}('${fileName}'))`
|
||||
)
|
||||
|
||||
// 移除 import Block from './Block.vue' 语句
|
||||
magicStr.remove(node.start!, node.end!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 拿到类型声明,拆分到另一个函数
|
||||
if (hasDefineAsyncComponent) {
|
||||
// 存在异步引入组件
|
||||
if (vueImportNode) {
|
||||
const asyncSpec = vueImportNode.specifiers.find(
|
||||
(spec) => spec.type === 'ImportSpecifier' && spec.local.name === 'defineAsyncComponent'
|
||||
)
|
||||
|
||||
if (!asyncSpec) {
|
||||
const firstRelativeSpec = vueImportNode.specifiers.find((spec) => spec.type === 'ImportSpecifier')
|
||||
|
||||
if (firstRelativeSpec) {
|
||||
magicStr.appendLeft(firstRelativeSpec.start!, 'defineAsyncComponent, ')
|
||||
} else {
|
||||
magicStr.appendRight(vueImportNode.specifiers[0].end!, ', { defineAsyncComponent }')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
magicStr.appendLeft(ast[0].start!, "import { defineAsyncComponent } from 'vue'\n")
|
||||
}
|
||||
}
|
||||
|
||||
return magicStr.toString()
|
||||
}
|
||||
|
||||
const DEFAULT_COMPONENT_NAME = '__sfc__'
|
||||
|
||||
const compileBlockScript = (descriptor: SFCDescriptor, id: string): [string, BindingMetadata | undefined] => {
|
||||
const isJsx = testIsJsx(descriptor)
|
||||
const expressionPlugins: CompilerOptions['expressionPlugins'] = []
|
||||
|
||||
if (isJsx) {
|
||||
expressionPlugins.push('jsx')
|
||||
}
|
||||
|
||||
// TODO: try catch
|
||||
const compiledScript = compileScript(descriptor, {
|
||||
genDefaultAs: DEFAULT_COMPONENT_NAME,
|
||||
inlineTemplate: true,
|
||||
id,
|
||||
templateOptions: {
|
||||
compilerOptions: {
|
||||
expressionPlugins
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let code = compiledScript.content
|
||||
|
||||
if (isJsx) {
|
||||
code = transformVueJsx(code) || ''
|
||||
}
|
||||
|
||||
return [code, compiledScript.bindings]
|
||||
}
|
||||
|
||||
interface IParsedFileItem {
|
||||
fileName: string
|
||||
sourceCode: string
|
||||
compilerParseResult: SFCParseResult
|
||||
importedFiles: string[]
|
||||
fileNameWithRelativePath: string
|
||||
}
|
||||
|
||||
// 依次构建 script、template、style,然后组装成 import
|
||||
const compileFile = (file: IParsedFileItem): Omit<compiledItem, 'blobURL'> => {
|
||||
const descriptor = file.compilerParseResult.descriptor
|
||||
|
||||
// 编译 script
|
||||
const [compiledScript, bindings] = compileBlockScript(descriptor, file.fileName)
|
||||
let componentCode = `${compiledScript}`
|
||||
|
||||
// 编译 template
|
||||
if (!descriptor.scriptSetup && descriptor.template) {
|
||||
const { code: compiledTemplate } = compileBlockTemplate(descriptor, file.fileName, bindings)
|
||||
|
||||
componentCode += `\n ${compiledTemplate} \n ${DEFAULT_COMPONENT_NAME}.render = render`
|
||||
}
|
||||
|
||||
const hasScoped = descriptor.styles.some((styleItem) => styleItem.scoped)
|
||||
|
||||
if (hasScoped) {
|
||||
componentCode += `\n${DEFAULT_COMPONENT_NAME}.__scopeId='data-v-${file.fileName}'`
|
||||
}
|
||||
|
||||
// 编译 style
|
||||
const styleString = compileBlockStyle(descriptor, file.fileName)
|
||||
|
||||
return {
|
||||
js: `${componentCode}\nexport default ${DEFAULT_COMPONENT_NAME}`,
|
||||
style: styleString
|
||||
}
|
||||
}
|
||||
|
||||
// 解析依赖的文件
|
||||
const parseImportedFiles = (descriptor: SFCDescriptor): string[] => {
|
||||
let scriptContent = ''
|
||||
|
||||
if (descriptor.script) {
|
||||
scriptContent = descriptor.script.content
|
||||
} else if (descriptor.scriptSetup) {
|
||||
scriptContent = descriptor.scriptSetup.content
|
||||
}
|
||||
|
||||
if (!scriptContent) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ast = babelParse(scriptContent, { sourceFilename: descriptor.filename, sourceType: 'module', plugins: ['jsx'] })
|
||||
.program.body
|
||||
const res: string[] = []
|
||||
|
||||
for (const node of ast) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
const source = node.source.value
|
||||
|
||||
// 相对路径依赖,区块嵌套的场景
|
||||
if (source.startsWith('./')) {
|
||||
res.push(node.source.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const getJSBlobURL = (str: string) => {
|
||||
const blob = new Blob([str], { type: 'application/javascript' })
|
||||
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
export interface IFileItem {
|
||||
fileName: string
|
||||
sourceCode: string
|
||||
}
|
||||
|
||||
export type IFileList = IFileItem[]
|
||||
|
||||
export interface IConfig {
|
||||
compileCache?: Map<string, compiledItem>
|
||||
globalGetterName?: string
|
||||
}
|
||||
|
||||
// TODO: 支持 importMap
|
||||
export const compile = (fileList: IFileList, config: IConfig) => {
|
||||
const parsedFileList = fileList.map((fileItem) => {
|
||||
const { fileName, sourceCode } = fileItem
|
||||
// FIXME:这里解析的结果不能重复使用,因为可能会涉及修改引入的依赖
|
||||
const { descriptor, errors } = parse(sourceCode, { filename: fileName })
|
||||
|
||||
if (errors) {
|
||||
// TODO: 抛出错误
|
||||
}
|
||||
|
||||
// TODO: 1. 当前仅支持 vue 文件编译,检查文件后缀,如果不是 .vue 结尾的,抛出错误
|
||||
// TODO: 2. 检查 style lang,仅支持 css
|
||||
// TODO: 3. 检查 template lang,当前不支持任何 template lang
|
||||
|
||||
// 解析依赖的文件
|
||||
const importedFiles = parseImportedFiles(descriptor)
|
||||
|
||||
return {
|
||||
fileName,
|
||||
sourceCode,
|
||||
compilerParseResult: {
|
||||
descriptor,
|
||||
errors
|
||||
},
|
||||
importedFiles,
|
||||
fileNameWithRelativePath: `./${fileName}.vue`
|
||||
}
|
||||
})
|
||||
|
||||
const compiledFilesSet: Set<string> = new Set()
|
||||
const resultMap: IResultMap = {}
|
||||
|
||||
const compileCache = config?.compileCache || new Map()
|
||||
|
||||
for (const fileItem of parsedFileList) {
|
||||
const fileName = fileItem.fileName
|
||||
const cache = compileCache.get(fileName)
|
||||
let js = ''
|
||||
let style = ''
|
||||
|
||||
// 优先使用缓存
|
||||
if (cache?.js && cache?.style) {
|
||||
js = cache.js
|
||||
style = cache.style
|
||||
} else {
|
||||
const compileRes = compileFile(fileItem)
|
||||
|
||||
js = compileRes.js
|
||||
style = compileRes.style
|
||||
}
|
||||
|
||||
const resolvedImportJs = resolveRelativeImport(js, config?.globalGetterName)
|
||||
|
||||
resultMap[fileName] = {
|
||||
js: resolvedImportJs,
|
||||
style,
|
||||
blobURL: getJSBlobURL(resolvedImportJs)
|
||||
}
|
||||
|
||||
compileCache.set(fileName, resultMap[fileName])
|
||||
|
||||
compiledFilesSet.add(fileItem.fileNameWithRelativePath)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { transformSync } from '@babel/core'
|
||||
import vueJsx from '@vue/babel-plugin-jsx'
|
||||
import type { SFCDescriptor } from 'vue/compiler-sfc'
|
||||
|
||||
export const testIsJsx = (descriptor: SFCDescriptor) => {
|
||||
const lang = descriptor.script?.lang || descriptor?.scriptSetup?.lang || ''
|
||||
|
||||
return /jsx$/.test(lang)
|
||||
}
|
||||
|
||||
export const transformVueJsx = (sourceCode: string) => {
|
||||
return transformSync(sourceCode, {
|
||||
babelrc: false,
|
||||
plugins: [vueJsx],
|
||||
sourceMaps: false,
|
||||
configFile: false
|
||||
})?.code
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-popover class="block-link-field" popper-class="option-popper block-new-attr-popover">
|
||||
<tiny-input placeholder="请输入字段名称"></tiny-input>
|
||||
</tiny-popover>
|
||||
<div>
|
||||
<tiny-button type="primary">TinyEngine 前端可视化设计器</tiny-button>
|
||||
<slot name="menu" :title="state.title">
|
||||
<span>TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as vue from 'vue'
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
import { Input as TinyInput, Popover as TinyPopover, Button as TinyButton } from '@opentiny/vue'
|
||||
|
||||
const props = defineProps({ testSlot: { type: Object, default: () => ({}) } })
|
||||
const emit = defineEmits([])
|
||||
|
||||
const state = vue.reactive({
|
||||
title: 'test slot params'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
.test {
|
||||
width: 100px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
color: #191919;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="head-title">这里是头部</h1>
|
||||
<block-menu></block-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import BlockMenu from './BlockMenu.vue'
|
||||
|
||||
onMounted(() => {
|
||||
console.log('blockhead mounted')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.head-title {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div>
|
||||
<ul class="block-menu">
|
||||
<li>菜单1</li>
|
||||
<li>菜单2</li>
|
||||
<li>菜单3</li>
|
||||
<li>菜单4</li>
|
||||
<li>菜单5</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
onMounted(() => {
|
||||
console.log('blockMenu mounted')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.block-menu {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div>block test aaaa</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
console.log('onMounted')
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<tiny-grid
|
||||
:editConfig="{ trigger: 'click', mode: 'cell', showStatus: true }"
|
||||
:columns="state.columns"
|
||||
:data="[
|
||||
{
|
||||
id: '1',
|
||||
name: 'GFD科技有限公司',
|
||||
city: '福州',
|
||||
employees: 800,
|
||||
created_date: '2014-04-30 00:56:00',
|
||||
boole: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'WWW科技有限公司',
|
||||
city: '深圳',
|
||||
employees: 300,
|
||||
created_date: '2016-07-08 12:36:22',
|
||||
boole: true
|
||||
}
|
||||
]"
|
||||
></tiny-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="jsx">
|
||||
import { Grid as TinyGrid, Numeric as TinyNumeric, Input as TinyInput } from '@opentiny/vue'
|
||||
import * as vue from 'vue'
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
const props = defineProps({})
|
||||
|
||||
const emit = defineEmits([])
|
||||
|
||||
const state = vue.reactive({
|
||||
columns: [
|
||||
{ type: 'index', width: 60 },
|
||||
{ type: 'selection', width: 60 },
|
||||
{
|
||||
field: 'employees',
|
||||
title: '员工数',
|
||||
slots: {
|
||||
default: ({ row, column, rowIndex }, h) => (
|
||||
<div>
|
||||
<TinyNumeric
|
||||
allow-empty={true}
|
||||
placeholder="请输入"
|
||||
controlsPosition="right"
|
||||
step={1}
|
||||
modelValue={row.employees}
|
||||
onChange={(...eventArgs) => onChangeNumber(eventArgs, row, column, rowIndex)}
|
||||
onUpdate:modelValue={(value) => (row.employees = value)}
|
||||
></TinyNumeric>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{ field: 'created_date', title: '创建日期' },
|
||||
{
|
||||
field: 'city',
|
||||
title: '城市',
|
||||
slots: {
|
||||
default: ({ row, column, rowIndex }, h) => (
|
||||
<div>
|
||||
<TinyInput
|
||||
placeholder="请输入"
|
||||
modelValue={row.city}
|
||||
onChange={(...eventArgs) => onChangeInput(eventArgs, row, column, rowIndex)}
|
||||
onUpdate:modelValue={(value) => (row.city = value)}
|
||||
></TinyInput>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const onChangeInput = function onChangeInput(eventArgs, args0, args1, args2) {
|
||||
console.log('onChangeInput', eventArgs)
|
||||
}
|
||||
const onChangeNumber = function onChangeNumber(eventArgs, args0, args1, args2) {
|
||||
console.log('onChangeNumber', eventArgs)
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"emitDeclarationOnly": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"emitDeclarationOnly": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts", ".eslintrc.cjs"]
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import path from 'node:path'
|
||||
import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill'
|
||||
import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill'
|
||||
|
||||
// @ts-ignore
|
||||
const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default
|
||||
// @ts-ignore
|
||||
const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [dts({ rollupTypes: true, tsconfigPath: './tsconfig.json' }), vue()],
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
plugins: [
|
||||
nodeGlobalsPolyfillPlugin({
|
||||
process: true,
|
||||
buffer: true
|
||||
}),
|
||||
nodeModulesPolyfillPlugin()
|
||||
]
|
||||
}
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, './src/index.ts'),
|
||||
name: 'block-compiler',
|
||||
fileName: (_format, entryName) => `${entryName}.js`,
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['@babel/core', '@vue/babel-plugin-jsx', 'vue', 'vue/compiler-sfc']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-block-build",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"description": "translate block to webcomponent template",
|
||||
"main": "./dist/web-components.es.js",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-vite-config",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ const getDevAlias = (useSourceAlias) => {
|
|||
'@opentiny/tiny-engine-builtin-component': path.resolve(basePath, 'packages/builtinComponent/index.js'),
|
||||
'@opentiny/tiny-engine-meta-register': path.resolve(basePath, 'packages/register/src/index.js'),
|
||||
'@opentiny/tiny-engine-layout': path.resolve(basePath, 'packages/layout/index.js'),
|
||||
'@opentiny/tiny-engine-configurator': path.resolve(basePath, 'packages/configurator/src/index.js')
|
||||
'@opentiny/tiny-engine-configurator': path.resolve(basePath, 'packages/configurator/src/index.js'),
|
||||
'@opentiny/tiny-engine-block-compiler': path.resolve(basePath, 'packages/block-compiler/src/index.ts')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-vite-plugin-meta-comments",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
export { default as CanvasCol } from './src/components/CanvasCol.vue'
|
||||
export { default as CanvasRow } from './src/components/CanvasRow.vue'
|
||||
export { default as CanvasRowColContainer } from './src/components/CanvasRowColContainer.vue'
|
||||
export { default as CanvasFlexBox } from './src/components/CanvasFlexBox.vue'
|
||||
export { default as CanvasSection } from './src/components/CanvasSection.vue'
|
||||
export { default as meta } from './src/meta'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-builtin-component",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"description": "",
|
||||
"main": "dist/index.mjs",
|
||||
"module": "dist/index.mjs",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="canvas-flex-box">
|
||||
<slot> </slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps } from 'vue'
|
||||
import { getStyleValue } from './helper'
|
||||
|
||||
const props = defineProps({
|
||||
flexDirection: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
gap: {
|
||||
type: [String, Number],
|
||||
default: '8px'
|
||||
},
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: '8px'
|
||||
},
|
||||
alignItems: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
justifyContent: {
|
||||
type: String,
|
||||
default: 'flex-start'
|
||||
}
|
||||
})
|
||||
|
||||
const styles = computed(() => ({
|
||||
flexDirection: props.flexDirection,
|
||||
gap: getStyleValue(props.gap),
|
||||
padding: getStyleValue(props.padding),
|
||||
alignItems: props.alignItems,
|
||||
justifyContent: props.justifyContent
|
||||
}))
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.canvas-flex-box {
|
||||
display: flex;
|
||||
flex: 1 1 0px;
|
||||
flex-direction: v-bind('styles.flexDirection');
|
||||
gap: v-bind('styles.gap');
|
||||
padding: v-bind('styles.padding');
|
||||
align-items: v-bind('styles.alignItems');
|
||||
justify-content: v-bind('styles.justifyContent');
|
||||
:deep(.canvas-container) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<div class="canvas-section">
|
||||
<slot> </slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.canvas-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
{
|
||||
"component": {
|
||||
"icon": "Box",
|
||||
"name": {
|
||||
"zh_CN": "弹性容器"
|
||||
},
|
||||
"component": "CanvasFlexBox",
|
||||
"schema": {
|
||||
"slots": {},
|
||||
"properties": [
|
||||
{
|
||||
"label": {
|
||||
"zh_CN": "基础信息"
|
||||
},
|
||||
"description": {
|
||||
"zh_CN": "基础信息"
|
||||
},
|
||||
"collapse": {
|
||||
"number": 6,
|
||||
"text": {
|
||||
"zh_CN": "显示更多"
|
||||
}
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"property": "flexDirection",
|
||||
"type": "String",
|
||||
"defaultValue": "row",
|
||||
"bindState": true,
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "排列方向"
|
||||
}
|
||||
},
|
||||
"cols": 12,
|
||||
"rules": [],
|
||||
"widget": {
|
||||
"component": "SelectConfigurator",
|
||||
"props": {
|
||||
"options": [
|
||||
{
|
||||
"label": "水平,起点在左端",
|
||||
"value": "row"
|
||||
},
|
||||
{
|
||||
"label": "水平,起点在右端",
|
||||
"value": "row-reverse"
|
||||
},
|
||||
{
|
||||
"label": "垂直,起点在上沿",
|
||||
"value": "column"
|
||||
},
|
||||
{
|
||||
"label": "垂直,起点在下沿",
|
||||
"value": "column-reverse"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"property": "gap",
|
||||
"defaultValue": "8px",
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "间距"
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"component": "InputConfigurator"
|
||||
},
|
||||
"description": {
|
||||
"zh_CN": "控制容器内水平和垂直的间距"
|
||||
},
|
||||
"labelPosition": "left"
|
||||
},
|
||||
{
|
||||
"property": "padding",
|
||||
"defaultValue": "8px",
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "内边距"
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"component": "InputConfigurator"
|
||||
},
|
||||
"labelPosition": "left"
|
||||
},
|
||||
{
|
||||
"property": "justifyContent",
|
||||
"type": "String",
|
||||
"defaultValue": "flex-start",
|
||||
"bindState": true,
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "水平对齐方式"
|
||||
}
|
||||
},
|
||||
"cols": 12,
|
||||
"rules": [],
|
||||
"widget": {
|
||||
"component": "SelectConfigurator",
|
||||
"props": {
|
||||
"options": [
|
||||
{
|
||||
"label": "左对齐",
|
||||
"value": "flex-start"
|
||||
},
|
||||
{
|
||||
"label": "右对齐",
|
||||
"value": "flex-end"
|
||||
},
|
||||
{
|
||||
"label": "居中",
|
||||
"value": "center"
|
||||
},
|
||||
{
|
||||
"label": "两端对齐,子元素间隔相等",
|
||||
"value": "space-between"
|
||||
},
|
||||
{
|
||||
"label": "子元素两侧间隔相等",
|
||||
"value": "space-around"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"property": "alignItems",
|
||||
"type": "String",
|
||||
"defaultValue": "center",
|
||||
"bindState": true,
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "垂直对齐方式"
|
||||
}
|
||||
},
|
||||
"cols": 12,
|
||||
"rules": [],
|
||||
"widget": {
|
||||
"component": "SelectConfigurator",
|
||||
"props": {
|
||||
"options": [
|
||||
{
|
||||
"label": "交叉轴的中点对齐",
|
||||
"value": "center"
|
||||
},
|
||||
{
|
||||
"label": "交叉轴的起点对齐",
|
||||
"value": "flex-start"
|
||||
},
|
||||
{
|
||||
"label": "交叉轴的终点对齐",
|
||||
"value": "flex-end"
|
||||
},
|
||||
{
|
||||
"label": "以子元素第一行文字的基线对齐",
|
||||
"value": "baseline"
|
||||
},
|
||||
{
|
||||
"label": "占满容器高度",
|
||||
"value": "stretch"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"events": {
|
||||
"onClick": {
|
||||
"label": {
|
||||
"zh_CN": "点击事件"
|
||||
},
|
||||
"description": {
|
||||
"zh_CN": "点击时触发的回调函数"
|
||||
},
|
||||
"type": "event",
|
||||
"functionInfo": {
|
||||
"params": [],
|
||||
"returns": {}
|
||||
},
|
||||
"defaultValue": ""
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"properties": []
|
||||
},
|
||||
"contentMenu": {
|
||||
"actions": []
|
||||
}
|
||||
},
|
||||
"configure": {
|
||||
"loop": true,
|
||||
"isContainer": true,
|
||||
"nestingRule": {
|
||||
"childWhitelist": [],
|
||||
"descendantBlacklist": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"snippet": {
|
||||
"name": {
|
||||
"zh_CN": "弹性容器"
|
||||
},
|
||||
"screenshot": "",
|
||||
"snippetName": "CanvasFlexBox",
|
||||
"icon": "Box",
|
||||
"schema": {
|
||||
"componentName": "CanvasFlexBox",
|
||||
"props": {
|
||||
"flexDirection": "row",
|
||||
"gap": "8px",
|
||||
"padding": "8px"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,21 +68,21 @@
|
|||
"schema": {
|
||||
"componentName": "CanvasRowColContainer",
|
||||
"props": {
|
||||
"rowGap": "20px"
|
||||
"rowGap": "16px"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "CanvasRow",
|
||||
"props": {
|
||||
"rowGap": "20px",
|
||||
"colGap": "20px"
|
||||
"rowGap": "16px",
|
||||
"colGap": "16px"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "CanvasCol",
|
||||
"props": {
|
||||
"rowGap": "20px",
|
||||
"colGap": "20px",
|
||||
"rowGap": "16px",
|
||||
"colGap": "16px",
|
||||
"grow": true,
|
||||
"shrink": true,
|
||||
"widthType": "auto"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"component": {
|
||||
"icon": "Box",
|
||||
"name": {
|
||||
"zh_CN": "全宽居中布局"
|
||||
},
|
||||
"component": "CanvasSection",
|
||||
"schema": {
|
||||
"slots": {},
|
||||
"properties": [
|
||||
{
|
||||
"label": {
|
||||
"zh_CN": "基础信息"
|
||||
},
|
||||
"description": {
|
||||
"zh_CN": "基础信息"
|
||||
},
|
||||
"collapse": {
|
||||
"number": 6,
|
||||
"text": {
|
||||
"zh_CN": "显示更多"
|
||||
}
|
||||
},
|
||||
"content": []
|
||||
}
|
||||
],
|
||||
"events": {
|
||||
"onClick": {
|
||||
"label": {
|
||||
"zh_CN": "点击事件"
|
||||
},
|
||||
"description": {
|
||||
"zh_CN": "点击时触发的回调函数"
|
||||
},
|
||||
"type": "event",
|
||||
"functionInfo": {
|
||||
"params": [],
|
||||
"returns": {}
|
||||
},
|
||||
"defaultValue": ""
|
||||
}
|
||||
},
|
||||
"shortcuts": {
|
||||
"properties": []
|
||||
},
|
||||
"contentMenu": {
|
||||
"actions": []
|
||||
}
|
||||
},
|
||||
"configure": {
|
||||
"loop": true,
|
||||
"isContainer": true,
|
||||
"nestingRule": {
|
||||
"childWhitelist": [],
|
||||
"descendantBlacklist": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"snippet": {
|
||||
"name": {
|
||||
"zh_CN": "全宽居中布局"
|
||||
},
|
||||
"screenshot": "",
|
||||
"snippetName": "CanvasSection",
|
||||
"icon": "Box",
|
||||
"schema": {
|
||||
"componentName": "CanvasSection",
|
||||
"props": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,24 @@
|
|||
import CanvasCol from './CanvasCol.json'
|
||||
import CanvasRow from './CanvasRow.json'
|
||||
import CanvasRowColContainer from './CanvasRowColContainer.json'
|
||||
import CanvasFlexBox from './CanvasFlexBox.json'
|
||||
import CanvasSection from './CanvasSection.json'
|
||||
|
||||
export default {
|
||||
components: [CanvasCol.component, CanvasRow.component, CanvasRowColContainer.component],
|
||||
components: [
|
||||
CanvasCol.component,
|
||||
CanvasRow.component,
|
||||
CanvasRowColContainer.component,
|
||||
CanvasFlexBox.component,
|
||||
CanvasSection.component
|
||||
],
|
||||
snippets: [
|
||||
{
|
||||
group: 'layout',
|
||||
label: {
|
||||
zh_CN: '布局与容器'
|
||||
},
|
||||
children: [CanvasRowColContainer.snippet]
|
||||
children: [CanvasRowColContainer.snippet, CanvasFlexBox.snippet, CanvasSection.snippet]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
# schema 元服务相关API(Experimental)
|
||||
|
||||
## 直接修改 schema 引用 & 调用通知更新
|
||||
|
||||
使用示例:
|
||||
|
||||
```javascript
|
||||
import { useCanvas, useMessage } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const pageSchema = useCanvas().getPageSchema()
|
||||
|
||||
pageSchema.css = "xxxx"
|
||||
|
||||
useMessage().publish({ topic: 'schemaChange' })
|
||||
```
|
||||
|
||||
注意:直接修改 schema 引用当前不能涉及到节点的增加、删除,不然会节点树 nodesMap 无法更新,导致画布无法选中新增的组件。
|
||||
|
||||
|
||||
> 注意:以下所有 API 皆为 Experimental 实验 API,请不要用在生产阶段
|
||||
|
||||
## 导入/导出 schema
|
||||
|
||||
> 这里的导入导出仅包含页面级别,不包含应用级别 schema
|
||||
|
||||
**导入 schema:**
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const data = { /*页面/区块 schema*/ }
|
||||
|
||||
useCanvas().importSchema(data)
|
||||
```
|
||||
|
||||
**导出 schema:**
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().exportSchema()
|
||||
```
|
||||
|
||||
## 页面 schema相关操作
|
||||
|
||||
> 主要描述对页面 schema 的增删查改操作
|
||||
|
||||
### 获取当前页面/区块 schema:
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().getPageSchema()
|
||||
```
|
||||
|
||||
### 获取当前选中节点 schema:
|
||||
|
||||
```javascript
|
||||
import { useProperties } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const schema = useProperties().getSchema()
|
||||
```
|
||||
|
||||
### 根据 id 查询对应的 节点schema(schema 片段)
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const schema = useCanvas().getNode('453254', false)
|
||||
```
|
||||
|
||||
类型:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 根据节点 id 获取 schema 片段
|
||||
* id: schema id
|
||||
* parent: 是否需要同时获取 parent 节点
|
||||
*/
|
||||
type getNode = (id: string, parent: boolean) => INode | { node: INode; parent: INode }
|
||||
```
|
||||
|
||||
### 节点操作
|
||||
|
||||
#### 插入节点
|
||||
|
||||
使用示例:
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'insert',
|
||||
parentId: '432423',
|
||||
newNodeData: { componentName: 'div', props: {}, children: [] },
|
||||
position: 'after',
|
||||
referTargetNodeId: '898432'
|
||||
})
|
||||
```
|
||||
|
||||
类型:
|
||||
|
||||
```typescript
|
||||
interface IInsertOperation {
|
||||
// 操作类型为 insert
|
||||
type: 'insert';
|
||||
// 要插入的节点的 父节点 id
|
||||
parentId: string;
|
||||
// 新节点数据
|
||||
newNodeData: INode;
|
||||
// 相对节点的 id,比如我们想要插入父节点 id 中 第 5 个 children 的后面,或者前面
|
||||
referTargetNodeId: string;
|
||||
// 相对节点的位置
|
||||
position: 'after' | 'before';
|
||||
}
|
||||
```
|
||||
|
||||
#### 删除节点
|
||||
|
||||
使用示例:
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'delete',
|
||||
id: '432423'
|
||||
})
|
||||
```
|
||||
|
||||
类型:
|
||||
|
||||
```typescript
|
||||
interface IDeleteOperation {
|
||||
type: 'delete';
|
||||
id: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### 修改节点 props
|
||||
|
||||
使用示例:
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'changeProps',
|
||||
id: '432423',
|
||||
value: { text: 'TinyEngine' },
|
||||
option: { overwrite: false }
|
||||
})
|
||||
```
|
||||
|
||||
类型:
|
||||
|
||||
```typescript
|
||||
interface IChangePropsOperation {
|
||||
type: 'changeProps';
|
||||
// 节点 id
|
||||
id: string;
|
||||
// 新的 props 值
|
||||
value: Record<string, any>;
|
||||
// 操作类型:是否覆写
|
||||
option: { overwrite: boolean; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新节点属性
|
||||
|
||||
使用示例:
|
||||
|
||||
```javascript
|
||||
import { useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'updateAttributes',
|
||||
id: '432423',
|
||||
value: { props: { ... }, loop: { ... } },
|
||||
overwrite: boolean
|
||||
})
|
||||
```
|
||||
|
||||
类型:
|
||||
|
||||
```typescript
|
||||
interface IUpdateAttrOperation {
|
||||
type: 'updateAttributes';
|
||||
id: string;
|
||||
// 对节点的属性修改
|
||||
value: Record<string, any>;
|
||||
// 是否是直接覆盖
|
||||
overwrite: boolean;
|
||||
}
|
||||
```
|
||||
|
|
@ -36,7 +36,8 @@ import {
|
|||
getOptions,
|
||||
getMetaApi,
|
||||
META_SERVICE,
|
||||
META_APP
|
||||
META_APP,
|
||||
useNotify
|
||||
} from '@opentiny/tiny-engine-meta-register'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import * as ast from '@opentiny/tiny-engine-common/js/ast'
|
||||
|
|
@ -72,7 +73,7 @@ export default {
|
|||
|
||||
const removeNode = (node) => {
|
||||
const { pageState } = useCanvas()
|
||||
footData.value = useCanvas().canvasApi.value.getNodePath(node?.id)
|
||||
footData.value = useCanvas().getNodePath(node?.id)
|
||||
pageState.currentSchema = {}
|
||||
pageState.properties = null
|
||||
}
|
||||
|
|
@ -102,10 +103,11 @@ export default {
|
|||
// 1. 页面或区块状态是未保存状态(尝试编辑)
|
||||
// 2. 页面刷新或第一次进入页面(含从别的页面或区块切换到别的页面或区块)
|
||||
// 3. 页面上已经有弹窗,不允许重复弹窗
|
||||
// 4. 当前历史堆栈为0,且当前未保存状态和上一次未保存状态不一致,不重复弹窗
|
||||
|
||||
const showConfirm = !isSaved || pageSchema !== oldPageSchema
|
||||
|
||||
if (!showConfirm || showModal) {
|
||||
if (!showConfirm || showModal || (useHistory().historyState?.index === 0 && isSaved !== oldIsSaved)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +131,6 @@ export default {
|
|||
useModal().confirm({
|
||||
title: '提示',
|
||||
message: renderMsg,
|
||||
status: 'info',
|
||||
exec: callback,
|
||||
cancel: callback,
|
||||
hide: () => {
|
||||
|
|
@ -139,19 +140,21 @@ export default {
|
|||
}
|
||||
)
|
||||
|
||||
const nodeSelected = (node, parent, type) => {
|
||||
const nodeSelected = (node, parent, type, id) => {
|
||||
const { toolbars } = useLayout().layoutState
|
||||
if (type !== 'clickTree') {
|
||||
useLayout().closePlugin()
|
||||
}
|
||||
|
||||
const { getSchema, getNodePath } = useCanvas().canvasApi.value
|
||||
const { getSchema, getNodePath } = useCanvas()
|
||||
const schemaItem = useCanvas().getNodeById(id)
|
||||
|
||||
const pageSchema = getSchema()
|
||||
|
||||
const schema = getSchema()
|
||||
// 如果选中的节点是画布,就设置成默认选中最外层schema
|
||||
useProperties().getProps(node || schema, parent)
|
||||
useCanvas().setCurrentSchema(node || schema)
|
||||
footData.value = getNodePath(node?.id)
|
||||
useProperties().getProps(schemaItem || pageSchema, parent)
|
||||
useCanvas().setCurrentSchema(schemaItem || pageSchema)
|
||||
footData.value = getNodePath(schemaItem?.id)
|
||||
toolbars.visiblePopover = false
|
||||
}
|
||||
|
||||
|
|
@ -218,13 +221,16 @@ export default {
|
|||
// 需要在canvas/render或内置组件里使用的方法
|
||||
getMaterial: useMaterial().getMaterial,
|
||||
addHistory: useHistory().addHistory,
|
||||
registerBlock: useMaterial().registerBlock,
|
||||
request: getMetaApi(META_SERVICE.Http).getHttp(),
|
||||
getPageById: getMetaApi(META_APP.AppManage).getPageById,
|
||||
getPageAncestors: usePage().getAncestors,
|
||||
getBaseInfo: () => getMetaApi(META_SERVICE.GlobalService).getBaseInfo(),
|
||||
addHistoryDataChangedCallback,
|
||||
ast
|
||||
ast,
|
||||
getBlockByName: useMaterial().getBlockByName,
|
||||
useModal,
|
||||
useMessage,
|
||||
useNotify
|
||||
},
|
||||
isBlock,
|
||||
CanvasLayout,
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable no-new-func */
|
||||
import { reactive, ref } from 'vue'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import { useHistory } from '@opentiny/tiny-engine-meta-register'
|
||||
import { reactive, ref, toRaw } from 'vue'
|
||||
import * as jsonDiffPatch from 'jsondiffpatch'
|
||||
import DiffMatchPatch from 'diff-match-patch'
|
||||
import { constants, utils } from '@opentiny/tiny-engine-utils'
|
||||
import { useHistory, getMetaApi, useMessage } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const { COMPONENT_NAME } = constants
|
||||
const { deepClone } = utils
|
||||
|
||||
const defaultPageState = {
|
||||
currentVm: null,
|
||||
|
|
@ -53,6 +56,7 @@ const defaultSchema = {
|
|||
|
||||
const canvasApi = ref({})
|
||||
const isCanvasApiReady = ref(false)
|
||||
const nodesMap = ref(new Map())
|
||||
|
||||
const initCanvasApi = (newCanvasApi) => {
|
||||
canvasApi.value = newCanvasApi
|
||||
|
|
@ -60,11 +64,83 @@ const initCanvasApi = (newCanvasApi) => {
|
|||
}
|
||||
|
||||
const pageState = reactive({ ...defaultPageState, loading: true })
|
||||
const rootSchema = ref([
|
||||
{
|
||||
id: 0,
|
||||
componentName: 'div',
|
||||
props: pageState.pageSchema?.props || {},
|
||||
children: pageState.pageSchema?.children || []
|
||||
}
|
||||
])
|
||||
|
||||
const generateNodesMap = (nodes, parent) => {
|
||||
nodes.forEach((nodeItem) => {
|
||||
if (!nodeItem.id) {
|
||||
nodeItem.id = utils.guid()
|
||||
}
|
||||
|
||||
nodesMap.value.set(nodeItem.id, {
|
||||
node: nodeItem,
|
||||
parent
|
||||
})
|
||||
|
||||
if (Array.isArray(nodeItem.children) && nodeItem.children.length) {
|
||||
generateNodesMap(nodeItem.children, nodeItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const jsonDiffPatchInstance = jsonDiffPatch.create({
|
||||
objectHash: function (obj, index) {
|
||||
return obj.fileName || obj.id || `$$index:${index}`
|
||||
},
|
||||
arrays: {
|
||||
detectMove: true,
|
||||
includeValueOnMove: false
|
||||
},
|
||||
textDiff: {
|
||||
diffMatchPatch: DiffMatchPatch,
|
||||
minLength: 60
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
propertyFilter: function (name, context) {
|
||||
return name.slice(0, 1) !== '$'
|
||||
},
|
||||
cloneDiffValues: false
|
||||
})
|
||||
|
||||
const { publish } = useMessage()
|
||||
|
||||
// 重置画布数据
|
||||
const resetCanvasState = async (state = {}) => {
|
||||
const previousSchema = JSON.parse(JSON.stringify(pageState.pageSchema))
|
||||
|
||||
Object.assign(pageState, defaultPageState, state)
|
||||
|
||||
await canvasApi.value?.setSchema(pageState.pageSchema)
|
||||
nodesMap.value.clear()
|
||||
|
||||
if (pageState.pageSchema) {
|
||||
if (!pageState.pageSchema.children) {
|
||||
pageState.pageSchema.children = []
|
||||
}
|
||||
|
||||
rootSchema.value = [
|
||||
{
|
||||
id: 0,
|
||||
componentName: 'div',
|
||||
props: pageState.pageSchema.props || {},
|
||||
children: pageState.pageSchema.children
|
||||
}
|
||||
]
|
||||
|
||||
nodesMap.value.set(0, { node: rootSchema.value, parent: pageState.pageSchema })
|
||||
|
||||
generateNodesMap(pageState.pageSchema.children, pageState.pageSchema)
|
||||
}
|
||||
|
||||
const diffPatch = jsonDiffPatchInstance.diff(previousSchema, pageState.pageSchema)
|
||||
|
||||
publish({ topic: 'schemaImport', data: { current: pageState.pageSchema, previous: previousSchema, diffPatch } })
|
||||
}
|
||||
|
||||
// 页面重置画布数据
|
||||
|
|
@ -80,11 +156,17 @@ const resetBlockCanvasState = async (state = {}) => {
|
|||
await resetCanvasState(state)
|
||||
}
|
||||
|
||||
const getDefaultSchema = (componentName = 'Page', fileName = '') => ({
|
||||
...defaultSchema,
|
||||
componentName,
|
||||
fileName
|
||||
})
|
||||
const getDefaultSchema = (componentName = 'Page', fileName = '') => {
|
||||
const DEFAULT_PAGE = getMetaApi('engine.service.page')?.getDefaultPage() || { page_content: { props: {}, css: '' } }
|
||||
|
||||
return {
|
||||
...defaultSchema,
|
||||
props: DEFAULT_PAGE.page_content?.props || {},
|
||||
css: DEFAULT_PAGE.page_content?.css || '',
|
||||
componentName,
|
||||
fileName
|
||||
}
|
||||
}
|
||||
|
||||
const setSaved = (flag = false) => {
|
||||
pageState.isSaved = flag
|
||||
|
|
@ -97,10 +179,13 @@ const clearCanvas = () => {
|
|||
const { fileName, componentName } = pageState.pageSchema || {}
|
||||
|
||||
resetCanvasState({
|
||||
pageSchema: { ...getDefaultSchema(componentName, fileName) }
|
||||
pageSchema: { ...deepClone(getDefaultSchema(componentName, fileName)) }
|
||||
})
|
||||
|
||||
setSaved(false)
|
||||
|
||||
canvasApi.value?.clearSelect?.()
|
||||
canvasApi.value?.updateRect?.()
|
||||
}
|
||||
|
||||
const isBlock = () => pageState.isBlock
|
||||
|
|
@ -109,12 +194,12 @@ const isBlock = () => pageState.isBlock
|
|||
const initData = (schema = { ...defaultSchema }, currentPage) => {
|
||||
if (schema.componentName === COMPONENT_NAME.Block) {
|
||||
resetBlockCanvasState({
|
||||
pageSchema: schema,
|
||||
pageSchema: toRaw(schema),
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
resetPageCanvasState({
|
||||
pageSchema: schema,
|
||||
pageSchema: toRaw(schema),
|
||||
currentPage,
|
||||
loading: false
|
||||
})
|
||||
|
|
@ -145,6 +230,326 @@ const clearCurrentState = () => {
|
|||
}
|
||||
const getCurrentPage = () => pageState.currentPage
|
||||
|
||||
const getNodeById = (id) => {
|
||||
return nodesMap.value.get(id)?.node
|
||||
}
|
||||
|
||||
const getNodeWithParentById = (id) => {
|
||||
return nodesMap.value.get(id)
|
||||
}
|
||||
|
||||
const delNode = (id) => {
|
||||
nodesMap.value.delete(id)
|
||||
}
|
||||
|
||||
const clearNodes = () => {
|
||||
nodesMap.value.clear()
|
||||
}
|
||||
|
||||
const setNode = (schema, parent) => {
|
||||
schema.id = schema.id || utils.guid()
|
||||
|
||||
nodesMap.value.set(schema.id, { node: schema, parent })
|
||||
}
|
||||
|
||||
const getNode = (id, parent) => {
|
||||
return parent ? nodesMap.value.get(id) : nodesMap.value.get(id)?.node
|
||||
}
|
||||
|
||||
const operationTypeMap = {
|
||||
insert: (operation) => {
|
||||
const { parentId, newNodeData, position, referTargetNodeId } = operation
|
||||
const parentNode = getNode(parentId) || pageState.pageSchema
|
||||
|
||||
if (!parentNode) {
|
||||
return {}
|
||||
}
|
||||
|
||||
parentNode.children = parentNode.children || []
|
||||
|
||||
if (!newNodeData.id) {
|
||||
newNodeData.id = utils.guid()
|
||||
}
|
||||
|
||||
if (referTargetNodeId) {
|
||||
const referenceNode = getNode(referTargetNodeId)
|
||||
let index = parentNode.children.indexOf(referenceNode)
|
||||
|
||||
if (index === -1) {
|
||||
index = 0
|
||||
}
|
||||
|
||||
index = position === 'before' ? index : index + 1
|
||||
|
||||
parentNode.children.splice(index, 0, newNodeData)
|
||||
|
||||
setNode(newNodeData, parentNode)
|
||||
|
||||
// 递归构建 nodeMap
|
||||
if (Array.isArray(newNodeData?.children) && newNodeData.children.length) {
|
||||
const newNode = getNode(newNodeData.id)
|
||||
generateNodesMap(newNodeData.children, newNode)
|
||||
}
|
||||
|
||||
return {
|
||||
current: newNodeData,
|
||||
previous: undefined
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'before') {
|
||||
parentNode.children.unshift(newNodeData)
|
||||
} else {
|
||||
parentNode.children.push(newNodeData)
|
||||
}
|
||||
|
||||
setNode(newNodeData, parentNode)
|
||||
|
||||
// 递归构建 nodeMap
|
||||
if (Array.isArray(newNodeData?.children) && newNodeData.children.length) {
|
||||
const newNode = getNode(newNodeData.id)
|
||||
generateNodesMap(newNodeData.children, newNode)
|
||||
}
|
||||
|
||||
return {
|
||||
current: newNodeData,
|
||||
previous: undefined
|
||||
}
|
||||
},
|
||||
delete: (operation) => {
|
||||
const { id } = operation
|
||||
const targetNode = getNode(id, true)
|
||||
|
||||
if (!targetNode) {
|
||||
return
|
||||
}
|
||||
|
||||
const { parent, node } = targetNode
|
||||
|
||||
const index = parent.children.indexOf(node)
|
||||
|
||||
if (index > -1) {
|
||||
parent.children.splice(index, 1)
|
||||
nodesMap.value.delete(node.id)
|
||||
}
|
||||
|
||||
let children = [...(node.children || [])]
|
||||
|
||||
// 递归清理 nodesMap
|
||||
while (children?.length) {
|
||||
const len = children.length
|
||||
children.forEach((item) => {
|
||||
const nodeItem = getNode(item.id)
|
||||
nodesMap.value.delete(item.id)
|
||||
|
||||
if (Array.isArray(nodeItem.children) && nodeItem.children.length) {
|
||||
children.push(...nodeItem.children)
|
||||
}
|
||||
})
|
||||
|
||||
children = children.slice(len)
|
||||
}
|
||||
|
||||
return {
|
||||
current: undefined,
|
||||
previous: node
|
||||
}
|
||||
},
|
||||
changeProps: (operation) => {
|
||||
const { id, value, option: changeOption } = operation
|
||||
let { node } = getNode(id, true) || {}
|
||||
const previous = deepClone(node)
|
||||
const { overwrite = false } = changeOption || {}
|
||||
|
||||
if (!node) {
|
||||
node = pageState.pageSchema
|
||||
}
|
||||
|
||||
if (!node.props) {
|
||||
node.props = {}
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
node.props = value.props
|
||||
} else {
|
||||
Object.assign(node.props, value?.props || {})
|
||||
}
|
||||
|
||||
return {
|
||||
current: node,
|
||||
previous
|
||||
}
|
||||
},
|
||||
updateAttributes: (operation) => {
|
||||
const { id, value, overwrite } = operation
|
||||
const { id: _id, children, ...restAttr } = value
|
||||
const node = getNode(id)
|
||||
|
||||
// 其他属性直接浅 merge
|
||||
Object.assign(node, restAttr)
|
||||
|
||||
// 配置了 overwrite,需要将没有传入的属性进行删除(不包括 children)
|
||||
if (overwrite) {
|
||||
const { id: _unUsedId, children: _unUsedChildren, ...restOrigin } = node
|
||||
const originKeys = Object.keys(restOrigin)
|
||||
const newKeysSet = new Set(Object.keys(restAttr))
|
||||
|
||||
originKeys.forEach((key) => {
|
||||
if (!newKeysSet.has(key)) {
|
||||
delete node[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!Array.isArray(children)) {
|
||||
// 非数组类型的 children,比如是直接的字符串作为 children
|
||||
if (children || typeof children === 'string') {
|
||||
node.children = children
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const newChildren = children.map((item) => {
|
||||
if (!item.id) {
|
||||
item.id = utils.guid()
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
// 传了 children 进来,需要找出来被删除的、新增的,剩下的是修改的。
|
||||
const originChildrenIds = (node.children || []).filter(({ id }) => id).map(({ id }) => id)
|
||||
const originChildrenSet = new Set(originChildrenIds)
|
||||
|
||||
const newChildrenSet = new Set(newChildren.map(({ id }) => id))
|
||||
// 被删除的项
|
||||
const deletedIds = originChildrenIds.filter((id) => !newChildrenSet.has(id))
|
||||
const deletedIdsSet = new Set(deletedIds)
|
||||
|
||||
for (const id of deletedIds) {
|
||||
operationTypeMap.delete({ id })
|
||||
}
|
||||
|
||||
// 筛选出来新增的和修改的
|
||||
const changedChildren = newChildren.filter(({ id }) => !deletedIdsSet.has(id))
|
||||
|
||||
changedChildren.forEach((childItem, index) => {
|
||||
// 新增
|
||||
if (!originChildrenSet.has(childItem.id)) {
|
||||
operationTypeMap.insert({
|
||||
parentId: id,
|
||||
newNodeData: childItem,
|
||||
position: 'after',
|
||||
referTargetNodeId: changedChildren?.[index]?.id
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 直接改引用插入进来,但是没有构建对应的 Map,需要构建Map
|
||||
if (!getNode(childItem.id)) {
|
||||
setNode(childItem, node)
|
||||
|
||||
// 递归构建 nodeMap
|
||||
if (Array.isArray(childItem?.children) && childItem.children.length) {
|
||||
const newNode = getNode(childItem.id)
|
||||
generateNodesMap(childItem.children, newNode)
|
||||
}
|
||||
}
|
||||
|
||||
// 递归修改
|
||||
operationTypeMap.updateAttributes({ id: childItem.id, value: childItem })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const lastUpdateType = ref('')
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* @param {*} operation
|
||||
* @returns
|
||||
*/
|
||||
const operateNode = async (operation) => {
|
||||
if (!operationTypeMap[operation.type]) {
|
||||
return
|
||||
}
|
||||
|
||||
operationTypeMap[operation.type](operation)
|
||||
|
||||
lastUpdateType.value = operation.type
|
||||
|
||||
publish({ topic: 'schemaChange', data: { operation } })
|
||||
|
||||
if (operation.type !== 'insert') {
|
||||
// 这里 setTimeout 延时是需要等画布更新渲染完成,然后再更新,才能得到正确的组件 offset
|
||||
setTimeout(() => {
|
||||
canvasApi.value.updateRect?.()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取传入的 schema 与最新 schema 的 diff
|
||||
const getSchemaDiff = (schema) => {
|
||||
return jsonDiffPatchInstance.diff(schema, pageState.pageSchema)
|
||||
}
|
||||
|
||||
const patchLatestSchema = (schema) => {
|
||||
// 这里 pageSchema 需要 deepClone,不然 patch 的时候,会 patch 成同一个引用,造成画布无法更新
|
||||
const diff = jsonDiffPatchInstance.diff(schema, deepClone(pageState.pageSchema))
|
||||
|
||||
if (diff) {
|
||||
jsonDiffPatchInstance.patch(schema, diff)
|
||||
}
|
||||
}
|
||||
|
||||
const importSchema = (data) => {
|
||||
let importData = data
|
||||
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
importData = JSON.parse(data)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[useCanvas.importSchema] Invalid data')
|
||||
}
|
||||
}
|
||||
|
||||
// JSON 格式校验
|
||||
resetCanvasState({
|
||||
pageSchema: importData
|
||||
})
|
||||
|
||||
canvasApi.value?.clearSelect?.()
|
||||
}
|
||||
|
||||
const exportSchema = () => {
|
||||
return JSON.stringify(pageState.pageSchema)
|
||||
}
|
||||
|
||||
const getSchema = () => {
|
||||
return pageState.pageSchema || {}
|
||||
}
|
||||
|
||||
const getNodePath = (id, nodes = []) => {
|
||||
const { parent, node } = getNodeWithParentById(id) || {}
|
||||
|
||||
node && nodes.unshift({ name: node.componentName, node: id })
|
||||
|
||||
if (parent) {
|
||||
parent && getNodePath(parent.id, nodes)
|
||||
} else {
|
||||
nodes.unshift({ name: 'BODY', node: id })
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
const updateSchema = (data) => {
|
||||
Object.assign(pageState.pageSchema, data)
|
||||
|
||||
publish({ topic: 'schemaChange', data: {} })
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return {
|
||||
pageState,
|
||||
|
|
@ -163,6 +568,21 @@ export default function () {
|
|||
getCurrentPage,
|
||||
initCanvasApi,
|
||||
canvasApi,
|
||||
isCanvasApiReady
|
||||
isCanvasApiReady,
|
||||
getNodeById,
|
||||
getNodeWithParentById,
|
||||
delNode,
|
||||
clearNodes,
|
||||
setNode,
|
||||
getNode,
|
||||
operateNode,
|
||||
lastUpdateType,
|
||||
getSchemaDiff,
|
||||
patchLatestSchema,
|
||||
importSchema,
|
||||
exportSchema,
|
||||
getSchema,
|
||||
getNodePath,
|
||||
updateSchema
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,14 @@ export function getImportMapData(overrideVersions = {}) {
|
|||
const blockRequire = {
|
||||
imports: {
|
||||
'@opentiny/vue': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue.mjs`,
|
||||
'@opentiny/vue-icon': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue-icon.mjs`
|
||||
'@opentiny/vue-icon': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue-icon.mjs`,
|
||||
'element-plus': `${VITE_CDN_DOMAIN}/element-plus@2.4.2/dist/index.full.mjs`,
|
||||
'@opentiny/tiny-engine-builtin-component': `${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component@^2.0.0/dist/index.mjs`
|
||||
},
|
||||
importStyles: [`${VITE_CDN_DOMAIN}/@opentiny/vue-theme@${importMapVersions.tinyVue}/index.css`]
|
||||
importStyles: [
|
||||
`${VITE_CDN_DOMAIN}/@opentiny/vue-theme@${importMapVersions.tinyVue}/index.css`,
|
||||
`${VITE_CDN_DOMAIN}/element-plus@2.4.2/dist/index.css`
|
||||
]
|
||||
}
|
||||
|
||||
// 以下内容由于物料协议不支持声明子依赖而@opentiny/vue需要依赖所以需要补充
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted, ref, computed, onUnmounted } from 'vue'
|
||||
import { onMounted, ref, computed, onUnmounted, watch, watchEffect } from 'vue'
|
||||
import { iframeMonitoring } from '@opentiny/tiny-engine-common/js/monitor'
|
||||
import { useTranslate, useCanvas, useMaterial } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useTranslate, useCanvas, useMaterial, useMessage, useResource } from '@opentiny/tiny-engine-meta-register'
|
||||
import { NODE_UID, NODE_LOOP, DESIGN_MODE } from '../../common'
|
||||
import { registerHostkeyEvent, removeHostkeyEvent } from './keyboard'
|
||||
import CanvasMenu, { closeMenu, openMenu } from './components/CanvasMenu.vue'
|
||||
|
|
@ -114,6 +114,25 @@ export default {
|
|||
if (iframe.value) {
|
||||
const win = iframe.value.contentWindow
|
||||
win.thirdPartyDeps = useMaterial().materialState.thirdPartyDeps
|
||||
|
||||
const { subscribe, unsubscribe } = useMessage()
|
||||
const { getSchemaDiff, patchLatestSchema, getSchema, getNode } = useCanvas()
|
||||
const { appSchemaState } = useResource()
|
||||
|
||||
iframe.value.contentWindow.host = {
|
||||
unsubscribe,
|
||||
subscribe,
|
||||
getSchemaDiff,
|
||||
patchLatestSchema,
|
||||
watch,
|
||||
watchEffect,
|
||||
getSchema,
|
||||
appSchema: appSchemaState,
|
||||
schemaUtils: {
|
||||
getSchema,
|
||||
getNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,11 +124,10 @@ import {
|
|||
updateRect,
|
||||
copyNode,
|
||||
getRenderer,
|
||||
getSchema,
|
||||
dragStart,
|
||||
getCurrentElement
|
||||
} from '../container'
|
||||
import { useLayout, useMaterial } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useLayout, useMaterial, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { Popover } from '@opentiny/vue'
|
||||
import shortCutPopover from './shortCutPopover.vue'
|
||||
|
||||
|
|
@ -236,7 +235,7 @@ export default {
|
|||
return !props.resize && parent && parent?.type !== 'JSSlot'
|
||||
})
|
||||
|
||||
const showToParent = computed(() => getCurrent().parent !== getSchema())
|
||||
const showToParent = computed(() => getCurrent().parent !== useCanvas().getSchema())
|
||||
|
||||
const isModal = computed(() => {
|
||||
const config = useMaterial().getMaterial(props.selectState.componentName)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { useLayout } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useLayout, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { canvasState } from '../container'
|
||||
|
||||
export default {
|
||||
|
|
@ -106,6 +106,15 @@ export default {
|
|||
{ flush: 'post' }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => sizeStyle.value,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
useCanvas().canvasApi.value?.updateRect?.()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
onMouseDown,
|
||||
onMouseMove,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ import {
|
|||
NODE_TAG,
|
||||
NODE_LOOP
|
||||
} from '../../common'
|
||||
import { useCanvas, useLayout, useResource, useTranslate, useMaterial } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useCanvas, useLayout, useTranslate, useMaterial } from '@opentiny/tiny-engine-meta-register'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
import { isVsCodeEnv } from '@opentiny/tiny-engine-common/js/environments'
|
||||
import Builtin from '../../render/src/builtin/builtin.json' //TODO 画布内外应该分开
|
||||
|
||||
|
|
@ -72,15 +73,7 @@ export const getDesignMode = () => getRenderer()?.getDesignMode()
|
|||
|
||||
export const setDesignMode = (mode) => getRenderer()?.setDesignMode(mode)
|
||||
|
||||
export const getGlobalState = () => getRenderer().getGlobalState()
|
||||
|
||||
export const getNode = (id, parent) => getRenderer()?.getNode(id, parent)
|
||||
|
||||
export const getSchema = () => getRenderer()?.getSchema()
|
||||
|
||||
export const getContext = () => {
|
||||
return getRenderer().getContext()
|
||||
}
|
||||
export const getSchema = () => useCanvas().getPageSchema()
|
||||
|
||||
// 记录拖拽状态
|
||||
export const dragState = reactive({
|
||||
|
|
@ -251,48 +244,51 @@ const getRect = (element) => {
|
|||
}
|
||||
|
||||
const inserAfter = ({ parent, node, data }) => {
|
||||
const parentChildren = parent.children
|
||||
const index = parentChildren.indexOf(node)
|
||||
parent.children.splice(index + 1, 0, data)
|
||||
if (!data.id) {
|
||||
data.id = utils.guid()
|
||||
}
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'insert',
|
||||
parentId: parent.id,
|
||||
newNodeData: data,
|
||||
position: 'after',
|
||||
referTargetNodeId: node.id
|
||||
})
|
||||
}
|
||||
|
||||
const insertBefore = ({ parent, node, data }) => {
|
||||
const parentChildren = parent.children
|
||||
const index = parentChildren.indexOf(node)
|
||||
parent.children.splice(index, 0, data)
|
||||
if (!data.id) {
|
||||
data.id = utils.guid()
|
||||
}
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'insert',
|
||||
parentId: parent.id,
|
||||
newNodeData: data,
|
||||
position: 'before',
|
||||
referTargetNodeId: node.id
|
||||
})
|
||||
}
|
||||
|
||||
const insertInner = ({ node, data }, position) => {
|
||||
node.children = node.children || []
|
||||
|
||||
if (position === POSITION.TOP || position === POSITION.LEFT) {
|
||||
node.children.unshift(data)
|
||||
} else {
|
||||
node.children.push(data)
|
||||
if (!data.id) {
|
||||
data.id = utils.guid()
|
||||
}
|
||||
|
||||
useCanvas().operateNode({
|
||||
type: 'insert',
|
||||
parentId: node.id,
|
||||
newNodeData: data,
|
||||
position: [POSITION.TOP, POSITION.LEFT].includes(position) ? 'before' : 'after'
|
||||
})
|
||||
}
|
||||
|
||||
export const removeNode = ({ parent, node }) => {
|
||||
const parentChildren = parent.children || parent.value
|
||||
const index = parentChildren.indexOf(node)
|
||||
|
||||
if (index > -1) {
|
||||
parentChildren.splice(index, 1)
|
||||
} else {
|
||||
const templates = parentChildren.filter(({ componentName }) => componentName === 'Template')
|
||||
|
||||
templates.forEach((template) => {
|
||||
const { children } = template
|
||||
|
||||
if (children.length) {
|
||||
children.splice(children.indexOf(node), 1)
|
||||
}
|
||||
|
||||
if (!children.length) {
|
||||
parentChildren.splice(parentChildren.indexOf(template), 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
export const removeNode = (id) => {
|
||||
useCanvas().operateNode({
|
||||
type: 'delete',
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
export const removeNodeById = (id) => {
|
||||
|
|
@ -300,7 +296,7 @@ export const removeNodeById = (id) => {
|
|||
return
|
||||
}
|
||||
|
||||
removeNode(getNode(id, true))
|
||||
removeNode(id)
|
||||
clearSelect()
|
||||
getController().addHistory()
|
||||
canvasState.emit('remove')
|
||||
|
|
@ -426,13 +422,13 @@ const isAncestor = (ancestor, descendant) => {
|
|||
let descendantId = typeof descendant === 'string' ? descendant : descendant.id
|
||||
|
||||
while (descendantId) {
|
||||
const { parent } = getNode(descendantId, true) || {}
|
||||
const { parent } = useCanvas().getNodeWithParentById(descendantId) || {}
|
||||
|
||||
if (parent.id === ancestorId) {
|
||||
if (parent?.id === ancestorId) {
|
||||
return true
|
||||
}
|
||||
|
||||
descendantId = parent.id
|
||||
descendantId = parent?.id
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
@ -484,6 +480,7 @@ const setHoverRect = (element, data) => {
|
|||
const configure = getConfigure(componentName)
|
||||
const rect = getRect(element)
|
||||
const { left, height, top, width } = rect
|
||||
const { getSchema, getNodeWithParentById } = useCanvas()
|
||||
|
||||
hoverState.configure = configure
|
||||
|
||||
|
|
@ -495,7 +492,7 @@ const setHoverRect = (element, data) => {
|
|||
|
||||
// 如果拖拽经过的元素是body或者是带有容器属性的盒子,并且在元素内部插入,则需要特殊处理
|
||||
if ((isBodyEl(element) || configure?.isContainer) && rectType === POSITION.IN) {
|
||||
const { node } = isBodyEl(element) ? { node: getSchema() } : getNode(id, true) || {}
|
||||
const { node } = isBodyEl(element) ? { node: getSchema() } : getNodeWithParentById(id) || {}
|
||||
const children = node?.children || []
|
||||
if (children.length > 0) {
|
||||
// 如果容器盒子有子节点,则以最后一个子节点为拖拽参照物
|
||||
|
|
@ -649,10 +646,12 @@ export const selectNode = async (id, type) => {
|
|||
const loopId = type.split('=')[1]
|
||||
canvasState.loopId = loopId
|
||||
}
|
||||
const { node, parent } = getNode(id, true) || {}
|
||||
|
||||
const { node, parent } = useCanvas().getNodeWithParentById(id) || {}
|
||||
|
||||
let element = querySelectById(id, type)
|
||||
|
||||
if (element) {
|
||||
if (element && node) {
|
||||
const { rootSelector } = getConfigure(node.componentName)
|
||||
element = rootSelector ? element.querySelector(rootSelector) : element
|
||||
}
|
||||
|
|
@ -662,7 +661,7 @@ export const selectNode = async (id, type) => {
|
|||
|
||||
await scrollToNode(element)
|
||||
setSelectRect(element)
|
||||
canvasState.emit('selected', node, parent, type)
|
||||
canvasState.emit('selected', node, parent, type, id)
|
||||
|
||||
return node
|
||||
}
|
||||
|
|
@ -674,7 +673,7 @@ export const hoverNode = (id, data) => {
|
|||
|
||||
export const insertNode = (node, position = POSITION.IN, select = true) => {
|
||||
if (!node.parent) {
|
||||
insertInner({ node: canvasState.schema, data: node.data }, position)
|
||||
insertInner({ node: useCanvas().pageState.pageSchema, data: node.data }, position)
|
||||
} else {
|
||||
switch (position) {
|
||||
case POSITION.TOP:
|
||||
|
|
@ -709,7 +708,8 @@ export const copyNode = (id) => {
|
|||
if (!id) {
|
||||
return
|
||||
}
|
||||
const { node, parent } = getNode(id, true)
|
||||
|
||||
const { node, parent } = useCanvas().getNodeWithParentById(id)
|
||||
|
||||
inserAfter({ parent, node, data: copyObject(node) })
|
||||
getController().addHistory()
|
||||
|
|
@ -721,15 +721,18 @@ export const onMouseUp = () => {
|
|||
const absolute = canvasState.type === 'absolute'
|
||||
const sourceId = data?.id
|
||||
const lineId = lineState.id
|
||||
const { getNodeWithParentById, getSchema } = useCanvas()
|
||||
|
||||
if (draging && !forbidden) {
|
||||
const { parent, node } = getNode(lineId, true) || {} // target
|
||||
const targetNode = { parent, node, data: toRaw(data) }
|
||||
const { parent, node } = getNodeWithParentById(lineId) || {} // target
|
||||
|
||||
const insertData = toRaw(data)
|
||||
const targetNode = { parent, node, data: { ...insertData, children: insertData.children || [] } }
|
||||
|
||||
if (sourceId) {
|
||||
// 内部拖拽
|
||||
if (sourceId !== lineId && !absolute) {
|
||||
removeNode(getNode(sourceId, true))
|
||||
removeNode(sourceId)
|
||||
insertNode(targetNode, position)
|
||||
}
|
||||
} else {
|
||||
|
|
@ -748,26 +751,6 @@ export const onMouseUp = () => {
|
|||
dragEnd()
|
||||
}
|
||||
|
||||
export const setPageCss = (css = '') => {
|
||||
const id = 'page-css'
|
||||
const document = getDocument()
|
||||
let element = document.getElementById(id)
|
||||
const head = document.querySelector('head')
|
||||
|
||||
document.body.setAttribute('style', '')
|
||||
|
||||
if (!element) {
|
||||
element = document.createElement('style')
|
||||
element.setAttribute('type', 'text/css')
|
||||
element.setAttribute('id', id)
|
||||
|
||||
element.innerHTML = css
|
||||
head.appendChild(element)
|
||||
} else {
|
||||
element.innerHTML = css
|
||||
}
|
||||
}
|
||||
|
||||
export const addStyle = (href) => appendStyle(href, getDocument())
|
||||
|
||||
export const addScript = (src) => appendScript(src, getDocument())
|
||||
|
|
@ -786,59 +769,10 @@ export const setLocales = (messages, merge) => {
|
|||
})
|
||||
}
|
||||
|
||||
export const setState = (state) => {
|
||||
getRenderer().setState(state)
|
||||
}
|
||||
|
||||
export const setUtils = (utils, clear, isForceRefresh) => {
|
||||
getRenderer().setUtils(utils, clear, isForceRefresh)
|
||||
}
|
||||
|
||||
export const updateUtils = (utils) => {
|
||||
getRenderer().updateUtils(utils)
|
||||
}
|
||||
|
||||
export const deleteUtils = (utils) => {
|
||||
getRenderer().deleteUtils(utils)
|
||||
}
|
||||
|
||||
export const deleteState = (variable) => {
|
||||
getRenderer().deleteState(variable)
|
||||
}
|
||||
|
||||
export const setGlobalState = (state) => {
|
||||
useResource().resState.globalState = state
|
||||
getRenderer().setGlobalState(state)
|
||||
}
|
||||
|
||||
export const getNodePath = (id, nodes = []) => {
|
||||
const { parent, node } = getNode(id, true) || {}
|
||||
|
||||
node && nodes.unshift({ name: node.componentName, node: id })
|
||||
|
||||
if (parent) {
|
||||
parent && getNodePath(parent.id, nodes)
|
||||
} else {
|
||||
nodes.unshift({ name: 'BODY', node: id })
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
export const setSchema = async (schema) => {
|
||||
clearHover()
|
||||
clearSelect()
|
||||
canvasState.schema = await getRenderer()?.setSchema(schema)
|
||||
|
||||
return canvasState.schema
|
||||
}
|
||||
|
||||
export const setConfigure = (configure) => {
|
||||
getRenderer().setConfigure(configure)
|
||||
}
|
||||
|
||||
export const setProps = (data) => getRenderer()?.setProps(data)
|
||||
|
||||
export const setI18n = (data) => {
|
||||
const messages = data || useTranslate().getData()
|
||||
const i18n = getRenderer().getI18n()
|
||||
|
|
@ -868,12 +802,8 @@ export const canvasDispatch = (name, data, doc = getDocument()) => {
|
|||
export const canvasApi = {
|
||||
dragStart,
|
||||
updateRect,
|
||||
getContext,
|
||||
getNodePath,
|
||||
dragMove,
|
||||
setLocales,
|
||||
setState,
|
||||
deleteState,
|
||||
getRenderer,
|
||||
clearSelect,
|
||||
selectNode,
|
||||
|
|
@ -881,41 +811,26 @@ export const canvasApi = {
|
|||
insertNode,
|
||||
removeNode,
|
||||
addComponent,
|
||||
setPageCss,
|
||||
addScript,
|
||||
addStyle,
|
||||
getNode,
|
||||
getCurrent,
|
||||
setSchema,
|
||||
setUtils,
|
||||
updateUtils,
|
||||
deleteUtils,
|
||||
getSchema,
|
||||
setI18n,
|
||||
getCanvasType,
|
||||
setCanvasType,
|
||||
setProps,
|
||||
setGlobalState,
|
||||
getGlobalState,
|
||||
getDesignMode,
|
||||
setDesignMode,
|
||||
getDocument,
|
||||
canvasDispatch,
|
||||
Builtin,
|
||||
setDataSourceMap: (...args) => {
|
||||
return canvasState.renderer.setDataSourceMap(...args)
|
||||
removeBlockCompsCache: (...args) => {
|
||||
return canvasState.renderer.removeBlockCompsCache(...args)
|
||||
},
|
||||
getDataSourceMap: (...args) => {
|
||||
return canvasState.renderer.getDataSourceMap(...args)
|
||||
updateCanvas: (...args) => {
|
||||
return canvasState.renderer.updateCanvas(...args)
|
||||
}
|
||||
}
|
||||
|
||||
export const initCanvas = ({ renderer, iframe, emit, controller }) => {
|
||||
const currentSchema = getSchema()
|
||||
|
||||
// 在点击刷新按钮的情况下继续保留最新的schema.json
|
||||
const schema = currentSchema ? currentSchema : useCanvas().getPageSchema()
|
||||
|
||||
canvasState.iframe = iframe
|
||||
canvasState.emit = emit
|
||||
// 存放画布外层传进来的插件api
|
||||
|
|
@ -930,11 +845,6 @@ export const initCanvas = ({ renderer, iframe, emit, controller }) => {
|
|||
senterMessage({ type: 'i18nReady', value: true }, '*')
|
||||
}
|
||||
|
||||
setGlobalState(useResource().resState.globalState)
|
||||
renderer.setDataSourceMap(useResource().resState.dataSource)
|
||||
// 设置画布全局的utils工具类上下文环境
|
||||
setUtils(useResource().resState.utils)
|
||||
setSchema(schema)
|
||||
setConfigure(useMaterial().getConfigureMap())
|
||||
canvasDispatch('updateDependencies', { detail: useMaterial().materialState.thirdPartyDeps })
|
||||
canvasState.loading = false
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
getCurrent,
|
||||
insertNode,
|
||||
selectNode,
|
||||
getSchema,
|
||||
POSITION,
|
||||
removeNodeById,
|
||||
allowInsert,
|
||||
|
|
@ -22,7 +21,7 @@ import {
|
|||
clearHover,
|
||||
hoverState
|
||||
} from './container'
|
||||
import { useHistory } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useHistory, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { copyObject } from '../../common'
|
||||
import { getClipboardSchema, setClipboardSchema } from './utils'
|
||||
|
||||
|
|
@ -63,7 +62,7 @@ const handlerArrow = (keyCode) => {
|
|||
if (schema) {
|
||||
index = parent.children.indexOf(schema)
|
||||
} else {
|
||||
schema = getSchema()
|
||||
schema = useCanvas().getSchema()
|
||||
}
|
||||
|
||||
let obj = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-canvas",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
|
@ -43,9 +43,11 @@
|
|||
"@opentiny/tiny-engine-meta-register": "workspace:*",
|
||||
"@opentiny/tiny-engine-utils": "workspace:*",
|
||||
"@opentiny/tiny-engine-webcomponent-core": "workspace:*",
|
||||
"@vue/babel-plugin-jsx": "1.1.1",
|
||||
"@vue/babel-plugin-jsx": "^1.2.5",
|
||||
"@vue/shared": "^3.3.4",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"jsondiffpatch": "^0.6.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-selector-parser": "^7.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="block-error-container">区块 {{ name }} 加载错误</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.block-error-container {
|
||||
// 画布内不能使用 css 变量
|
||||
background-color: #f23030;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>区块 {{ name }} 加载中</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -11,27 +11,22 @@
|
|||
*/
|
||||
|
||||
import { provide, watch, defineComponent, PropType, ref, inject, onUnmounted, h, Ref } from 'vue'
|
||||
|
||||
import { useBroadcastChannel } from '@vueuse/core'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
|
||||
import { getDesignMode, setDesignMode, setController, useCustomRenderer, getController } from './canvas-function'
|
||||
import { setConfigure } from './material-function'
|
||||
import { removeBlockCompsCache, setConfigure } from './material-function'
|
||||
import { useUtils, useBridge, useDataSourceMap, useGlobalState } from './application-function'
|
||||
import { IPageSchema, useContext, usePageContext, useSchema } from './page-block-function'
|
||||
import { api, setCurrentApi } from './canvas-function/canvas-api'
|
||||
import { getPageAncestors } from './material-function/page-getter'
|
||||
import CanvasEmpty from './canvas-function/CanvasEmpty.vue'
|
||||
import { setCurrentPage } from './canvas-function/page-switcher'
|
||||
|
||||
const { BROADCAST_CHANNEL } = constants
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
|
||||
// global-context singleton
|
||||
const { context: globalContext, setContext: setGlobalContext } = useContext()
|
||||
const { refreshKey, utils, getUtils, setUtils, updateUtils, deleteUtils } = useUtils(globalContext)
|
||||
const { bridge, setBridge, getBridge } = useBridge()
|
||||
const { refreshKey, utils, getUtils, setUtils } = useUtils(globalContext)
|
||||
const { bridge } = useBridge()
|
||||
const { getDataSourceMap, setDataSourceMap } = useDataSourceMap()
|
||||
const { getGlobalState, setGlobalState, stores } = useGlobalState()
|
||||
const { setGlobalState, stores } = useGlobalState()
|
||||
const updateGlobalContext = () => {
|
||||
const context = {
|
||||
utils,
|
||||
|
|
@ -46,20 +41,12 @@ const updateGlobalContext = () => {
|
|||
setGlobalContext(context, true)
|
||||
}
|
||||
updateGlobalContext()
|
||||
const activePageContext = usePageContext()
|
||||
export const activePageContext = usePageContext()
|
||||
|
||||
const {
|
||||
schema: activeSchema,
|
||||
getSchema,
|
||||
setSchema,
|
||||
getState,
|
||||
setState,
|
||||
deleteState,
|
||||
getProps,
|
||||
setProps,
|
||||
getMethods,
|
||||
setMethods,
|
||||
setPagecss
|
||||
setPageCss
|
||||
} = useSchema(activePageContext, {
|
||||
utils,
|
||||
bridge,
|
||||
|
|
@ -67,44 +54,31 @@ const {
|
|||
getDataSourceMap
|
||||
})
|
||||
const { getRenderer, setRenderer } = useCustomRenderer()
|
||||
const getNode = (id, parent) => (id ? activePageContext.getNode(id, parent) : activeSchema)
|
||||
const { getContext, getRoot, setNode, setCondition, getCondition, getConditions } = activePageContext
|
||||
const { setCondition } = activePageContext
|
||||
const updateCanvas = () => {
|
||||
refreshKey.value++
|
||||
}
|
||||
setCurrentApi({
|
||||
getUtils,
|
||||
setUtils,
|
||||
updateUtils,
|
||||
deleteUtils,
|
||||
getBridge,
|
||||
setBridge,
|
||||
getMethods,
|
||||
setMethods,
|
||||
setController,
|
||||
setConfigure,
|
||||
getSchema,
|
||||
setSchema,
|
||||
getState,
|
||||
deleteState,
|
||||
setState,
|
||||
getProps,
|
||||
setProps,
|
||||
getContext,
|
||||
getNode,
|
||||
getRoot,
|
||||
setPagecss,
|
||||
setCondition,
|
||||
getCondition,
|
||||
getConditions,
|
||||
getGlobalState,
|
||||
getDataSourceMap,
|
||||
setDataSourceMap,
|
||||
setGlobalState,
|
||||
setNode,
|
||||
getRenderer,
|
||||
setRenderer,
|
||||
getDesignMode,
|
||||
setDesignMode
|
||||
setDesignMode,
|
||||
removeBlockCompsCache,
|
||||
updateCanvas
|
||||
})
|
||||
|
||||
const throttleUpdateSchema = useThrottleFn(
|
||||
() => {
|
||||
window.host.patchLatestSchema(activeSchema)
|
||||
},
|
||||
100,
|
||||
true
|
||||
)
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
entry: {
|
||||
|
|
@ -162,18 +136,83 @@ export default defineComponent({
|
|||
onUnmounted(() => {
|
||||
cancel()
|
||||
})
|
||||
|
||||
window.host.subscribe({
|
||||
topic: 'schemaChange',
|
||||
subscriber: 'canvasRenderer',
|
||||
callback: throttleUpdateSchema
|
||||
})
|
||||
|
||||
window.host.subscribe({
|
||||
topic: 'schemaImport',
|
||||
subscriber: 'canvasRenderer',
|
||||
callback: () => {
|
||||
setSchema(window.host.getSchema())
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => activeSchema.css,
|
||||
(value) => {
|
||||
setPageCss(value)
|
||||
}
|
||||
)
|
||||
|
||||
const utilsWatchCanceler = window.host.watch(
|
||||
() => window.host.appSchema?.utils,
|
||||
(data) => {
|
||||
setUtils(data)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const dataSourceWatchCanceler = window.host.watch(
|
||||
() => window.host.appSchema?.dataSource,
|
||||
(data) => {
|
||||
setDataSourceMap(data)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const globalStateWatchCanceler = window.host.watch(
|
||||
() => window.host.appSchema?.globalState,
|
||||
(data) => {
|
||||
setGlobalState(data)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
window.host.unsubscribe({
|
||||
topic: 'schemaChange',
|
||||
subscriber: 'canvasRenderer'
|
||||
})
|
||||
|
||||
window.host.unsubscribe({
|
||||
topic: 'schemaImport',
|
||||
subscriber: 'canvasRenderer'
|
||||
})
|
||||
|
||||
utilsWatchCanceler()
|
||||
dataSourceWatchCanceler()
|
||||
globalStateWatchCanceler()
|
||||
})
|
||||
}
|
||||
|
||||
let schema = activeSchema
|
||||
let setCurrentSchema
|
||||
let setCurrentMethod = setMethods
|
||||
if (pageContext.pageId && !props.active && !props.entry) {
|
||||
// 注意顶层使用activeSchema和对应的api
|
||||
const {
|
||||
schema: inActiveSchema,
|
||||
setSchema: setInactiveSchema,
|
||||
setMethods: setInactiveMethods
|
||||
} = useSchema(pageContext, {
|
||||
const { schema: inActiveSchema, setSchema: setInactiveSchema } = useSchema(pageContext, {
|
||||
utils,
|
||||
bridge,
|
||||
stores,
|
||||
|
|
@ -181,30 +220,10 @@ export default defineComponent({
|
|||
})
|
||||
schema = inActiveSchema
|
||||
setCurrentSchema = setInactiveSchema
|
||||
setCurrentMethod = setInactiveMethods
|
||||
}
|
||||
|
||||
provide('rootSchema', schema)
|
||||
|
||||
const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength })
|
||||
watch(
|
||||
() => schema?.children?.length,
|
||||
(length) => {
|
||||
post(length)
|
||||
}
|
||||
)
|
||||
|
||||
// 这里监听schema.methods,为了保证methods上下文环境始终为最新
|
||||
watch(
|
||||
() => schema.methods,
|
||||
(value) => {
|
||||
setCurrentMethod(value, true)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
if (!props.entry) {
|
||||
watch(
|
||||
[() => props.active, () => props.renderSchema],
|
||||
|
|
|
|||
|
|
@ -1,16 +1,7 @@
|
|||
import { reset } from '../data-utils'
|
||||
|
||||
export function useBridge() {
|
||||
const bridge = {}
|
||||
const setBridge = (data, clear = false) => {
|
||||
clear && reset(bridge)
|
||||
Object.assign(bridge, data)
|
||||
}
|
||||
|
||||
const getBridge = () => bridge
|
||||
return {
|
||||
bridge,
|
||||
setBridge,
|
||||
getBridge
|
||||
bridge
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@ const Func = Function
|
|||
|
||||
export function useGlobalState() {
|
||||
const globalState = ref([])
|
||||
const getGlobalState = () => {
|
||||
return globalState.value
|
||||
}
|
||||
|
||||
const setGlobalState = (data = []) => {
|
||||
globalState.value = data
|
||||
|
|
@ -28,7 +25,6 @@ export function useGlobalState() {
|
|||
})
|
||||
return {
|
||||
globalState,
|
||||
getGlobalState,
|
||||
setGlobalState,
|
||||
stores
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,20 @@ export function useUtils(context: Record<string, any>) {
|
|||
const utils: Record<string, Function | any> = {}
|
||||
const getUtils = () => utils
|
||||
|
||||
const setUtils = (data: Array<IUtil>, clear = false, isForceRefresh = false) => {
|
||||
if (clear) {
|
||||
reset(utils)
|
||||
const setUtils = (data: Array<IUtil>) => {
|
||||
if (!Array.isArray(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 筛选出来已经被删除的 key
|
||||
const newKeys = new Set(data.map(({ name }) => name))
|
||||
const currentKeys = Object.keys(utils)
|
||||
const deletedUtilsKeys = currentKeys.filter((item) => !newKeys.has(item))
|
||||
|
||||
for (const key of deletedUtilsKeys) {
|
||||
delete utils[key]
|
||||
}
|
||||
|
||||
const utilsCollection = {}
|
||||
// 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具
|
||||
data?.forEach((item) => {
|
||||
|
|
@ -44,31 +54,13 @@ export function useUtils(context: Record<string, any>) {
|
|||
})
|
||||
Object.assign(utils, utilsCollection)
|
||||
|
||||
// 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布
|
||||
if (isForceRefresh) {
|
||||
refreshKey.value++
|
||||
}
|
||||
}
|
||||
|
||||
const updateUtils = (data: Array<IUtil>) => {
|
||||
setUtils(data, false, true)
|
||||
}
|
||||
|
||||
const deleteUtils = (data: Array<IUtil>) => {
|
||||
data?.forEach((item) => {
|
||||
if (utils[item.name]) {
|
||||
delete utils[item.name]
|
||||
}
|
||||
})
|
||||
setUtils([], false, true)
|
||||
refreshKey.value++
|
||||
}
|
||||
|
||||
return {
|
||||
refreshKey,
|
||||
utils,
|
||||
getUtils,
|
||||
setUtils,
|
||||
updateUtils,
|
||||
deleteUtils
|
||||
setUtils
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
*/
|
||||
|
||||
import { getController } from '../render'
|
||||
import { api } from '../RenderMain'
|
||||
import { useModal } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
const NAME_PREFIX = {
|
||||
loop: 'loop',
|
||||
|
|
@ -64,7 +62,6 @@ const removeState = (pageSchema, variableName) => {
|
|||
}
|
||||
|
||||
const setStateWithSourceRef = (pageSchema, variableName, sourceRef, data) => {
|
||||
api.setState({ [variableName]: data })
|
||||
pageSchema.state[variableName] = data
|
||||
|
||||
if (sourceRef.value.data?.option?.isSync) {
|
||||
|
|
@ -104,7 +101,7 @@ const defaultHandlerTemplate = ({ node, sourceRef, schemaId, pageSchema }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const generateAssginColumns = (newColumns, oldColumns) => {
|
||||
const generateAssignColumns = (newColumns, oldColumns) => {
|
||||
newColumns.forEach((item) => {
|
||||
const targetColumn = oldColumns.find((value) => value.field === item.field)
|
||||
if (targetColumn) {
|
||||
|
|
@ -114,22 +111,38 @@ const generateAssginColumns = (newColumns, oldColumns) => {
|
|||
return newColumns
|
||||
}
|
||||
|
||||
const askShouldImportData = ({ node, sourceRef }) => {
|
||||
useModal().confirm({
|
||||
message: '检测到表格存在配置的数据,是否需要引入?',
|
||||
exec() {
|
||||
const sourceColums = sourceRef.value?.data?.columns?.map(({ title, field }) => ({ title, field })) || []
|
||||
// 这里需要找到对应列,然后进行列合并
|
||||
node.props.columns = generateAssginColumns(sourceColums, node.props.columns)
|
||||
},
|
||||
cancel() {
|
||||
node.props.columns = [...(sourceRef.value.data?.columns || [])]
|
||||
}
|
||||
})
|
||||
const askShouldImportData = ({ node, sourceRef, updateKey }) => {
|
||||
const { publish } = getController().useMessage()
|
||||
|
||||
getController()
|
||||
.useModal()
|
||||
.confirm({
|
||||
message: '检测到表格存在配置的数据,是否需要引入?',
|
||||
exec() {
|
||||
try {
|
||||
const sourceColumns = sourceRef.value?.data?.columns?.map(({ title, field }) => ({ title, field })) || []
|
||||
// 这里需要找到对应列,然后进行列合并
|
||||
node.props.columns = generateAssignColumns(sourceColumns, node.props.columns)
|
||||
|
||||
publish({ topic: 'schemaChange', data: {} })
|
||||
updateKey.value++
|
||||
} catch (error) {
|
||||
getController().useNotify({
|
||||
type: 'error',
|
||||
message: '引入配置数据失败'
|
||||
})
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
node.props.columns = [...(sourceRef.value.data?.columns || [])]
|
||||
|
||||
publish({ topic: 'schemaChange', data: {} })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName }) => {
|
||||
if (!node || !node.props) {
|
||||
const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName, updateKey }) => {
|
||||
if (!node || !node.props || !sourceName) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +150,7 @@ const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName
|
|||
delete node?.props?.data
|
||||
|
||||
if (node.props.columns.length) {
|
||||
askShouldImportData({ node, sourceRef })
|
||||
askShouldImportData({ node, sourceRef, updateKey })
|
||||
} else {
|
||||
node.props.columns = [...(sourceRef.value.data?.columns || [])]
|
||||
}
|
||||
|
|
@ -176,7 +189,7 @@ this.dataSourceMap.${sourceName}.load().then((res) => {
|
|||
}
|
||||
|
||||
const extraHandlerMap = {
|
||||
TinyGrid: ({ node, sourceRef, schemaId, pageSchema }) => {
|
||||
TinyGrid: ({ node, sourceRef, schemaId, pageSchema, updateKey }) => {
|
||||
const sourceName = sourceRef.value?.name
|
||||
const methodName = `${NAME_PREFIX.table}${schemaId}`
|
||||
|
||||
|
|
@ -185,7 +198,7 @@ const extraHandlerMap = {
|
|||
value: `{ api: this.${methodName} }`
|
||||
}
|
||||
|
||||
const updateNode = () => updateNodeHandler({ node, sourceRef, pageSchema, sourceName, methodName })
|
||||
const updateNode = () => updateNodeHandler({ node, sourceRef, pageSchema, sourceName, methodName, updateKey })
|
||||
|
||||
const clearBindVar = () => {
|
||||
// 当数据源组件children字段为空时,及时清空创建的methods
|
||||
|
|
@ -272,7 +285,7 @@ const extraHandlerMap = {
|
|||
}
|
||||
}
|
||||
|
||||
export const getHandler = ({ node, sourceRef, schemaId, pageSchema }) =>
|
||||
export const getHandler = ({ node, sourceRef, schemaId, pageSchema, updateKey }) =>
|
||||
extraHandlerMap[node.componentName]
|
||||
? extraHandlerMap[node.componentName]({ node, sourceRef, schemaId, pageSchema })
|
||||
? extraHandlerMap[node.componentName]({ node, sourceRef, schemaId, pageSchema, updateKey })
|
||||
: defaultHandlerTemplate({ node, sourceRef, schemaId, pageSchema })
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
<template>
|
||||
<component :is="tag" v-bind="$attrs">
|
||||
<component :is="tag" v-bind="$attrs" :key="updateKey">
|
||||
<slot>
|
||||
<canvas-placeholder>{{ source?.name || '未选择数据源' }}</canvas-placeholder>
|
||||
<canvas-placeholder></canvas-placeholder>
|
||||
</slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, computed, inject } from 'vue'
|
||||
import { getController } from '../render'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import CanvasPlaceholder from './CanvasPlaceholder.vue'
|
||||
|
||||
import { getHandler } from './CanvasCollection.js'
|
||||
import { getController } from '../render'
|
||||
import { getHandler } from './CanvasCollection'
|
||||
|
||||
export const fetchDataSourceDetail = (dataSourceId) =>
|
||||
getController().request.get(`/app-center/api/sources/detail/${dataSourceId}`) // TODO: 强行耦合了
|
||||
|
|
@ -21,6 +20,7 @@ export default {
|
|||
CanvasPlaceholder
|
||||
},
|
||||
props: {
|
||||
// TODO: 这里实际上没有提供配置的入口,可以考虑删除了(出码也没有定制)
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
|
|
@ -33,7 +33,8 @@ export default {
|
|||
},
|
||||
setup(props) {
|
||||
const source = ref(null)
|
||||
const pageSchema = inject('rootSchema')
|
||||
// 这里 key 的作用是:触发组件更新,这样就能触发 tiny-grid 表格组件重新触发 fetchData 方法,进而刷新数据显示到画布
|
||||
const updateKey = ref(0)
|
||||
|
||||
if (props.dataSource) {
|
||||
fetchDataSourceDetail(props.dataSource).then((res) => {
|
||||
|
|
@ -42,41 +43,45 @@ export default {
|
|||
}
|
||||
|
||||
let handler
|
||||
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
async (value) => {
|
||||
if (value) {
|
||||
source.value = await fetchDataSourceDetail(value)
|
||||
if (props.schema?.children[0]) {
|
||||
const { getSchema, getNode } = window.host.schemaUtils
|
||||
const node = getNode(props.schema?.children?.[0]?.id)
|
||||
|
||||
if (node) {
|
||||
handler = getHandler({
|
||||
sourceRef: source,
|
||||
node: props.schema?.children[0],
|
||||
node,
|
||||
schemaId: props.schema.id,
|
||||
pageSchema
|
||||
pageSchema: getSchema(),
|
||||
updateKey
|
||||
})
|
||||
}
|
||||
handler?.updateNode()
|
||||
updateKey.value++
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isEmpty = computed(() => {
|
||||
const { children } = props.schema || {}
|
||||
|
||||
return Array.isArray(children) ? !children.length : !children
|
||||
})
|
||||
|
||||
watch(
|
||||
() => isEmpty.value,
|
||||
(value) => {
|
||||
const { getSchema, getNode } = window.host.schemaUtils
|
||||
const pageSchema = getSchema()
|
||||
|
||||
if (value) {
|
||||
// 清除自动创建的state,method与setup逻辑
|
||||
if (handler) {
|
||||
handler.clearBindVar()
|
||||
} else {
|
||||
const schemaId = props.schema?.id
|
||||
|
||||
// 当页面初始化时handler是不存在的,所以需要通过数据源的schemaId(唯一性),去删除对应的方法
|
||||
Object.keys(pageSchema.methods || {})?.some((item) => {
|
||||
if (item.includes(schemaId)) {
|
||||
|
|
@ -87,20 +92,26 @@ export default {
|
|||
})
|
||||
}
|
||||
} else {
|
||||
if (props.schema?.children[0]) {
|
||||
const node = getNode(props.schema?.children?.[0]?.id)
|
||||
if (node) {
|
||||
handler = getHandler({
|
||||
sourceRef: source,
|
||||
node: props.schema?.children[0],
|
||||
node,
|
||||
schemaId: props.schema.id,
|
||||
pageSchema
|
||||
pageSchema,
|
||||
updateKey
|
||||
})
|
||||
handler.updateNode()
|
||||
}
|
||||
}
|
||||
|
||||
const { publish } = getController().useMessage()
|
||||
|
||||
publish({ topic: 'schemaChange', data: {} })
|
||||
updateKey.value++
|
||||
}
|
||||
)
|
||||
|
||||
return { source }
|
||||
return { source, updateKey }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
"content": [
|
||||
{
|
||||
"property": "name",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"label": {
|
||||
"text": {
|
||||
"zh_CN": "插槽名称"
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
},
|
||||
{
|
||||
"property": "params",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"label": {
|
||||
"text": {
|
||||
|
|
@ -246,7 +246,7 @@
|
|||
"content": [
|
||||
{
|
||||
"property": "condition",
|
||||
"type": "Boolean",
|
||||
"type": "boolean",
|
||||
"defaultValue": true,
|
||||
"label": {
|
||||
"text": {
|
||||
|
|
@ -262,7 +262,7 @@
|
|||
},
|
||||
{
|
||||
"property": "style",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"label": {
|
||||
"text": {
|
||||
|
|
@ -278,7 +278,7 @@
|
|||
},
|
||||
{
|
||||
"property": "dataSource",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"bindState": false,
|
||||
"label": {
|
||||
|
|
@ -332,7 +332,7 @@
|
|||
"content": [
|
||||
{
|
||||
"property": "text",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。",
|
||||
"label": {
|
||||
"text": {
|
||||
|
|
@ -404,7 +404,7 @@
|
|||
"content": [
|
||||
{
|
||||
"property": "name",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "IconDel",
|
||||
"bindState": true,
|
||||
"label": {
|
||||
|
|
@ -474,7 +474,7 @@
|
|||
"content": [
|
||||
{
|
||||
"property": "src",
|
||||
"type": "String",
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"bindState": true,
|
||||
"label": {
|
||||
|
|
@ -557,6 +557,7 @@
|
|||
"schema": {
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"style": "display: inline-block;",
|
||||
"text": "TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import renderer from '../render'
|
|||
function defaultRenderer(schema, refreshKey, entry, active, isPage = true) {
|
||||
// 渲染画布增加根节点,与出码和预览保持一致
|
||||
const rootChildrenSchema = {
|
||||
id: 0,
|
||||
componentName: 'div',
|
||||
// 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码
|
||||
props: { ...schema.props, 'data-id': 'root-container', 'data-page-active': active },
|
||||
|
|
@ -24,6 +25,7 @@ function defaultRenderer(schema, refreshKey, entry, active, isPage = true) {
|
|||
}
|
||||
|
||||
return h(
|
||||
// TODO: 这里顶层的 i18n-host 在不支持 webComponent 的区块之后,应该也不需要webComponent 的 i18n provider 了
|
||||
'tiny-i18n-host',
|
||||
{
|
||||
locale: 'zh_CN',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { transformSync } from '@babel/core'
|
|||
import i18nHost from '@opentiny/tiny-engine-i18n-host'
|
||||
|
||||
import { globalNotify } from '../canvas-function'
|
||||
import { collectionMethodsMap, customElements, getComponent, getIcon } from '../material-function'
|
||||
import { collectionMethodsMap, getComponent, getIcon } from '../material-function'
|
||||
import { newFn } from '../data-utils'
|
||||
import { renderDefault } from '../render'
|
||||
|
||||
|
|
@ -61,8 +61,7 @@ const transformJSX = (code) => {
|
|||
[
|
||||
babelPluginJSX,
|
||||
{
|
||||
pragma: 'h',
|
||||
isCustomElement: (name) => customElements[name]
|
||||
pragma: 'h'
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -152,43 +151,39 @@ const parseJSXFunction = (data, _scope, ctx) => {
|
|||
export const generateFn = (innerFn, context?) => {
|
||||
return (...args) => {
|
||||
// 如果有数据源标识,则表格的fetchData返回数据源的静态数据
|
||||
const sourceId = collectionMethodsMap[innerFn.realName || innerFn.name]
|
||||
if (sourceId) {
|
||||
return innerFn.call(context, ...args)
|
||||
} else {
|
||||
let result = null
|
||||
|
||||
// 这里是为了兼容用户写法报错导致画布异常,但无法捕获promise内部的异常
|
||||
try {
|
||||
result = innerFn.call(context, ...args)
|
||||
} catch (error) {
|
||||
globalNotify({
|
||||
type: 'warning',
|
||||
title: `函数:${innerFn.name}执行报错`,
|
||||
message: error?.message || `函数:${innerFn.name}执行报错,请检查语法`
|
||||
})
|
||||
}
|
||||
let result = null
|
||||
|
||||
// 这里注意如果innerFn返回的是一个promise则需要捕获异常,重新返回默认一条空数据
|
||||
if (result.then) {
|
||||
result = new Promise((resolve) => {
|
||||
result.then(resolve).catch((error) => {
|
||||
globalNotify({
|
||||
type: 'warning',
|
||||
title: '异步函数执行报错',
|
||||
message: error?.message || '异步函数执行报错,请检查语法'
|
||||
})
|
||||
// 这里需要至少返回一条空数据,方便用户使用表格默认插槽
|
||||
resolve({
|
||||
result: [{}],
|
||||
page: { total: 1 }
|
||||
})
|
||||
// 这里是为了兼容用户写法报错导致画布异常,但无法捕获promise内部的异常
|
||||
try {
|
||||
result = innerFn.call(context, ...args)
|
||||
} catch (error) {
|
||||
globalNotify({
|
||||
type: 'warning',
|
||||
title: `函数:${innerFn.name}执行报错`,
|
||||
message: error?.message || `函数:${innerFn.name}执行报错,请检查语法`
|
||||
})
|
||||
}
|
||||
|
||||
// 这里注意如果innerFn返回的是一个promise则需要捕获异常,重新返回默认一条空数据
|
||||
if (result.then) {
|
||||
result = new Promise((resolve) => {
|
||||
result.then(resolve).catch((error) => {
|
||||
globalNotify({
|
||||
type: 'warning',
|
||||
title: '异步函数执行报错',
|
||||
message: error?.message || '异步函数执行报错,请检查语法'
|
||||
})
|
||||
// 这里需要至少返回一条空数据,方便用户使用表格默认插槽
|
||||
resolve({
|
||||
result: [{}],
|
||||
page: { total: 1 }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
const parseJSFunction = (data, _scope, ctx) => {
|
||||
|
|
|
|||
|
|
@ -10,3 +10,10 @@ export const newFn = (...argv) => {
|
|||
const Fn = Function
|
||||
return new Fn(...argv)
|
||||
}
|
||||
|
||||
export const getDeletedKeys = (objA, objB) => {
|
||||
const keyA = Object.keys(objA)
|
||||
const keyB = new Set(Object.keys(objB))
|
||||
|
||||
return keyA.filter((item) => !keyB.has(item))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import { getCurrentInstance, nextTick, provide, inject } from 'vue'
|
|||
import { I18nInjectionKey } from 'vue-i18n'
|
||||
import { api } from './RenderMain'
|
||||
import { globalNotify } from './canvas-function'
|
||||
import { collectionMethodsMap } from './material-function'
|
||||
import { generateFn } from './data-function'
|
||||
|
||||
export const lowcodeWrap = (props, context) => {
|
||||
|
|
@ -60,13 +59,13 @@ export const lowcodeWrap = (props, context) => {
|
|||
|
||||
const wrap = (fn) => {
|
||||
if (typeof fn === 'function') {
|
||||
const fnName = fn.name
|
||||
if (fn.toString().includes('return this')) {
|
||||
const fnString = fn.toString()
|
||||
|
||||
if (fnString.includes('return this')) {
|
||||
return () => global
|
||||
} else if (fnName && collectionMethodsMap[fnName.slice(0, -1)]) {
|
||||
// 这里区块打包的时候会在方法名称后面多加一个字符串,所以此处需要截取下函数名称
|
||||
fn.realName = fnName.slice(0, -1)
|
||||
return generateFn(fn)
|
||||
} else if (/this\.dataSourceMap\.[0-9a-zA-Z_]+\.load\(\)/.test(fnString)) {
|
||||
const renderContext = (inject('pageContext') as Ref<any>).value
|
||||
return generateFn(fn, renderContext)
|
||||
} else if (fn.name === 'setter' || fn.name === 'getter') {
|
||||
// 这里需要保证在消费区块时,区块中的访问器函数可以正常执行
|
||||
return (...args) => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { h } from 'vue'
|
||||
import { isHTMLTag, hyphenate } from '@vue/shared'
|
||||
import { h, defineAsyncComponent } from 'vue'
|
||||
import { isHTMLTag } from '@vue/shared'
|
||||
import * as TinyVueIcon from '@opentiny/vue-icon'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
import { CanvasRow, CanvasCol, CanvasRowColContainer } from '@opentiny/tiny-engine-builtin-component'
|
||||
|
||||
import {
|
||||
CanvasRow,
|
||||
CanvasCol,
|
||||
CanvasRowColContainer,
|
||||
CanvasFlexBox,
|
||||
CanvasSection
|
||||
} from '@opentiny/tiny-engine-builtin-component'
|
||||
import {
|
||||
CanvasBox,
|
||||
CanvasCollection,
|
||||
|
|
@ -15,9 +21,8 @@ import {
|
|||
CanvasRouterLink
|
||||
} from '../builtin'
|
||||
import { getController } from '../canvas-function/controller'
|
||||
import { generateCollection } from './support-collection'
|
||||
import BlockLoadError from '../BlockLoadError.vue'
|
||||
|
||||
export const customElements = {}
|
||||
export const Mapper = {
|
||||
Icon: CanvasIcon,
|
||||
Text: CanvasText,
|
||||
|
|
@ -27,6 +32,8 @@ export const Mapper = {
|
|||
slot: CanvasSlot,
|
||||
Template: CanvasBox,
|
||||
Img: CanvasImg,
|
||||
CanvasSection,
|
||||
CanvasFlexBox,
|
||||
CanvasRow,
|
||||
CanvasCol,
|
||||
CanvasRowColContainer,
|
||||
|
|
@ -42,91 +49,62 @@ const getBlock = (name) => {
|
|||
return window.blocks?.[name]
|
||||
}
|
||||
|
||||
const { hyphenateRE } = utils
|
||||
const getPlainProps = (object: Record<string, any> = {}) => {
|
||||
const { slot, ...rest } = object
|
||||
const props = {}
|
||||
const blockComponentsBlobUrlMap = new Map<string, string>()
|
||||
|
||||
if (slot) {
|
||||
rest.slot = slot.name || slot
|
||||
}
|
||||
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
let renderKey = key
|
||||
|
||||
// html 标签属性会忽略大小写,所以传递包含大写的 props 需要转换为 kebab 形式的 props
|
||||
if (!/on[A-Z]/.test(renderKey) && hyphenateRE.test(renderKey)) {
|
||||
renderKey = hyphenate(renderKey)
|
||||
// TODO: 这里的全局 getter 方法名,可以做成配置化
|
||||
const loadBlockComponent = async (name: string) => {
|
||||
try {
|
||||
if (blockComponentsBlobUrlMap.has(name)) {
|
||||
return import(/* @vite-ignore */ blockComponentsBlobUrlMap.get(name))
|
||||
}
|
||||
|
||||
if (['boolean', 'string', 'number'].includes(typeof value)) {
|
||||
props[renderKey] = value
|
||||
} else {
|
||||
// 如果传给webcomponent标签的是对象或者数组需要使用.prop修饰符,转化成h函数就是如下写法
|
||||
props[`.${renderKey}`] = value
|
||||
}
|
||||
})
|
||||
return props
|
||||
}
|
||||
const blocksBlob = (await getController().getBlockByName(name)) as Array<{ blobURL: string; style: string }>
|
||||
|
||||
const generateBlockContent = (schema) => {
|
||||
if (schema?.componentName === 'Collection') {
|
||||
generateCollection(schema)
|
||||
}
|
||||
if (Array.isArray(schema?.children)) {
|
||||
schema.children.forEach((item) => {
|
||||
generateBlockContent(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
const registerBlock = (componentName) => {
|
||||
getController()
|
||||
.registerBlock?.(componentName)
|
||||
.then((res) => {
|
||||
const blockSchema = res.content
|
||||
for (const [fileName, value] of Object.entries(blocksBlob)) {
|
||||
blockComponentsBlobUrlMap.set(fileName, value.blobURL)
|
||||
|
||||
// 拿到区块数据,建立区块中数据源的映射关系
|
||||
generateBlockContent(blockSchema)
|
||||
|
||||
// 如果区块的根节点有百分比高度,则需要特殊处理,把高度百分比传递下去,适配大屏应用
|
||||
if (/height:\s*?[\d|.]+?%/.test(blockSchema?.props?.style)) {
|
||||
const blockDoms = document.querySelectorAll<HTMLElement>(hyphenate(componentName))
|
||||
blockDoms.forEach((item) => {
|
||||
item.style.height = '100%'
|
||||
})
|
||||
if (!value.style) {
|
||||
continue
|
||||
}
|
||||
})
|
||||
|
||||
// 注册 CSS,以区块为维度
|
||||
const stylesheet = document.querySelector(`#${fileName}`)
|
||||
|
||||
if (stylesheet) {
|
||||
stylesheet.innerHTML = value.style
|
||||
} else {
|
||||
const newStylesheet = document.createElement('style')
|
||||
newStylesheet.innerHTML = value.style
|
||||
newStylesheet.setAttribute('id', fileName)
|
||||
document.head.appendChild(newStylesheet)
|
||||
}
|
||||
}
|
||||
|
||||
return import(/* @vite-ignore */ blockComponentsBlobUrlMap.get(name))
|
||||
} catch (error) {
|
||||
// 加载错误提示
|
||||
return h(BlockLoadError, { name })
|
||||
}
|
||||
}
|
||||
|
||||
export const wrapCustomElement = (componentName) => {
|
||||
const material = getController().getMaterial(componentName)
|
||||
window.loadBlockComponent = loadBlockComponent
|
||||
|
||||
if (!Object.keys(material).length) {
|
||||
registerBlock(componentName)
|
||||
}
|
||||
const getBlockComponent = (name) => {
|
||||
return defineAsyncComponent(() => loadBlockComponent(name))
|
||||
}
|
||||
|
||||
customElements[componentName] = {
|
||||
name: componentName + '.ce',
|
||||
render() {
|
||||
return h(
|
||||
hyphenate(componentName),
|
||||
window.parent.TinyGlobalConfig.dslMode === 'Vue' ? getPlainProps(this.$attrs) : this.$attrs,
|
||||
this.$slots.default?.()
|
||||
)
|
||||
}
|
||||
}
|
||||
// 移除区块缓存
|
||||
export const removeBlockCompsCache = () => {
|
||||
blockComponentsBlobUrlMap.forEach((_, fileName) => {
|
||||
const stylesheet = document.querySelector(`#${fileName}`)
|
||||
stylesheet?.remove?.()
|
||||
})
|
||||
|
||||
return customElements[componentName]
|
||||
blockComponentsBlobUrlMap.clear()
|
||||
}
|
||||
|
||||
export const getIcon = (name) => TinyVueIcon?.[name]?.() || ''
|
||||
|
||||
export const getComponent = (name) => {
|
||||
return (
|
||||
Mapper[name] ||
|
||||
getNative(name) ||
|
||||
getBlock(name) ||
|
||||
customElements[name] ||
|
||||
(isHTMLTag(name) ? name : wrapCustomElement(name))
|
||||
)
|
||||
return Mapper[name] || getNative(name) || getBlock(name) || (isHTMLTag(name) ? name : getBlockComponent(name))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
|
||||
import { shallowReactive } from 'vue'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
|
||||
export function useContext() {
|
||||
const context = shallowReactive({})
|
||||
|
|
@ -29,38 +28,6 @@ export function useContext() {
|
|||
}
|
||||
}
|
||||
|
||||
export function useNodes() {
|
||||
const nodes = {}
|
||||
|
||||
const setNode = (schema, parent) => {
|
||||
schema.id = schema.id || utils.guid()
|
||||
nodes[schema.id] = { node: schema, parent }
|
||||
}
|
||||
|
||||
const getNode = (id, parent) => {
|
||||
return parent ? nodes[id] : nodes[id].node
|
||||
}
|
||||
|
||||
const delNode = (id) => delete nodes[id]
|
||||
|
||||
const clearNodes = () => {
|
||||
Object.keys(nodes).forEach(delNode)
|
||||
}
|
||||
const getRoot = (id) => {
|
||||
const { parent } = getNode(id, true)
|
||||
|
||||
return parent?.id ? getRoot(parent.id) : parent
|
||||
}
|
||||
|
||||
return {
|
||||
setNode,
|
||||
getNode,
|
||||
delNode,
|
||||
clearNodes,
|
||||
getRoot
|
||||
}
|
||||
}
|
||||
|
||||
export function useCondition() {
|
||||
// 从大纲树控制隐藏
|
||||
const conditions = shallowReactive({})
|
||||
|
|
@ -107,13 +74,11 @@ export function useCssScopeId() {
|
|||
}
|
||||
export function usePageContext() {
|
||||
const contextExpose = useContext()
|
||||
const nodeExpose = useNodes()
|
||||
const conditionExpose = useCondition()
|
||||
const contextParentExpose = usePageContextParent()
|
||||
const cssCopeIdExpose = useCssScopeId()
|
||||
return {
|
||||
...contextExpose,
|
||||
...nodeExpose,
|
||||
...conditionExpose,
|
||||
...contextParentExpose,
|
||||
...cssCopeIdExpose,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { initStyle } from '../material-function/page-getter'
|
||||
import { getController } from '../render'
|
||||
export function setPagecss(css = '', pageId?) {
|
||||
export function setPageCss(css = '', pageId?) {
|
||||
const cssPageId = pageId ?? getController().getBaseInfo().pageId
|
||||
const key = `data-te-page-${cssPageId}`
|
||||
initStyle(key, css)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ export function useProps(generateAccessor: ReturnType<typeof useAccessorMap>['ge
|
|||
Object.assign(props, data)
|
||||
}
|
||||
|
||||
const getProps = () => props
|
||||
|
||||
const initProps = (properties = []) => {
|
||||
const props: Record<string, any> = {}
|
||||
const accessorFunctions: Array<ReturnType<typeof generateAccessor>> = []
|
||||
|
|
@ -38,7 +36,6 @@ export function useProps(generateAccessor: ReturnType<typeof useAccessorMap>['ge
|
|||
return {
|
||||
props,
|
||||
initProps,
|
||||
getProps,
|
||||
setProps
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { reactive, watchEffect } from 'vue'
|
||||
import { reactive, watchEffect, watch } from 'vue'
|
||||
import { reset } from '../data-utils'
|
||||
import { useAccessorMap } from './accessor-map'
|
||||
import { useState } from './state'
|
||||
|
|
@ -6,30 +6,50 @@ import { useProps } from './props'
|
|||
import { useMethods } from './methods'
|
||||
import { nextTick } from 'vue'
|
||||
import { globalNotify } from '../canvas-function'
|
||||
import { setPagecss } from './css'
|
||||
import { setPageCss } from './css'
|
||||
import type { IPageSchema, ISchemaChildrenItem } from '@opentiny/tiny-engine-dsl-vue'
|
||||
export { IPageSchema, ISchemaChildrenItem }
|
||||
|
||||
export function useSchema(
|
||||
{ context: globalContext, setContext, getContext, clearNodes, getNode },
|
||||
{ context: globalContext, setContext, getContext },
|
||||
{ utils, bridge, stores, getDataSourceMap }
|
||||
) {
|
||||
const schema = reactive<Partial<IPageSchema>>({})
|
||||
const { generateAccessor, stateAccessorMap, propsAccessorMap, generateStateAccessors } = useAccessorMap(globalContext)
|
||||
|
||||
const { state, getState, setState, deleteState } = useState(schema, {
|
||||
const { state, setState } = useState(schema, {
|
||||
getContext,
|
||||
generateStateAccessors
|
||||
})
|
||||
|
||||
const { props, initProps, getProps, setProps } = useProps(generateAccessor)
|
||||
const { props, initProps, setProps } = useProps(generateAccessor)
|
||||
|
||||
const { methods, getMethods, setMethods } = useMethods({
|
||||
setContext,
|
||||
getContext
|
||||
})
|
||||
|
||||
const getSchema = () => schema
|
||||
watch(
|
||||
() => schema.state,
|
||||
(value) => {
|
||||
setState(value)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
// 这里监听schema.methods,为了保证methods上下文环境始终为最新
|
||||
watch(
|
||||
() => schema.methods,
|
||||
(value) => {
|
||||
setMethods(value, true)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const setSchema = async (data: IPageSchema, pageId?: string) => {
|
||||
const newSchema = JSON.parse(JSON.stringify(data || schema))
|
||||
reset(schema)
|
||||
|
|
@ -71,9 +91,9 @@ export function useSchema(
|
|||
|
||||
// 这里setState(会触发画布渲染),是因为状态管理里面的变量会用到props、utils、bridge、stores、methods
|
||||
setState(newSchema.state, true)
|
||||
clearNodes()
|
||||
|
||||
await nextTick()
|
||||
setPagecss(data.css, pageId)
|
||||
setPageCss(data.css, pageId)
|
||||
Object.assign(schema, newSchema)
|
||||
|
||||
// 当上下文环境设置完成之后再去处理区块属性访问器的watchEffect
|
||||
|
|
@ -99,7 +119,6 @@ export function useSchema(
|
|||
}
|
||||
return {
|
||||
schema,
|
||||
getSchema,
|
||||
setSchema,
|
||||
...{
|
||||
generateAccessor,
|
||||
|
|
@ -109,14 +128,11 @@ export function useSchema(
|
|||
},
|
||||
...{
|
||||
state,
|
||||
getState,
|
||||
setState,
|
||||
deleteState
|
||||
setState
|
||||
},
|
||||
...{
|
||||
props,
|
||||
initProps,
|
||||
getProps,
|
||||
setProps
|
||||
},
|
||||
...{
|
||||
|
|
@ -125,9 +141,8 @@ export function useSchema(
|
|||
setMethods
|
||||
},
|
||||
...{
|
||||
getContext,
|
||||
getNode
|
||||
getContext
|
||||
},
|
||||
setPagecss
|
||||
setPageCss
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import { shallowReactive } from 'vue'
|
||||
|
||||
import { reset } from '../data-utils'
|
||||
import { getDeletedKeys } from '../data-utils'
|
||||
import { isStateAccessor, parseData } from '../data-function'
|
||||
|
||||
export function useState(schema, { getContext, generateStateAccessors }) {
|
||||
const state = shallowReactive({})
|
||||
const getState = () => state
|
||||
|
||||
const deleteState = (variable: string) => {
|
||||
delete state[variable]
|
||||
}
|
||||
|
||||
const setState = (data, clear = false) => {
|
||||
clear && reset(state)
|
||||
if (!schema.state) {
|
||||
schema.state = data
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const deletedKeys = getDeletedKeys(state, data)
|
||||
|
||||
// 同步删除的 key
|
||||
for (const key of deletedKeys) {
|
||||
delete state[key]
|
||||
}
|
||||
|
||||
Object.assign(state, parseData(data, {}, getContext()) || {})
|
||||
|
|
@ -35,8 +36,6 @@ export function useState(schema, { getContext, generateStateAccessors }) {
|
|||
}
|
||||
return {
|
||||
state,
|
||||
getState,
|
||||
setState,
|
||||
deleteState
|
||||
setState
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { defineComponent, h, inject, provide, Ref } from 'vue'
|
||||
import { defineComponent, h, inject, provide, Ref, Suspense } from 'vue'
|
||||
|
||||
import { NODE_UID as DESIGN_UIDKEY, NODE_TAG as DESIGN_TAGKEY, NODE_LOOP as DESIGN_LOOPID } from '../../common'
|
||||
import { getDesignMode, DESIGN_MODE } from './canvas-function'
|
||||
import { parseCondition, parseData, parseLoopArgs } from './data-function'
|
||||
import { blockSlotDataMap, getComponent, generateCollection, Mapper, configure } from './material-function'
|
||||
import { blockSlotDataMap, getComponent, Mapper, configure } from './material-function'
|
||||
import { getPage } from './material-function/page-getter'
|
||||
import BlockLoading from './BlockLoading.vue'
|
||||
|
||||
export const renderDefault = (children, scope, parent) =>
|
||||
children.map?.((child) =>
|
||||
|
|
@ -34,7 +35,7 @@ const stopEvent = (event) => {
|
|||
return false
|
||||
}
|
||||
|
||||
const generateSlotGroup = (children, isCustomElm, schema) => {
|
||||
const generateSlotGroup = (children, schema) => {
|
||||
const slotGroup = {}
|
||||
|
||||
children.forEach((child) => {
|
||||
|
|
@ -42,7 +43,6 @@ const generateSlotGroup = (children, isCustomElm, schema) => {
|
|||
const slot = child.slot || props?.slot?.name || props?.slot || 'default'
|
||||
const isNotEmptyTemplate = componentName === 'Template' && children.length
|
||||
|
||||
isCustomElm && (child.props.slot = 'slot') // CE下需要给子节点加上slot标识
|
||||
slotGroup[slot] = slotGroup[slot] || {
|
||||
value: [],
|
||||
params,
|
||||
|
|
@ -55,9 +55,9 @@ const generateSlotGroup = (children, isCustomElm, schema) => {
|
|||
return slotGroup
|
||||
}
|
||||
|
||||
const renderSlot = (children, scope, schema, isCustomElm?) => {
|
||||
const renderSlot = (children, scope, schema) => {
|
||||
if (children.some((a) => a.componentName === 'Template')) {
|
||||
const slotGroup = generateSlotGroup(children, isCustomElm, schema)
|
||||
const slotGroup = generateSlotGroup(children, schema)
|
||||
const slots = {}
|
||||
|
||||
Object.keys(slotGroup).forEach((slotName) => {
|
||||
|
|
@ -150,7 +150,7 @@ const injectPlaceHolder = (componentName, children) => {
|
|||
return children
|
||||
}
|
||||
|
||||
const renderGroup = (children, scope, parent, pageContext) => {
|
||||
const renderGroup = (children, scope, pageContext) => {
|
||||
return children.map?.((schema) => {
|
||||
const { componentName, children, loop, loopArgs, condition, id } = schema
|
||||
const loopList = parseData(loop, scope, pageContext.context)
|
||||
|
|
@ -163,8 +163,6 @@ const renderGroup = (children, scope, parent, pageContext) => {
|
|||
loopArgs
|
||||
})
|
||||
|
||||
pageContext.setNode(schema, parent)
|
||||
|
||||
if (pageContext.conditions[id] === false || !parseCondition(condition, mergeScope, pageContext.context)) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -190,20 +188,22 @@ const getChildren = (schema, mergeScope, pageContext) => {
|
|||
|
||||
const component = getComponent(componentName)
|
||||
const isNative = typeof component === 'string'
|
||||
const isCustomElm = customElements[componentName]
|
||||
const isGroup = checkGroup(componentName)
|
||||
|
||||
if (Array.isArray(renderChildren)) {
|
||||
if (isNative || isCustomElm) {
|
||||
return renderDefault(renderChildren, mergeScope, schema)
|
||||
} else {
|
||||
return isGroup
|
||||
? renderGroup(renderChildren, mergeScope, schema, pageContext)
|
||||
: renderSlot(renderChildren, mergeScope, schema, isCustomElm)
|
||||
// children 空的场景,不能返回空数组,因为有部分组件会误以为使用了自定义插槽,从而无法渲染默认插槽内容,比如 TinyTree 组件
|
||||
if (!renderChildren.length) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return parseData(renderChildren, mergeScope, pageContext.context)
|
||||
|
||||
if (isNative) {
|
||||
return renderDefault(renderChildren, mergeScope, schema)
|
||||
}
|
||||
return isGroup
|
||||
? renderGroup(renderChildren, mergeScope, pageContext)
|
||||
: renderSlot(renderChildren, mergeScope, schema)
|
||||
}
|
||||
return parseData(renderChildren, mergeScope, pageContext.context)
|
||||
}
|
||||
function getRenderPageId(currentPageId, isPageStart) {
|
||||
const pagePathFromRoot = (inject('page-ancestors') as Ref<any[]>).value
|
||||
|
|
@ -239,8 +239,6 @@ export const renderer = defineComponent({
|
|||
const { scope, schema, parent, ancestors } = this
|
||||
const { componentName, loop, loopArgs, condition } = schema
|
||||
const pageContext = this.currentPageContext
|
||||
// 处理数据源和表格fetchData的映射关系
|
||||
generateCollection(schema)
|
||||
|
||||
if (!componentName) {
|
||||
return parseData(schema, scope, pageContext.context)
|
||||
|
|
@ -280,18 +278,28 @@ export const renderer = defineComponent({
|
|||
mergeScope = mergeScope ? { ...mergeScope, ...slotData } : slotData
|
||||
}
|
||||
|
||||
// 给每个节点设置schema.id,并缓存起来
|
||||
pageContext.setNode(schema, parent)
|
||||
|
||||
if (pageContext.conditions[schema.id] === false || !parseCondition(condition, mergeScope, pageContext.context)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return h(
|
||||
const Ele = h(
|
||||
component,
|
||||
getBindProps(schema, mergeScope, pageContext.context, pageContext),
|
||||
getChildren(schema, mergeScope, pageContext)
|
||||
)
|
||||
// 区块加上 suspense 渲染,就可以在网络延时的时候显示加载中的字样或者动画,优化体验
|
||||
if (schema.componentType === 'Block') {
|
||||
return h(
|
||||
Suspense,
|
||||
{},
|
||||
{
|
||||
default: () => Ele,
|
||||
fallback: () => h(BlockLoading, { name: componentName })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return Ele
|
||||
}
|
||||
|
||||
return loopList?.length ? loopList.map(renderElement) : renderElement()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import { addScript, addStyle, dynamicImportComponents, updateDependencies } from
|
|||
import TinyI18nHost, { I18nInjectionKey } from '@opentiny/tiny-engine-common/js/i18n'
|
||||
import Main, { api } from './RenderMain'
|
||||
import lowcode from './lowcode'
|
||||
import { supportUmdBlock } from './supportUmdBlock'
|
||||
|
||||
type ITinyI18nHostI18nHost = typeof TinyI18nHost
|
||||
interface IExtendsTinyI18nHost extends ITinyI18nHostI18nHost {
|
||||
|
|
@ -33,8 +32,6 @@ const initRenderContext = () => {
|
|||
window.TinyLowcodeComponent = {}
|
||||
window.TinyComponentLibs = {}
|
||||
|
||||
supportUmdBlock()
|
||||
|
||||
document.addEventListener('updateDependencies', updateDependencies)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
import * as Vue from 'vue'
|
||||
import * as VueI18n from 'vue-i18n'
|
||||
import * as TinyWebcomponentCore from '@opentiny/tiny-engine-webcomponent-core'
|
||||
import * as TinyVueIcon from '@opentiny/vue-icon'
|
||||
import TinyVue from '@opentiny/vue'
|
||||
import TinyI18nHost from '@opentiny/tiny-engine-common/js/i18n'
|
||||
import { camelize, capitalize } from '@vue/shared'
|
||||
import { blockSlotDataMap, getComponent } from './material-function'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Vue: typeof Vue
|
||||
VueI18n: typeof VueI18n
|
||||
TinyVue: typeof TinyVue
|
||||
TinyVueIcon: typeof TinyVueIcon
|
||||
TinyI18nHost: typeof TinyI18nHost
|
||||
TinyWebcomponentCore: typeof TinyWebcomponentCore
|
||||
}
|
||||
}
|
||||
// 和 @opentiny/tiny-engine-block-build 打包umd方式相适配
|
||||
export function supportUmdBlock() {
|
||||
// 不能采用new Proxy代理Vue的方案,在编译后的vue会报错警告,采用一下方案扩展用于注入一些区块加载逻辑
|
||||
window.Vue = {
|
||||
...Vue,
|
||||
resolveComponent(...args) {
|
||||
// 此处先执行vue内部的解析组件的方法,如果可以拿到组件对象则直接返回,反之则去注册区块
|
||||
const component = Vue.resolveComponent(args[0])
|
||||
if (component && typeof component === 'string') {
|
||||
return getComponent(capitalize(camelize(args[0])))
|
||||
} else {
|
||||
return component
|
||||
}
|
||||
},
|
||||
// renderSlot方法第三个参数是作用域插槽传递的数据,格式{ data: vue.unref(state).componentData }
|
||||
renderSlot(...args) {
|
||||
// 获取当前vue的实例
|
||||
const instance = Vue.getCurrentInstance()
|
||||
|
||||
// 获取当前区块名称
|
||||
const blockName = instance.attrs.dataTag as string
|
||||
|
||||
const [, slotName, slotData] = args
|
||||
|
||||
// 如果是作用域插槽,则获取作用域插槽传递过来的参数
|
||||
if (slotData) {
|
||||
if (blockSlotDataMap[blockName]) {
|
||||
blockSlotDataMap[blockName][slotName] = slotData
|
||||
} else {
|
||||
blockSlotDataMap[blockName] = { [slotName]: slotData }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vue源码中的renderSlot会忽略default插槽的名称,所以这里必须手动添加args第三个参数的name值
|
||||
* vue源码如右所示:if (name !== 'default') props.name = name; return createVNode('slot', props, fallback && fallback());
|
||||
**/
|
||||
if (slotName === 'default') {
|
||||
args[2] = args[2] || {}
|
||||
args[2].name = slotName
|
||||
}
|
||||
|
||||
return Vue.renderSlot(...args)
|
||||
}
|
||||
}
|
||||
|
||||
window.VueI18n = VueI18n
|
||||
window.TinyVue = TinyVue
|
||||
window.TinyVueIcon = TinyVueIcon
|
||||
window.TinyWebcomponentCore = TinyWebcomponentCore
|
||||
window.TinyI18nHost = TinyI18nHost
|
||||
}
|
||||
|
|
@ -8,5 +8,7 @@ export declare global {
|
|||
scripts: Array<any>
|
||||
}
|
||||
TinyGlobalConfig: Record<string, any>
|
||||
loadBlockComponent: (blockName: string) => Promise<any>
|
||||
host: any
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,31 +4,29 @@
|
|||
<tiny-select
|
||||
ref="selectRef"
|
||||
v-model="selectValue"
|
||||
placeholder="请根据显示值搜索"
|
||||
placeholder="请选择多语言文案"
|
||||
filterable
|
||||
is-drop-inherit-width
|
||||
:filter-method="filterMethod"
|
||||
@change="selectI18n"
|
||||
>
|
||||
<tiny-option v-for="item in langData" :key="item.key" :label="item[currentLang]" :value="item.key">
|
||||
<div style="display: flex">
|
||||
<span style="flex: 1">{{ item.key }}</span>
|
||||
<span style="flex: 1">{{ item[currentLang] }}</span>
|
||||
</div>
|
||||
<tiny-option v-for="item in langData" :key="item.key" :label="item.key + item[currentLang]" :value="item.key">
|
||||
</tiny-option>
|
||||
</tiny-select>
|
||||
<div v-if="paramsForm.length" class="params-form">
|
||||
<label>国际化参数配置</label>
|
||||
<div class="label">国际化参数配置</div>
|
||||
<div v-for="param in paramsForm" :key="param.name" class="params-item">
|
||||
<label>{{ param.name }}</label>
|
||||
<tiny-input v-model="param.value" @update:modelValue="paramsChange"></tiny-input>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="suffix">
|
||||
<tiny-button type="info" @click="openCreateForm">
|
||||
<icon-plus class="icon-plus"></icon-plus>
|
||||
<span>创建新的多语言文案</span>
|
||||
</tiny-button>
|
||||
<tiny-button v-if="isBind" type="info" @click="unbindI18n"> 解除关联 </tiny-button>
|
||||
<div class="bottom-buttons">
|
||||
<tiny-button v-if="isBind" @click="unbindI18n">解除关联</tiny-button>
|
||||
<tiny-button type="primary" @click="openCreateForm">
|
||||
<span>创建新的多语言文案</span>
|
||||
</tiny-button>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-show="showEditItem" class="addNewLanguage">
|
||||
|
|
@ -43,9 +41,9 @@
|
|||
<input v-model="editForm[locale.lang]" class="tiny-input__inner" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-btns">
|
||||
<tiny-button type="info" @click="activeI18n">国际化管理 </tiny-button>
|
||||
<tiny-button type="info" @click="addBindI18n">添加并关联 </tiny-button>
|
||||
<div class="bottom-buttons">
|
||||
<tiny-button @click="activeI18n">国际化管理</tiny-button>
|
||||
<tiny-button type="primary" @click="addBindI18n">添加并关联</tiny-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -57,15 +55,13 @@ import { useLayout, useTranslate } from '@opentiny/tiny-engine-meta-register'
|
|||
import { PROP_DATA_TYPE } from '../js/constants'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
import { Select, Option, Button, Input } from '@opentiny/vue'
|
||||
import { iconPlus } from '@opentiny/vue-icon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinySelect: Select,
|
||||
TinyOption: Option,
|
||||
TinyButton: Button,
|
||||
TinyInput: Input,
|
||||
IconPlus: iconPlus()
|
||||
TinyInput: Input
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
|
|
@ -169,10 +165,7 @@ export default {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.languageContent {
|
||||
text-align: center;
|
||||
z-index: 99;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.tiny-svg {
|
||||
margin-right: 10px;
|
||||
|
|
@ -182,17 +175,6 @@ export default {
|
|||
color: #ccc;
|
||||
}
|
||||
}
|
||||
.tiny-button {
|
||||
max-width: initial;
|
||||
padding: 0 12px;
|
||||
background-color: var(--ti-lowcode-tabs-border-color);
|
||||
border-color: var(--ti-lowcode-tootip-input-border-color);
|
||||
color: var(--ti-lowcode-toolbar-icon-color);
|
||||
&:hover {
|
||||
background-color: var(--ti-lowcode-canvas-wrap-bg);
|
||||
border-color: var(--ti-lowcode-tabs-border-color);
|
||||
}
|
||||
}
|
||||
.addNewLanguage {
|
||||
.tiny-input {
|
||||
display: flex;
|
||||
|
|
@ -209,15 +191,16 @@ export default {
|
|||
.tiny-input__inner {
|
||||
flex: 1;
|
||||
}
|
||||
.add-btns {
|
||||
text-align: right;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.params-form {
|
||||
& > label {
|
||||
line-height: 30px;
|
||||
.label {
|
||||
margin: 16px 0;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.params-item + .params-item {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.params-item {
|
||||
display: flex;
|
||||
|
|
@ -225,7 +208,16 @@ export default {
|
|||
label {
|
||||
width: 80px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.bottom-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
.tiny-button,
|
||||
.tiny-button.tiny-button--default {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<tiny-dialog-box
|
||||
:visible="$attrs.visible"
|
||||
:visible="visible"
|
||||
title="发布区块"
|
||||
width="550px"
|
||||
append-to-body
|
||||
|
|
@ -10,10 +10,11 @@
|
|||
<tiny-form
|
||||
ref="deployBlockRef"
|
||||
label-position="left"
|
||||
label-width="100px"
|
||||
label-width="84px"
|
||||
label-align
|
||||
:model="formState"
|
||||
:rules="formRules"
|
||||
validate-type="text"
|
||||
>
|
||||
<tiny-form-item label="版本号" prop="version">
|
||||
<tiny-input v-model="formState.version" placeholder="请输入X.Y.Z格式版本号,如1.0.0"></tiny-input>
|
||||
|
|
@ -24,7 +25,7 @@
|
|||
<tiny-form-item label="保存设置" prop="needToSave" class="form-item-save">
|
||||
<tiny-checkbox v-model="formState.needToSave">发布成功后保存最新数据</tiny-checkbox>
|
||||
</tiny-form-item>
|
||||
<tiny-form-item label="schema比对">
|
||||
<tiny-form-item label="schema比对" class="schema-compare">
|
||||
<tiny-button class="compare-button" type="text" @click="changeCompare"> 查看本次发布的改动点 </tiny-button>
|
||||
<tiny-popover
|
||||
placement="top"
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
content="区块本地schema和线上新版本schema进行比对"
|
||||
>
|
||||
<template #reference>
|
||||
<icon-help-circle></icon-help-circle>
|
||||
<svg-icon name="plugin-icon-plugin-help"></svg-icon>
|
||||
</template>
|
||||
</tiny-popover>
|
||||
</tiny-form-item>
|
||||
|
|
@ -71,7 +72,6 @@
|
|||
|
||||
<script>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { iconHelpCircle } from '@opentiny/vue-icon'
|
||||
import {
|
||||
Checkbox as TinyCheckbox,
|
||||
Input as TinyInput,
|
||||
|
|
@ -81,14 +81,13 @@ import {
|
|||
Popover as TinyPopover,
|
||||
FormItem as TinyFormItem
|
||||
} from '@opentiny/vue'
|
||||
import { useNotify, useCanvas, getMetaApi, META_APP, useBlock } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useNotify, getMetaApi, META_APP } from '@opentiny/tiny-engine-meta-register'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import VueMonaco from './VueMonaco.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyCheckbox,
|
||||
IconHelpCircle: iconHelpCircle(),
|
||||
TinyButton,
|
||||
TinyDialogBox,
|
||||
TinyForm,
|
||||
|
|
@ -98,13 +97,17 @@ export default {
|
|||
VueMonaco
|
||||
},
|
||||
props: {
|
||||
nextVersion: {
|
||||
type: String,
|
||||
default: ''
|
||||
block: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
setup(props, { emit, attrs }) {
|
||||
emits: ['update:visible', 'changeSchema'],
|
||||
setup(props, { emit }) {
|
||||
const { COMPONENT_NAME } = constants
|
||||
const formState = reactive({
|
||||
deployInfo: '',
|
||||
|
|
@ -139,12 +142,31 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => attrs.visible,
|
||||
(visible) => {
|
||||
if (visible && !formState.version) {
|
||||
formState.version = props.nextVersion
|
||||
const getNextVersion = (block) => {
|
||||
const backupList = block.histories || []
|
||||
|
||||
let latestVersion = '1.0.0'
|
||||
let latestTime = 0
|
||||
backupList.forEach((v) => {
|
||||
const vTime = new Date(v.created_at).getTime()
|
||||
|
||||
if (vTime > latestTime) {
|
||||
latestTime = vTime
|
||||
latestVersion = v.version
|
||||
}
|
||||
})
|
||||
// version 符合X.Y.Z的字符结构
|
||||
return latestVersion.replace(/\d+$/, (match) => Number(match) + 1)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
|
||||
formState.version = getNextVersion(props.block)
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -152,10 +174,11 @@ export default {
|
|||
|
||||
const deployBlock = async () => {
|
||||
deployBlockRef.value.validate((valid) => {
|
||||
const { getEditBlock, publishBlock } = getMetaApi(META_APP.BlockManage)
|
||||
const { publishBlock } = getMetaApi(META_APP.BlockManage)
|
||||
|
||||
if (valid) {
|
||||
const params = {
|
||||
block: getEditBlock() || useBlock().getCurrentBlock(),
|
||||
block: props.block,
|
||||
is_compile: true,
|
||||
deploy_info: formState.deployInfo,
|
||||
version: formState.version,
|
||||
|
|
@ -172,21 +195,14 @@ export default {
|
|||
|
||||
const changeCompare = async (value) => {
|
||||
const api = getMetaApi(META_APP.BlockManage)
|
||||
|
||||
if (value) {
|
||||
const block = api.getEditBlock()
|
||||
const block = props.block
|
||||
const remote = await api.getBlockById(block?.id)
|
||||
const originalObj = remote?.content || {}
|
||||
state.originalCode = JSON.stringify(originalObj, null, 2)
|
||||
const getSchema = useCanvas().canvasApi.value.getSchema
|
||||
|
||||
// 转为普通对象,和线上代码顺序保持一致
|
||||
const pageSchema = getSchema?.() || {}
|
||||
if (pageSchema.componentName === 'Block') {
|
||||
state.code = JSON.stringify(pageSchema, null, 2)
|
||||
} else {
|
||||
// 非区块编辑
|
||||
state.code = state.originalCode
|
||||
}
|
||||
state.code = JSON.stringify(block?.content || {}, null, 2)
|
||||
state.compareVisible = true
|
||||
}
|
||||
}
|
||||
|
|
@ -209,9 +225,8 @@ export default {
|
|||
return
|
||||
}
|
||||
try {
|
||||
const pageSchema = JSON.parse(state.newCode)
|
||||
const setSchema = useCanvas().canvasApi.value.setSchema
|
||||
setSchema?.({ ...pageSchema, componentName: COMPONENT_NAME.Block })
|
||||
const newSchema = JSON.parse(state.newCode)
|
||||
emit('changeSchema', { ...newSchema, componentName: COMPONENT_NAME.Block })
|
||||
close()
|
||||
} catch (err) {
|
||||
useNotify({
|
||||
|
|
@ -254,7 +269,15 @@ export default {
|
|||
line-height: 0;
|
||||
}
|
||||
}
|
||||
.schema-compare {
|
||||
.tiny-button.tiny-button {
|
||||
padding-left: 0;
|
||||
padding-right: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.compare-button {
|
||||
font-size: 12px;
|
||||
padding-left: 0;
|
||||
padding-right: 8px;
|
||||
line-height: 28px;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default {
|
|||
const { addBlockEvent, removeEventLink, getCurrentBlock, appendEventEmit } = useBlock()
|
||||
const { PLUGIN_NAME, activePlugin } = useLayout()
|
||||
|
||||
const { schema } = useCanvas().canvasApi.value?.getSchema?.() || {}
|
||||
const { schema } = useCanvas()?.getSchema?.() || {}
|
||||
const events = schema?.events || []
|
||||
const eventsList = Object.entries(events).map(([key, value]) => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ export default {
|
|||
},
|
||||
setup(props) {
|
||||
const { confirm } = useModal()
|
||||
const { pageState, canvasApi } = useCanvas()
|
||||
const { pageState, canvasApi, getSchema } = useCanvas()
|
||||
const { addBlockProperty, removePropertyLink, getCurrentBlock, editBlockProperty } = useBlock()
|
||||
const { PLUGIN_NAME, activePlugin } = useLayout()
|
||||
const { schema } = canvasApi.value.getSchema?.() || {}
|
||||
const { schema } = getSchema?.() || {}
|
||||
|
||||
const state = reactive({
|
||||
newPropertyName: ''
|
||||
|
|
|
|||
|
|
@ -265,11 +265,11 @@ export default {
|
|||
|
||||
const updateValue = (value) => {
|
||||
const { property, type } = props.property
|
||||
const { setProp } = useProperties()
|
||||
const { setProp, getSchema } = useProperties()
|
||||
|
||||
// 是否双向绑定
|
||||
if (value?.type === SCHEMA_DATA_TYPE.JSExpression) {
|
||||
const currentComponent = useProperties().getSchema().componentName
|
||||
const currentComponent = getSchema().componentName
|
||||
const {
|
||||
schema: { events = {} }
|
||||
} = useMaterial().getMaterial(currentComponent)
|
||||
|
|
@ -282,13 +282,13 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
const { operateNode, isSaved } = useCanvas()
|
||||
|
||||
if (property === 'children') {
|
||||
useProperties().getSchema().children = value
|
||||
const schema = getSchema()
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { children: value } })
|
||||
} else {
|
||||
if (
|
||||
!useCanvas().isSaved() &&
|
||||
![PAGE_STATUS.Guest, PAGE_STATUS.Occupy].includes(useLayout().layoutState.pageStatus.state)
|
||||
) {
|
||||
if (!isSaved() && ![PAGE_STATUS.Guest, PAGE_STATUS.Occupy].includes(useLayout().layoutState.pageStatus.state)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -527,7 +527,7 @@ export default {
|
|||
}
|
||||
|
||||
.item-label {
|
||||
color: var(--ti-lowcode-meta-config-item-label-color);
|
||||
color: var(--te-common-text-secondary);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
line-height: 18px;
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
<template #suffix>
|
||||
<tiny-popover
|
||||
ref="popoverRef"
|
||||
width="260"
|
||||
width="340"
|
||||
trigger="click"
|
||||
:visible-arrow="false"
|
||||
popper-class="lowcode"
|
||||
popper-class="i18n-input-popover"
|
||||
@hide="onHide"
|
||||
>
|
||||
<div class="popover-content">
|
||||
<h3 class="title">绑定国际化</h3>
|
||||
<icon-close class="icon-close" @click="closePopover"></icon-close>
|
||||
<bind-i18n
|
||||
ref="addI1i8nRef"
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
</div>
|
||||
|
||||
<template #reference>
|
||||
<icon-language :class="['icon-language', { isBind }]"></icon-language>
|
||||
<svg-icon name="internationalization" :class="['icon-language', { isBind }]"></svg-icon>
|
||||
</template>
|
||||
</tiny-popover>
|
||||
</template>
|
||||
|
|
@ -37,7 +38,7 @@
|
|||
import { computed, ref, watchEffect } from 'vue'
|
||||
import { useTranslate } from '@opentiny/tiny-engine-meta-register'
|
||||
import { Input, Popover } from '@opentiny/vue'
|
||||
import { IconClose, IconLanguage } from '@opentiny/vue-icon'
|
||||
import { IconClose } from '@opentiny/vue-icon'
|
||||
import BindI18n from './BindI18n.vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -46,8 +47,7 @@ export default {
|
|||
TinyInput: Input,
|
||||
BindI18n,
|
||||
TinyPopover: Popover,
|
||||
IconClose: IconClose(),
|
||||
IconLanguage: IconLanguage()
|
||||
IconClose: IconClose()
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
|
|
@ -124,9 +124,27 @@ export default {
|
|||
}
|
||||
}
|
||||
.popover-content {
|
||||
text-align: right;
|
||||
position: relative;
|
||||
|
||||
.title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.icon-close {
|
||||
margin-right: 5px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.icon-language {
|
||||
color: var(--te-common-icon-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.tiny-popover.tiny-popover.tiny-popper[x-placement].i18n-input-popover {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="life-cycle">
|
||||
<tiny-popover v-model="state.showPopover" placement="bottom-end" trigger="hover" popperClass="option-popper">
|
||||
<template #reference>
|
||||
<tiny-button class="life-cycle-btn"><icon-plus class="icon-plus"></icon-plus>添加页面生命周期 </tiny-button>
|
||||
<tiny-button class="life-cycle-btn"><svg-icon name="add"></svg-icon>添加页面生命周期 </tiny-button>
|
||||
</template>
|
||||
<div class="popover-list">
|
||||
<ul>
|
||||
|
|
@ -21,13 +21,18 @@
|
|||
<div class="life-cycle-tips">{{ lifeCycleTips }}</div>
|
||||
<meta-list-items :optionsList="Object.keys(state.bindLifeCycles)" :draggable="false">
|
||||
<template #content="{ data }">
|
||||
<div>
|
||||
<div class="life-cycle-content-item">
|
||||
{{ data }}
|
||||
</div>
|
||||
</template>
|
||||
<template #operate="{ data }">
|
||||
<svg-button name="setting-outline" tips="编辑" placement="top" @click="openLifeCyclesPanel(data)"></svg-button>
|
||||
<svg-button name="delete" tips="删除" placement="top" @click="deleteLifeCycle(data)"></svg-button>
|
||||
<svg-button
|
||||
class="opt-button"
|
||||
:hoverBgColor="false"
|
||||
name="text-source-setting"
|
||||
@click="openLifeCyclesPanel(data)"
|
||||
></svg-button>
|
||||
<svg-button class="opt-button" :hoverBgColor="false" name="delete" @click="deleteLifeCycle(data)"></svg-button>
|
||||
</template>
|
||||
</meta-list-items>
|
||||
<tiny-dialog-box v-model:visible="state.showLifeCyclesDialog" fullscreen :title="state.title" :append-to-body="true">
|
||||
|
|
@ -72,7 +77,7 @@ import { Button, DialogBox, Popover, Search } from '@opentiny/vue'
|
|||
import { useModal, usePage, useNotify, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { getMergeMeta } from '@opentiny/tiny-engine-meta-register'
|
||||
import MetaListItems from './MetaListItems.vue'
|
||||
import { iconYes, iconPlus } from '@opentiny/vue-icon'
|
||||
import { iconYes } from '@opentiny/vue-icon'
|
||||
import VueMonaco from './VueMonaco.vue'
|
||||
import { initCompletion } from '../js/completion'
|
||||
import { initLinter, lint } from '../js/linter'
|
||||
|
|
@ -87,8 +92,7 @@ export default {
|
|||
MonacoEditor: VueMonaco,
|
||||
SvgButton,
|
||||
MetaListItems,
|
||||
IconYes: iconYes(),
|
||||
IconPlus: iconPlus()
|
||||
IconYes: iconYes()
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
@ -132,7 +136,7 @@ export default {
|
|||
}
|
||||
|
||||
const syncLifeCycle = () => {
|
||||
const currentSchema = useCanvas().canvasApi.value?.getSchema?.()
|
||||
const currentSchema = useCanvas().getSchema()
|
||||
const pageContent = getPageContent()
|
||||
const { id, fileName } = pageContent
|
||||
if (id === currentSchema.id || fileName === currentSchema.fileName) {
|
||||
|
|
@ -155,8 +159,7 @@ export default {
|
|||
|
||||
const openLifeCyclesPanel = (item) => {
|
||||
state.title = item
|
||||
const bindLifeCycleSource =
|
||||
props.bindLifeCycles?.[item] || useCanvas().canvasApi.value?.getSchema?.()?.lifeCycles?.[item]
|
||||
const bindLifeCycleSource = props.bindLifeCycles?.[item] || useCanvas().getSchema()?.lifeCycles?.[item]
|
||||
state.editorValue =
|
||||
bindLifeCycleSource?.value ||
|
||||
`function ${item} (${item === 'setup' ? '{ props, state, watch, onMounted }' : ''}) {} `
|
||||
|
|
@ -270,12 +273,20 @@ export default {
|
|||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.life-cycle-content-item {
|
||||
color: var(--te-common-text-primary);
|
||||
}
|
||||
.opt-button {
|
||||
width: auto;
|
||||
&:last-child {
|
||||
margin-right: var(--te-base-space-3x);
|
||||
}
|
||||
}
|
||||
|
||||
.popover-list {
|
||||
margin: 8px 0;
|
||||
li {
|
||||
padding: 0 12px;
|
||||
margin: 0 -8px;
|
||||
margin: 0 -16px;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,8 @@ export default {
|
|||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
},
|
||||
locale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,8 +303,9 @@ export default {
|
|||
width: 100%;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
padding-left: 10px;
|
||||
border: 1px solid var(--ti-lowcode-meta-codeEditor-border-color);
|
||||
border-radius: 6px;
|
||||
border-radius: var(--te-base-border-radius-1);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--ti-lowcode-meta-codeEditor-border-hover-color);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
:popper-class="['option-popper', { 'fixed-left': expand }]"
|
||||
:visible-arrow="!expand"
|
||||
title=""
|
||||
width="267"
|
||||
width="295"
|
||||
height="auto"
|
||||
trigger="manual"
|
||||
@hide="hide(item)"
|
||||
>
|
||||
|
|
@ -295,14 +296,21 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tiny-popover {
|
||||
position: relative;
|
||||
.icon-close {
|
||||
float: right;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
}
|
||||
.add-options {
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
padding: 20px 0 20px 2px;
|
||||
&.top {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: var(--ti-lowcode-toolbar-bg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,31 +30,33 @@
|
|||
</vue-draggable-next>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="(item, index) in optionsList" :key="index" class="option-item not-draggable">
|
||||
<meta-list-item
|
||||
:item="item"
|
||||
:index="index"
|
||||
:itemClick="itemClick"
|
||||
:dataScource="listItemOption"
|
||||
@changeItem="changeItem"
|
||||
@deleteItem="deleteItem"
|
||||
@editItem="editItem"
|
||||
>
|
||||
<template #content>
|
||||
<slot name="content" :data="item">
|
||||
<span>{{ item[textField] || formatName(item) }}</span>
|
||||
</slot>
|
||||
</template>
|
||||
<template #operate>
|
||||
<slot name="operate" :data="item"></slot>
|
||||
</template>
|
||||
<template #metaForm>
|
||||
<slot name="form" :data="item"></slot>
|
||||
</template>
|
||||
<template #footer>
|
||||
<slot name="footer" :data="item"></slot>
|
||||
</template>
|
||||
</meta-list-item>
|
||||
<div>
|
||||
<div v-for="(item, index) in optionsList" :key="index" class="option-item not-draggable">
|
||||
<meta-list-item
|
||||
:item="item"
|
||||
:index="index"
|
||||
:itemClick="itemClick"
|
||||
:dataScource="listItemOption"
|
||||
@changeItem="changeItem"
|
||||
@deleteItem="deleteItem"
|
||||
@editItem="editItem"
|
||||
>
|
||||
<template #content>
|
||||
<slot name="content" :data="item">
|
||||
<span>{{ item[textField] || formatName(item) }}</span>
|
||||
</slot>
|
||||
</template>
|
||||
<template #operate>
|
||||
<slot name="operate" :data="item"></slot>
|
||||
</template>
|
||||
<template #metaForm>
|
||||
<slot name="form" :data="item"></slot>
|
||||
</template>
|
||||
<template #footer>
|
||||
<slot name="footer" :data="item"></slot>
|
||||
</template>
|
||||
</meta-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
|
@ -103,7 +105,7 @@ export default {
|
|||
},
|
||||
setup(props, { emit }) {
|
||||
const listItemOption = computed(() => props)
|
||||
const { resState } = useResource()
|
||||
const { appSchemaState } = useResource()
|
||||
|
||||
const changeItem = (params) => {
|
||||
const optionsList = [...props.optionsList]
|
||||
|
|
@ -159,7 +161,7 @@ export default {
|
|||
if (item[props.textField]) {
|
||||
if (item[props.textField].i18nKey) {
|
||||
let i18nKey = item[props.textField].i18nKey
|
||||
text = resState.langs[i18nKey][resState.currentLang]
|
||||
text = appSchemaState.langs[i18nKey][appSchemaState.currentLang]
|
||||
} else {
|
||||
text = item[props.textField]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,19 @@
|
|||
</div>
|
||||
<div :class="['buttons', { fullscreen: fullscreen }]" id="icon-buttons">
|
||||
<slot name="buttons"></slot>
|
||||
<tiny-tooltip v-if="showFormatBtn && options.language === 'json'" content="格式化" placement="top">
|
||||
<tiny-tooltip
|
||||
v-if="showFormatBtn && options.language === 'json'"
|
||||
content="格式化"
|
||||
placement="top"
|
||||
:open-delay="OPEN_DELAY.Default"
|
||||
>
|
||||
<public-icon name="json" @click="formatCode"></public-icon>
|
||||
</tiny-tooltip>
|
||||
<span v-if="showFullScreenBtn">
|
||||
<tiny-tooltip v-if="!fullscreen" content="全屏" placement="top">
|
||||
<tiny-tooltip v-if="!fullscreen" content="全屏" placement="top" :open-delay="OPEN_DELAY.Default">
|
||||
<public-icon name="full-screen" @click="switchFullScreen(true)"></public-icon>
|
||||
</tiny-tooltip>
|
||||
<tiny-tooltip v-else content="退出全屏" placement="top">
|
||||
<tiny-tooltip v-else content="退出全屏" placement="top" :open-delay="OPEN_DELAY.Default">
|
||||
<public-icon name="cancel-full-screen" @click="switchFullScreen(false)"></public-icon>
|
||||
</tiny-tooltip>
|
||||
</span>
|
||||
|
|
@ -39,6 +44,8 @@ import { computed, ref, onActivated, onDeactivated } from 'vue'
|
|||
import { Tooltip } from '@opentiny/vue'
|
||||
import PublicIcon from './PublicIcon.vue'
|
||||
import VueMonaco from './VueMonaco.vue'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
const { OPEN_DELAY } = constants
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -122,7 +129,8 @@ export default {
|
|||
fullscreen,
|
||||
switchFullScreen,
|
||||
getValue,
|
||||
formatCode
|
||||
formatCode,
|
||||
OPEN_DELAY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div v-if="blockStyle === BlockStyles.Mini" class="header">
|
||||
<div class="col-checkbox"></div>
|
||||
<div v-if="blockStyle === BlockStyles.Mini && showCheckbox" class="header">
|
||||
<div class="col-checkbox">
|
||||
<select-all :allItems="data" :selected="checked" :hidden-label="true" @select-all="handleSelectAll"></select-all>
|
||||
</div>
|
||||
<div class="col-name">区块名称</div>
|
||||
<div class="col-time">创建时间</div>
|
||||
<div class="col-created-by">创建人</div>
|
||||
|
|
@ -16,7 +18,7 @@
|
|||
@mouseleave="state.hover = false"
|
||||
>
|
||||
<li v-if="showAddButton" class="block-item block-plus" @click="$emit('add')">
|
||||
<span class="block-plus-icon"><icon-plus></icon-plus></span>
|
||||
<span class="block-plus-icon"><svg-icon name="add"></svg-icon></span>
|
||||
<div class="item-text">添加区块</div>
|
||||
</li>
|
||||
<li
|
||||
|
|
@ -45,41 +47,21 @@
|
|||
<div v-if="blockStyle === BlockStyles.List" class="item-description">{{ item.description }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="blockStyle === BlockStyles.Mini" class="cell cell-time">
|
||||
<span>{{ format(item.created_at, 'yyyy/MM/dd hh:mm:ss') }}</span>
|
||||
</div>
|
||||
<div v-if="blockStyle === BlockStyles.Mini" class="cell cell-created-by">
|
||||
<span>{{ users.find((user) => user.id === item.createdBy)?.name || item.id }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="item.isShowProgress" class="progress-bar">
|
||||
<tiny-progress
|
||||
:text-inside="true"
|
||||
:stroke-width="8"
|
||||
:percentage="item.publishProgress"
|
||||
status="success"
|
||||
></tiny-progress>
|
||||
</div>
|
||||
|
||||
<div v-if="isBlockManage && !item.is_published" class="publish-flag">未发布</div>
|
||||
|
||||
<div v-if="isBlockManage" class="block-detail">
|
||||
<div class="setting-menu" @mouseover.stop="handleSettingMouseOver" @mouseleave="handleBlockItemLeave">
|
||||
<ul class="list">
|
||||
<tiny-tooltip content="编辑" placement="top">
|
||||
<li class="list-item" @mousedown.stop.left="editBlock({ event: $event, item, index })">
|
||||
<svg-button class="list-item-svg" name="to-edit"> </svg-button>
|
||||
</li>
|
||||
</tiny-tooltip>
|
||||
<tiny-tooltip content="设置" placement="top">
|
||||
<li
|
||||
class="list-item"
|
||||
@mouseover.stop="iconSettingMove"
|
||||
@mousedown.stop.prevent="iconClick({ event: $event, item, index })"
|
||||
>
|
||||
<svg-button class="list-item-svg" name="text-source-setting"> </svg-button>
|
||||
</li>
|
||||
</tiny-tooltip>
|
||||
<li class="list-item" @mousedown.stop.left="editBlock({ event: $event, item, index })">
|
||||
<svg-button class="list-item-svg" name="to-edit"> </svg-button>
|
||||
</li>
|
||||
<li
|
||||
class="list-item"
|
||||
@mouseover.stop="iconSettingMove"
|
||||
@mousedown.stop.prevent="iconClick({ event: $event, item, index })"
|
||||
>
|
||||
<svg-button class="list-item-svg" name="text-source-setting"> </svg-button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -103,19 +85,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.isAnimation"
|
||||
:class="[
|
||||
'deploy',
|
||||
{ success: item.deployStatus === taskStatus.FINISHED },
|
||||
{
|
||||
error: item.deployStatus === taskStatus.STOPPED
|
||||
}
|
||||
]"
|
||||
@mouseover.stop="item.isAnimation = false"
|
||||
>
|
||||
{{ deployTips[item.deployStatus] }}
|
||||
</div>
|
||||
</slot>
|
||||
</li>
|
||||
<div v-if="showBlockShot && state.hover && state.currentBlock.screenshot" class="block-shortcut">
|
||||
|
|
@ -138,11 +107,11 @@
|
|||
<script>
|
||||
import { computed, watch, inject, reactive } from 'vue'
|
||||
import { format } from '@opentiny/vue-renderless/common/date'
|
||||
import { iconPlus } from '@opentiny/vue-icon'
|
||||
import { Progress, Tooltip } from '@opentiny/vue'
|
||||
import { Tooltip } from '@opentiny/vue'
|
||||
import PluginBlockItemImg from './PluginBlockItemImg.vue'
|
||||
import SearchEmpty from './SearchEmpty.vue'
|
||||
import SvgButton from './SvgButton.vue'
|
||||
import SelectAll from './SelectAll.vue'
|
||||
|
||||
const BlockStyles = {
|
||||
Default: 'default',
|
||||
|
|
@ -155,12 +124,11 @@ const defaultImg =
|
|||
|
||||
export default {
|
||||
components: {
|
||||
TinyProgress: Progress,
|
||||
IconPlus: iconPlus(),
|
||||
TinyTooltip: Tooltip,
|
||||
PluginBlockItemImg,
|
||||
SvgButton,
|
||||
SearchEmpty
|
||||
SearchEmpty,
|
||||
SelectAll
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
|
|
@ -234,10 +202,10 @@ export default {
|
|||
default: 2
|
||||
}
|
||||
},
|
||||
emits: ['click', 'iconClick', 'add', 'deleteBlock', 'openVersionPanel', 'editBlock'],
|
||||
emits: ['click', 'iconClick', 'add', 'deleteBlock', 'openVersionPanel', 'editBlock', 'checkAll', 'cancelCheckAll'],
|
||||
setup(props, { emit }) {
|
||||
const panelState = inject('panelState', {})
|
||||
const blockUsers = inject('blockUsers')
|
||||
const blockUsers = inject('blockUsers', [])
|
||||
const state = reactive({
|
||||
activeIndex: -1,
|
||||
data: computed(() => props.data),
|
||||
|
|
@ -298,19 +266,6 @@ export default {
|
|||
state.activeIndex = -1
|
||||
}
|
||||
|
||||
// 区块发布任务说明
|
||||
const taskStatus = {
|
||||
RUNNING: 1,
|
||||
STOPPED: 2,
|
||||
FINISHED: 3
|
||||
}
|
||||
|
||||
const deployTips = {
|
||||
1: '正在发布中',
|
||||
2: '发布失败,请重新发布',
|
||||
3: '发布完成'
|
||||
}
|
||||
|
||||
const iconSettingMove = () => {
|
||||
state.hover = false
|
||||
}
|
||||
|
|
@ -361,6 +316,14 @@ export default {
|
|||
}
|
||||
)
|
||||
|
||||
const handleSelectAll = (items) => {
|
||||
if (Array.isArray(items)) {
|
||||
emit('checkAll', items)
|
||||
} else {
|
||||
emit('cancelCheckAll')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
BlockStyles,
|
||||
isShortcutPanel: panelState.isShortcutPanel,
|
||||
|
|
@ -370,8 +333,6 @@ export default {
|
|||
blockClick,
|
||||
iconClick,
|
||||
clearActive,
|
||||
taskStatus,
|
||||
deployTips,
|
||||
openBlockShotPanel,
|
||||
iconSettingMove,
|
||||
defaultImg,
|
||||
|
|
@ -379,7 +340,8 @@ export default {
|
|||
handleSettingMouseOver,
|
||||
handleShowVersionMenu,
|
||||
editBlock,
|
||||
format
|
||||
format,
|
||||
handleSelectAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -446,6 +408,11 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.col-checkbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.col-checkbox,
|
||||
.block-item-small-list:deep(.table-selection) {
|
||||
width: 40px;
|
||||
|
|
@ -485,16 +452,17 @@ export default {
|
|||
|
||||
.publish-flag {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
top: 4px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: var(--ti-lowcode-common-secondary-text-color);
|
||||
color: var(--te-common-text-primary);
|
||||
font-size: 12px;
|
||||
background-color: var(--ti-lowcode-component-block-list-item-tag-bg);
|
||||
padding: 2px;
|
||||
border-radius: 4px 0 4px 0;
|
||||
background-color: var(--te-common-bg-prompt);
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
transform: scale(0.9);
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
&.block-item-small-list {
|
||||
|
|
@ -507,7 +475,7 @@ export default {
|
|||
margin-left: 8px;
|
||||
}
|
||||
.item-text {
|
||||
width: calc(35% - 62px);
|
||||
width: 50%;
|
||||
}
|
||||
.publish-flag {
|
||||
position: static;
|
||||
|
|
@ -527,6 +495,9 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--te-common-bg-container);
|
||||
}
|
||||
}
|
||||
&:nth-child(even) {
|
||||
border-right: 0;
|
||||
|
|
@ -584,7 +555,7 @@ export default {
|
|||
.item-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
.tiny-svg {
|
||||
.svg-icon {
|
||||
font-size: 24px;
|
||||
color: var(--ti-lowcode-component-svg-button-color);
|
||||
}
|
||||
|
|
@ -770,7 +741,6 @@ export default {
|
|||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--ti-lowcode-component-block-setting-item-hover-bg);
|
||||
color: var(--ti-lowcode-common-primary-text-color);
|
||||
}
|
||||
.list-item-icon {
|
||||
|
|
@ -781,7 +751,7 @@ export default {
|
|||
color: var(--te-common-icon-secondary);
|
||||
}
|
||||
.list-item-svg:hover {
|
||||
background-color: var(--ti-lowcode-component-block-setting-item-hover-bg);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,14 @@
|
|||
</slot>
|
||||
<div class="button-group-wrap">
|
||||
<slot name="header">
|
||||
<div v-if="showIfFullScreen" class="cursor" @click="fullScreen">
|
||||
<icon-fullscreen v-if="!state.isFullScreen" class="icon-fullscreen"></icon-fullscreen>
|
||||
<icon-minscreen v-if="state.isFullScreen" class="icon-minscreen"></icon-minscreen>
|
||||
<span class="full-screen-label">{{ getFullScreenLabel(state.isFullScreen) }}</span>
|
||||
</div>
|
||||
<tiny-button v-if="!isIconButton" type="info" @click="$emit('save')" class="plugin-save">保存</tiny-button>
|
||||
<tiny-button v-if="isIconButton" :icon="icon" type="info" @click="$emit('add')">
|
||||
{{ iconButtonText }}
|
||||
</tiny-button>
|
||||
<div v-if="showIfFullScreen" class="cursor" @click="fullScreen">
|
||||
<svg-icon v-if="!state.isFullScreen" name="full-screen"></svg-icon>
|
||||
<svg-icon v-if="state.isFullScreen" name="cancel-full-screen"></svg-icon>
|
||||
</div>
|
||||
<icon-close class="icon-close close" @click="$emit('cancel')"></icon-close>
|
||||
</slot>
|
||||
</div>
|
||||
|
|
@ -32,7 +31,7 @@
|
|||
<script>
|
||||
import { reactive, watchEffect } from 'vue'
|
||||
import { Button } from '@opentiny/vue'
|
||||
import { iconPlus, iconFullscreen, iconMinscreen, iconClose } from '@opentiny/vue-icon'
|
||||
import { iconPlus, iconClose } from '@opentiny/vue-icon'
|
||||
|
||||
const EVENTS = {
|
||||
FULL_SCREEN_CHANGE: 'fullScreenChange',
|
||||
|
|
@ -45,8 +44,6 @@ const EVENTS = {
|
|||
export default {
|
||||
components: {
|
||||
TinyButton: Button,
|
||||
IconFullscreen: iconFullscreen(),
|
||||
IconMinscreen: iconMinscreen(),
|
||||
IconClose: iconClose()
|
||||
},
|
||||
props: {
|
||||
|
|
@ -114,8 +111,6 @@ export default {
|
|||
state,
|
||||
fullScreen,
|
||||
getFullScreenLabel,
|
||||
IconFullscreen: iconFullscreen(),
|
||||
IconMinscreen: iconMinscreen(),
|
||||
IconClose: iconClose()
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +120,7 @@ export default {
|
|||
<style lang="less" scoped>
|
||||
.plugin-setting {
|
||||
position: absolute;
|
||||
left: calc(var(--base-left-panel-width) - 6px);
|
||||
left: var(--base-left-panel-width);
|
||||
top: 0;
|
||||
width: var(--base-collection-panel-width);
|
||||
height: 100%;
|
||||
|
|
@ -134,8 +129,9 @@ export default {
|
|||
overflow: hidden;
|
||||
border-left: 1px solid var(--ti-lowcode-plugin-panel-header-border-bottom-color);
|
||||
&:not(.second-panel) {
|
||||
box-shadow: 6px 0px 3px 0px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 6px 0px 3px 0px var(--te-base-box-shadow-rgba-3);
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
}
|
||||
&.full-screen {
|
||||
width: var(--base-collection-panel-full-screen-width);
|
||||
|
|
@ -154,11 +150,12 @@ export default {
|
|||
}
|
||||
|
||||
.cursor {
|
||||
margin-left: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close {
|
||||
margin-left: 16px;
|
||||
margin-left: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
@ -176,6 +173,10 @@ export default {
|
|||
.plugin-setting-header-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
margin-right: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.svg-button + .svg-button) {
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-progress
|
||||
v-if="editBlock?.isShowProgress"
|
||||
class="progress-bar"
|
||||
:stroke-width="2"
|
||||
:text-inside="true"
|
||||
:percentage="editBlock?.publishProgress"
|
||||
status="success"
|
||||
></tiny-progress>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed } from 'vue'
|
||||
import { Progress as TinyProgress } from '@opentiny/vue'
|
||||
import { getMetaApi, META_APP } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyProgress
|
||||
},
|
||||
setup() {
|
||||
const { getEditBlock } = getMetaApi(META_APP.BlockManage) || {}
|
||||
const editBlock = getEditBlock ? computed(getEditBlock) : null
|
||||
|
||||
return {
|
||||
editBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<tiny-checkbox class="block-select-all" :indeterminate="isIndeterminate" v-model="selectedAll">
|
||||
{{ hiddenLabel ? '' : '全选' }}
|
||||
</tiny-checkbox>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Checkbox as TinyCheckbox } from '@opentiny/vue'
|
||||
import { computed, defineEmits, defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
allItems: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
hiddenLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selectAll'])
|
||||
|
||||
const selectedAll = computed({
|
||||
get() {
|
||||
return props.allItems.length > 0 && props.allItems.length === props.selected.length
|
||||
},
|
||||
set(value) {
|
||||
if (value) {
|
||||
emit('selectAll', props.allItems)
|
||||
} else {
|
||||
emit('selectAll', null)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isIndeterminate = computed(() => props.selected.length > 0 && props.selected.length !== props.allItems.length)
|
||||
</script>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<span class="svg-button" @click="$emit('click', $event)">
|
||||
<span class="svg-button" :class="{ 'svg-button-hover': hoverBgColor }" @click="$emit('click', $event)">
|
||||
<tiny-tooltip effect="dark" :content="tips" :placement="placement">
|
||||
<component :is="name" v-if="isTinyIcon" />
|
||||
<svg-icon v-else :name="name"></svg-icon>
|
||||
|
|
@ -27,6 +27,10 @@ export default {
|
|||
name: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
hoverBgColor: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
|
|
@ -45,17 +49,13 @@ export default {
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
color: var(--ti-lowcode-component-svg-button-color);
|
||||
color: var(--te-common-icon-secondary);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--ti-lowcode-component-svg-button-hover-color);
|
||||
background-color: var(--ti-lowcode-component-svg-button-hover-bg-color);
|
||||
}
|
||||
&.active {
|
||||
color: var(--ti-lowcode-component-svg-button-active-color);
|
||||
background-color: var(--ti-lowcode-component-svg-button-active-bg-color);
|
||||
|
|
@ -68,4 +68,11 @@ export default {
|
|||
outline: none;
|
||||
}
|
||||
}
|
||||
.svg-button-hover {
|
||||
color: var(--te-common-icon-primary);
|
||||
&:hover {
|
||||
color: var(--te-common-icon-primary);
|
||||
background-color: var(--te-common-bg-prompt);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export { default as BlockLinkField } from './BlockLinkField.vue'
|
|||
export { default as BlockLinkEvent } from './BlockLinkEvent.vue'
|
||||
export { default as BlockDescription } from './BlockDescription.vue'
|
||||
export { default as PluginBlockList } from './PluginBlockList.vue'
|
||||
export { default as SelectAll } from './SelectAll.vue'
|
||||
export { default as ButtonGroup } from './ButtonGroup.vue'
|
||||
export { default as CloseIcon } from './CloseIcon.vue'
|
||||
export { default as LifeCycles } from './LifeCycles.vue'
|
||||
|
|
@ -44,7 +45,6 @@ export { default as VueMonaco, setGlobalMonacoEditorTheme } from './VueMonaco.vu
|
|||
export { default as PublicIcon } from './PublicIcon.vue'
|
||||
export { default as BindI18n } from './BindI18n.vue'
|
||||
export { default as BlockDeployDialog } from './BlockDeployDialog.vue'
|
||||
export { default as ProgressBar } from './ProgressBar.vue'
|
||||
export { default as SearchEmpty } from './SearchEmpty.vue'
|
||||
export { default as MetaDescription } from './MetaDescription.vue'
|
||||
export { default as MetaList } from './MetaList.vue'
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<tiny-button v-bind="extendAttrs" class="toolbar-button">
|
||||
<span class="svg-wrap">
|
||||
<div class="svg-wrap">
|
||||
<span v-if="options?.showDots" class="dot"></span>
|
||||
<tiny-button class="toolbar-button">
|
||||
<svg-icon v-if="icon" :name="icon"></svg-icon>
|
||||
<span v-if="options?.showDots" class="dots"></span>
|
||||
</span>
|
||||
<span class="save-title">{{ content }}</span>
|
||||
<slot></slot>
|
||||
</tiny-button>
|
||||
<span class="save-title">{{ content }}</span>
|
||||
<slot></slot>
|
||||
</tiny-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { inject } from 'vue'
|
||||
import { Button } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -29,12 +28,6 @@ export default {
|
|||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const extendAttrs = inject('extend-attributes') || {}
|
||||
return {
|
||||
extendAttrs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -52,20 +45,19 @@ export default {
|
|||
|
||||
.svg-wrap {
|
||||
position: relative;
|
||||
.dots {
|
||||
|
||||
.dot {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--ti-lowcode-toolbar-dot-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
top: -3px;
|
||||
right: 2px;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.save-title {
|
||||
margin: 0 6px;
|
||||
.svg-icon.svg-icon.svg-icon {
|
||||
color: var(--te-common-icon-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
>
|
||||
<template #reference>
|
||||
<span class="icon">
|
||||
<span class="icon-hides" v-bind="extendAttrs">
|
||||
<span class="icon-hides" v-bind="$attrs">
|
||||
<svg-icon :name="icon"></svg-icon>
|
||||
<span v-if="options?.showDots" class="dots"></span>
|
||||
</span>
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
</tiny-popover>
|
||||
</template>
|
||||
<script>
|
||||
import { inject } from 'vue'
|
||||
import { Popover } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -35,13 +34,7 @@ export default {
|
|||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const extendAttrs = inject('extend-attributes') || {}
|
||||
return {
|
||||
extendAttrs
|
||||
default: () => ({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,35 @@ const getAllNestedBlocksSchema = async (pageSchema, fetchBlockSchemaApi, blockSe
|
|||
const extraList = []
|
||||
|
||||
schemaList.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
|
||||
res.push(item.value[0].content)
|
||||
extraList.push(getAllNestedBlocksSchema(item.value[0].content, fetchBlockSchemaApi, blockSet))
|
||||
const blockItem = item.value?.[0]
|
||||
|
||||
if (item.status !== 'fulfilled' || !blockItem) {
|
||||
return
|
||||
}
|
||||
|
||||
const historyId = blockItem?.current_history
|
||||
const historySchema = blockItem?.histories?.find?.((historyItem) => historyItem?.id === historyId)
|
||||
|
||||
let schemaContent = null
|
||||
|
||||
if (historyId && historySchema?.content) {
|
||||
schemaContent = historySchema.content
|
||||
} else {
|
||||
schemaContent = blockItem?.content
|
||||
}
|
||||
|
||||
if (!schemaContent) {
|
||||
return
|
||||
}
|
||||
|
||||
// 区块 schema 中加上当前版本号,让后续数据支持缓存或更丰富的操作
|
||||
schemaContent.version = historyId || ''
|
||||
// 区块子依赖
|
||||
schemaContent.subBlockDeps = blockNames
|
||||
|
||||
res.push(schemaContent)
|
||||
|
||||
extraList.push(getAllNestedBlocksSchema(schemaContent, fetchBlockSchemaApi, blockSet))
|
||||
})
|
||||
;(await Promise.allSettled(extraList)).forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { useResource, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-met
|
|||
export const getCanvasStatus = (data) => {
|
||||
const globalState = getMetaApi(META_SERVICE.GlobalService).getState()
|
||||
// 写死ID 待删除
|
||||
let isDemo = useResource().resState.isDemo
|
||||
let isDemo = useResource().appSchemaState.isDemo
|
||||
const { resetPasswordToken } = globalState.userInfo
|
||||
|
||||
if (isDemo && [PAGE_STATUS.Developer, PAGE_STATUS.SuperAdmin].includes(resetPasswordToken)) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const getSnippetsSuggestions = (monaco, range, wordContent) =>
|
|||
.filter(({ insertText }) => insertText.indexOf(wordContent) === 0)
|
||||
|
||||
const getUserWords = () => {
|
||||
const { bridge = [], dataSource = [], utils = [], globalState = [] } = useResource().resState
|
||||
const { bridge = [], dataSource = [], utils = [], globalState = [] } = useResource().appSchemaState
|
||||
|
||||
return {
|
||||
state: {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const open = (params = {}) => {
|
|||
|
||||
openUrl = isDevelopEnv
|
||||
? `./preview.html?tenant=${tenant}#${hashString}`
|
||||
: `${href}/preview?tenant=${tenant}#${hashString}`
|
||||
: `${href.endsWith('/') ? href : `${href}/`}preview?tenant=${tenant}#${hashString}`
|
||||
|
||||
const aTag = document.createElement('a')
|
||||
aTag.href = openUrl
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-common",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-configurator",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.1.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ export default {
|
|||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
color: var(--te-common-text-secondary);
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@
|
|||
<tiny-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"> </tiny-option>
|
||||
</tiny-select>
|
||||
<tiny-tooltip class="item" effect="dark" content="刷新数据源" placement="top">
|
||||
<icon-conment-refresh @click="refreshData"></icon-conment-refresh>
|
||||
<icon-common-refresh @click="refreshData"></icon-common-refresh>
|
||||
</tiny-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="jsx">
|
||||
import { useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
|
||||
import { Option, Select, Tooltip } from '@opentiny/vue'
|
||||
import { IconConmentRefresh } from '@opentiny/vue-icon'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { Option, Select, Tooltip } from '@opentiny/vue'
|
||||
import { iconConmentRefresh as iconCommonRefresh } from '@opentiny/vue-icon'
|
||||
import { useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinySelect: Select,
|
||||
TinyOption: Option,
|
||||
IconConmentRefresh: IconConmentRefresh(),
|
||||
IconCommonRefresh: iconCommonRefresh(),
|
||||
TinyTooltip: Tooltip
|
||||
},
|
||||
props: {
|
||||
|
|
@ -48,6 +48,8 @@ export default {
|
|||
const fetchDataSourceList = (appId) => getMetaApi(META_SERVICE.Http).get(`/app-center/api/sources/list/${appId}`)
|
||||
|
||||
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
|
||||
|
||||
// 获取数据源列表
|
||||
fetchDataSourceList(appId).then((data) => {
|
||||
options.value = data
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<template #content="{ data }">
|
||||
<div class="item-text">
|
||||
<div class="tiny-input">
|
||||
<input v-model="data.props.title" class="tiny-input__inner" />
|
||||
<tiny-input v-model="data.props.title" @update:modelValue="onTitleUpdate(data)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -20,16 +20,18 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Input } from '@opentiny/vue'
|
||||
import { MetaListItems } from '@opentiny/tiny-engine-common'
|
||||
import { useProperties, useMaterial, useHistory } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useProperties, useMaterial, useHistory, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
import { iconDel } from '@opentiny/vue-icon'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MetaListItems,
|
||||
IconDel: iconDel()
|
||||
IconDel: iconDel(),
|
||||
TinyInput: Input
|
||||
},
|
||||
setup() {
|
||||
const { children: schemaChildren, componentName } = useProperties().getSchema()
|
||||
|
|
@ -37,33 +39,62 @@ export default {
|
|||
const childComponentName =
|
||||
configureMap[componentName]?.nestingRule?.childWhitelist?.[0] || schemaChildren?.[0]?.componentName
|
||||
|
||||
schemaChildren.forEach((item) => {
|
||||
if (!item.props) {
|
||||
item.props = {
|
||||
title: '选项卡',
|
||||
name: ''
|
||||
const updateChildrenToValid = () => {
|
||||
const schema = useProperties().getSchema()
|
||||
const schemaChildren = schema.children || []
|
||||
let hasUpdate = false
|
||||
|
||||
const newChildren = schemaChildren.map((item) => {
|
||||
if (!item.props) {
|
||||
hasUpdate = true
|
||||
|
||||
item.props = {
|
||||
title: '选项卡',
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
const { operateNode } = useCanvas()
|
||||
|
||||
if (hasUpdate) {
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { children: newChildren } })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateChildrenToValid()
|
||||
})
|
||||
|
||||
const children = ref(schemaChildren)
|
||||
const addChildren = () => {
|
||||
const name = utils.guid()
|
||||
|
||||
schemaChildren.push({
|
||||
const schema = useProperties().getSchema()
|
||||
const newNodeData = {
|
||||
componentName: childComponentName,
|
||||
props: {
|
||||
title: '选项卡',
|
||||
name
|
||||
},
|
||||
children: [{ componentName: 'div' }]
|
||||
})
|
||||
}
|
||||
|
||||
const { operateNode } = useCanvas()
|
||||
|
||||
operateNode({ type: 'insert', parentId: schema.id, newNodeData, position: 'after' })
|
||||
|
||||
children.value = [...schemaChildren]
|
||||
}
|
||||
|
||||
const delChildren = (data) => {
|
||||
schemaChildren.splice(children.value.indexOf(data), 1)
|
||||
const { operateNode } = useCanvas()
|
||||
|
||||
operateNode({ type: 'delete', id: data.id })
|
||||
|
||||
children.value = [...schemaChildren]
|
||||
|
||||
useHistory().addHistory()
|
||||
}
|
||||
|
||||
|
|
@ -74,14 +105,34 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
const list = schemaChildren
|
||||
const spliceItem = list.splice(oldIndex, 1)
|
||||
const schema = useProperties().getSchema()
|
||||
const schemaChildren = schema.children
|
||||
|
||||
list.splice(newIndex, 0, ...spliceItem)
|
||||
children.value = [...list]
|
||||
const { operateNode } = useCanvas()
|
||||
|
||||
const newNodeData = schemaChildren[oldIndex]
|
||||
const referTargetNodeId = schemaChildren[newIndex].id
|
||||
|
||||
operateNode({ type: 'delete', id: schemaChildren[oldIndex].id })
|
||||
operateNode({
|
||||
type: 'insert',
|
||||
parentId: schema.id,
|
||||
newNodeData,
|
||||
position: newIndex < oldIndex ? 'before' : 'after',
|
||||
referTargetNodeId
|
||||
})
|
||||
|
||||
children.value = [...schemaChildren]
|
||||
}
|
||||
|
||||
return { children, addChildren, delChildren, dragEnd }
|
||||
const onTitleUpdate = (value) => {
|
||||
const { operateNode } = useCanvas()
|
||||
const id = value.id
|
||||
|
||||
operateNode({ type: 'changeProps', id, value: { props: value.props } })
|
||||
}
|
||||
|
||||
return { children, addChildren, delChildren, dragEnd, onTitleUpdate }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { reactive, ref, watchEffect } from 'vue'
|
||||
import { useProperties, useMaterial } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useProperties, useMaterial, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import { IconDel, IconEdit, IconClose, IconPlus } from '@opentiny/vue-icon'
|
||||
import { Form, FormItem, Input, Button, Popover, Tooltip } from '@opentiny/vue'
|
||||
import { utils } from '@opentiny/tiny-engine-utils'
|
||||
|
|
@ -95,12 +95,26 @@ export default {
|
|||
})
|
||||
|
||||
for (const [key, value] of Object.entries(useProperties().getSchema()?.props)) {
|
||||
!properties.includes(key) &&
|
||||
const isExcludedKey = properties.includes(key)
|
||||
|
||||
if (isExcludedKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
const index = attrs.value.findIndex(({ data: { key: attrKey } }) => attrKey === key)
|
||||
|
||||
if (index === -1) {
|
||||
attrs.value.push({
|
||||
text: `${key} = '${value}'`,
|
||||
data: { key, value },
|
||||
id: utils.guid()
|
||||
})
|
||||
} else {
|
||||
Object.assign(attrs.value[index], {
|
||||
text: `${key} = '${value}'`,
|
||||
data: { key, value }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -110,7 +124,10 @@ export default {
|
|||
|
||||
const updateSchema = () => {
|
||||
const mergeProps = {}
|
||||
const { props } = useProperties().getSchema()
|
||||
const { operateNode } = useCanvas()
|
||||
const { getSchema } = useProperties()
|
||||
const { props } = getSchema()
|
||||
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (properties.includes(key)) {
|
||||
mergeProps[key] = value
|
||||
|
|
@ -121,7 +138,9 @@ export default {
|
|||
mergeProps[attr.data.key] = attr.data.value
|
||||
})
|
||||
|
||||
useProperties().getSchema().props = mergeProps
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { props: mergeProps } })
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
|
|
@ -195,8 +214,8 @@ export default {
|
|||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 16px;
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
background: var(--ti-lowcode-canvas-wrap-bg);
|
||||
color: var(--te-common-text-secondary);
|
||||
background: var(--te-common-bg-container);
|
||||
border: 1px solid var(--ti-lowcode-left-button-border-color);
|
||||
border-radius: 2px;
|
||||
display: inline-flex;
|
||||
|
|
@ -223,7 +242,7 @@ export default {
|
|||
border: 1px solid var(--ti-lowcode-optionitem-border-color);
|
||||
background: var(--ti-lowcode-optionitem-background-color);
|
||||
margin-bottom: -1px;
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
color: var(--te-common-text-secondary);
|
||||
padding: 7px;
|
||||
display: grid;
|
||||
grid-template-columns: 3fr auto;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useProperties } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useProperties, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
import InputConfigurator from '../input-configurator/InputConfigurator.vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -44,12 +44,20 @@ export default {
|
|||
|
||||
const triggerType = (item, index) => {
|
||||
state.active = index
|
||||
useProperties().getSchema().componentName = item.toLowerCase()
|
||||
const { operateNode } = useCanvas()
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
if (schema) {
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { componentName: item.toLowerCase() } })
|
||||
}
|
||||
}
|
||||
|
||||
const change = (value) => {
|
||||
if (useProperties().getSchema()) {
|
||||
useProperties().getSchema().children = value
|
||||
const { operateNode } = useCanvas()
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
if (schema) {
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { children: value } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default {
|
|||
font-size: 16px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: var(--ti-lowcode-dialog-font-color);
|
||||
color: var(--te-common-text-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div v-for="(slot, index) in slotList" :key="slot.name" class="use-slot">
|
||||
<div class="use-slot-item-name">
|
||||
{{ slot.name }}
|
||||
<tiny-tooltip effect="dark" :content="state.currentComponent?.content" placement="top">
|
||||
<tiny-tooltip effect="dark" :content="state.currentComponent?.content" placement="bottom" width="260">
|
||||
<span class="item-icon">
|
||||
<component :is="state.currentComponent?.icon"></component>
|
||||
</span>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<div class="use-slot-item-content">
|
||||
<div class="use-slot-switch-wrap">
|
||||
<div :class="['e__switch', { 'e_is-checked': slot.bind }]">
|
||||
<span class="e__switch-core" @click="toggleSlot(index, slot)"></span>
|
||||
<span class="e__switch-core" @click="updateSlot(index, slot)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<tiny-form-item
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<tiny-input
|
||||
v-model="slot.params"
|
||||
class="use-slot-params"
|
||||
@change="validParams(paramsPropPath(index), slot)"
|
||||
@change="validParams(slot, paramsPropPath(index))"
|
||||
></tiny-input>
|
||||
</tiny-form-item>
|
||||
</div>
|
||||
|
|
@ -74,7 +74,7 @@ export default {
|
|||
TinyGrid: {
|
||||
content:
|
||||
'暴露给插槽使用的变量,为解构的参数,可以使用多个用逗号分隔,如:row(行数据),column(列数据),$table(内部表格实例),seq(序号),cell(单元格),columnIndex(列索引),rowIndex(行索引)',
|
||||
icon: SvgICons['IconUnknow']()
|
||||
icon: SvgICons['IconHelpCircle']()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,51 +94,7 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
const updateSlotParams = (slotData) => {
|
||||
emit('update:modelValue', slotData)
|
||||
|
||||
// 更新当前选中组件的根属性,不更新在jsslot中的数据非响应式
|
||||
const [propsName] = path.split('.')
|
||||
const schema = useProperties().getSchema()
|
||||
schema.props[propsName] = JSON.parse(JSON.stringify(schema.props[propsName]))
|
||||
}
|
||||
|
||||
const setSlotParams = ({ name, params = '' }) => {
|
||||
if (!props.modelValue?.[name]) {
|
||||
return
|
||||
}
|
||||
|
||||
const slotData = { ...(props.modelValue || {}) }
|
||||
|
||||
if (params.length) {
|
||||
slotData[name].params = params.split(',')
|
||||
} else {
|
||||
delete slotData[name].params
|
||||
}
|
||||
|
||||
updateSlotParams(slotData)
|
||||
}
|
||||
|
||||
const toggleSlot = (idx, { bind, name, params = '' }) => {
|
||||
// 原本绑定的,解除绑定
|
||||
if (bind) {
|
||||
useModal().confirm({
|
||||
title: '提示',
|
||||
message: '关闭后插槽内的内容将被清空,是否继续?',
|
||||
status: 'info',
|
||||
exec: () => {
|
||||
slotList.value[idx].bind = false
|
||||
const { [name]: _deleted, ...rest } = { ...(props.modelValue || {}) }
|
||||
updateSlotParams(rest)
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 未绑定的,新增绑定
|
||||
slotList.value[idx].bind = true
|
||||
|
||||
const updateSlot = (idx, { bind, name, params = '' }, isToggleSlot = true) => {
|
||||
const slotInfo = {
|
||||
[name]: {
|
||||
type: 'JSSlot',
|
||||
|
|
@ -150,17 +106,71 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
const slotData = { ...slotInfo, ...(props.modelValue || {}) }
|
||||
const { setProp } = useProperties()
|
||||
const { setNode, operateNode } = useCanvas()
|
||||
|
||||
if (params.length) {
|
||||
slotInfo[name].params = params.split(',')
|
||||
slotData[name].params = params.split(',')
|
||||
} else {
|
||||
delete slotData[name].params
|
||||
}
|
||||
|
||||
updateSlotParams({ ...(props.modelValue || {}), ...slotInfo })
|
||||
if (bind && isToggleSlot) {
|
||||
useModal().confirm({
|
||||
title: '提示',
|
||||
message: '关闭后插槽内的内容将被清空,是否继续?',
|
||||
status: 'info',
|
||||
exec: () => {
|
||||
slotList.value[idx].bind = false
|
||||
operateNode({ type: 'delete', id: slotData[name].value[0].id })
|
||||
|
||||
delete slotData[name]
|
||||
emit('update:modelValue', slotData)
|
||||
const [propsName] = path.split('.')
|
||||
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
setProp(propsName, schema.props[propsName])
|
||||
}
|
||||
})
|
||||
} else {
|
||||
slotList.value[idx].bind = true
|
||||
}
|
||||
|
||||
emit('update:modelValue', slotData)
|
||||
|
||||
// 更新当前选中组件的根属性,不根新在jsslot中的数据非响应式
|
||||
const [propsName] = path.split('.')
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
for (const data of Object.values(slotData)) {
|
||||
if (data.value?.[0] && !data.value[0]?.id) {
|
||||
// 手动设置节点
|
||||
setNode(data.value[0], schema)
|
||||
}
|
||||
}
|
||||
|
||||
setProp(propsName, schema.props[propsName])
|
||||
}
|
||||
|
||||
const validParams = (paramsPath, slot) => {
|
||||
const setParams = (slot) => {
|
||||
if (slot.bind) {
|
||||
const slotData = props.modelValue
|
||||
const { params, name } = slot
|
||||
|
||||
if (params.length) {
|
||||
slotData[name].params = params.split(',')
|
||||
} else {
|
||||
delete slotData[name].params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validParams = (slot, paramsPath) => {
|
||||
slotRef.value.validateField([paramsPath], (error) => {
|
||||
if (!error) {
|
||||
slot.bind && setSlotParams(slot)
|
||||
setParams(slot)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -171,12 +181,13 @@ export default {
|
|||
})
|
||||
|
||||
return {
|
||||
toggleSlot,
|
||||
updateSlot,
|
||||
slotList,
|
||||
paramsPropPath,
|
||||
slotRef,
|
||||
paramsStringValidator,
|
||||
validParams,
|
||||
setParams,
|
||||
state,
|
||||
componentsMap
|
||||
}
|
||||
|
|
@ -202,6 +213,7 @@ export default {
|
|||
|
||||
.use-slot-item-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.use-slot-switch-wrap {
|
||||
|
|
@ -227,6 +239,7 @@ export default {
|
|||
|
||||
.slot-name-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
|
|
@ -240,6 +253,7 @@ export default {
|
|||
position: relative;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
|
|
@ -262,7 +276,7 @@ export default {
|
|||
.e__switch-core::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
top: 2px;
|
||||
left: 1px;
|
||||
border-radius: 100%;
|
||||
transition: all 0.3s;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<script>
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import { SplitPanes, Pane } from '@opentiny/tiny-engine-common'
|
||||
import { useProperties } from '@opentiny/tiny-engine-meta-register'
|
||||
import { useProperties, useCanvas } from '@opentiny/tiny-engine-meta-register'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -195,6 +195,18 @@ export default {
|
|||
const spans = item.split(':')
|
||||
mode.value = item
|
||||
|
||||
const { operateNode } = useCanvas()
|
||||
const schema = useProperties().getSchema()
|
||||
|
||||
if (!schema) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error! There is no node selected. 错误!当前没有节点被选中。')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const cols = [...(schema?.children || [])]
|
||||
|
||||
spans.forEach((span, index) => {
|
||||
if (cols[index]) {
|
||||
cols[index].props.span = span
|
||||
|
|
@ -217,7 +229,7 @@ export default {
|
|||
}
|
||||
})
|
||||
|
||||
cols.length = spans.length
|
||||
operateNode({ type: 'updateAttributes', id: schema.id, value: { children: cols.slice(0, spans.length) } })
|
||||
}
|
||||
|
||||
const modeClick = (index, item) => {
|
||||
|
|
@ -268,7 +280,7 @@ export default {
|
|||
box-sizing: border-box;
|
||||
width: 48px;
|
||||
border: 1px solid var(--ti-lowcode-tabs-border-color);
|
||||
background: var(--ti-lowcode-canvas-wrap-bg);
|
||||
background: var(--te-common-bg-container);
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
margin: 0 4px 4px 4px;
|
||||
|
|
@ -313,13 +325,13 @@ export default {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background: var(--ti-lowcode-canvas-wrap-bg);
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
background: var(--te-common-bg-container);
|
||||
color: var(--te-common-text-secondary);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
:deep(.splitpanes__splitter) {
|
||||
background: var(--ti-lowcode-toolbar-view-hover-bg);
|
||||
background: var(--te-common-bg-container);
|
||||
border-left: var(--ti-lowcode-tabs-border-color);
|
||||
}
|
||||
|
||||
|
|
@ -328,7 +340,7 @@ export default {
|
|||
|
||||
.customize {
|
||||
margin-bottom: 8px;
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
color: var(--te-common-text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default {
|
|||
let newValue = value
|
||||
|
||||
if (value?.type === CONSTANT.JSEXPRESSION) {
|
||||
const pageSchema = useCanvas().canvasApi.value.getSchema()
|
||||
const pageSchema = useCanvas().getSchema()
|
||||
const stateName = value?.value?.replace(CONSTANT.STATE, '')
|
||||
newValue = pageSchema?.state?.[stateName]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,15 +107,15 @@ export default {
|
|||
padding-right: 20px;
|
||||
width: 136px;
|
||||
cursor: pointer;
|
||||
background: var(--ti-lowcode-tabs-bg);
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
background: var(--te-common-bg-prompt);
|
||||
color: var(--te-common-text-secondary);
|
||||
border: 1px solid var(--ti-lowcode-tabs-active-bg);
|
||||
border-radius: 3px;
|
||||
.icon-box {
|
||||
border-right: 1px solid var(--ti-lowcode-tabs-active-bg);
|
||||
padding: 4px 8px;
|
||||
.tiny-svg {
|
||||
color: var(--ti-lowcode-toolbar-breadcrumb-color);
|
||||
color: var(--te-common-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue