refactor: events meta app support custom common events and components (#586)

* refactor: events meta app support custom common events and components

* refactor: 事件绑定弹窗提取面板左右两部分组件

* fix: remove unnecessary imports

* fix: remove empty function

* refactor: rename component name

* fix: fix theme imports

* refactor: import self meta id
This commit is contained in:
Gene 2024-06-26 16:31:21 +08:00 committed by GitHub
parent 38f7ca58e4
commit 3258b26c2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 386 additions and 288 deletions

View File

@ -12,8 +12,18 @@
import entry from './src/Main.vue'
import metaData from './meta'
import { commonEvents } from './src/commonjs/events.js'
import BindEventsDialogContent from './src/components/BindEventsDialogContent.vue'
import BindEventsDialogSidebar from './src/components/BindEventsDialogSidebar.vue'
export default {
...metaData,
entry
entry,
options: {
commonEvents
},
components: {
BindEventsDialogSidebar,
BindEventsDialogContent
}
}

View File

@ -87,11 +87,11 @@
<script>
import { computed, reactive, watchEffect } from 'vue'
import { Popover, Button } from '@opentiny/vue'
import { useCanvas, useModal, useLayout, useBlock, useResource } from '@opentiny/tiny-engine-controller'
import { useModal } from '@opentiny/tiny-engine-controller'
import { getMergeMeta, useCanvas, useLayout, useBlock, useResource } from '@opentiny/tiny-engine-entry'
import { BlockLinkEvent, SvgButton } from '@opentiny/tiny-engine-common'
import { iconHelpQuery, iconChevronDown } from '@opentiny/vue-icon'
import BindEventsDialog, { open as openDialog } from './BindEventsDialog.vue'
import { commonEvents } from '../commonjs/events.js'
import AddEventsDialog from './AddEventsDialog.vue'
export default {
@ -115,6 +115,8 @@ export default {
const { highlightMethod } = getPluginApi(PLUGIN_NAME.PageController)
const { commonEvents = {} } = getMergeMeta('engine.setting.event').options
const state = reactive({
eventName: '', //
eventBinding: null, //
@ -131,7 +133,7 @@ export default {
const componentName = pageState?.currentSchema?.componentName
const componentSchema = getMaterial(componentName)
state.componentEvent = componentSchema?.content?.schema?.events || componentSchema?.schema?.events || {}
Object.assign(state.componentEvents, state.componentEvent)
state.componentEvents = { ...commonEvents, ...state.componentEvent }
const props = pageState?.currentSchema?.props || {}
const keys = Object.keys(props)
state.bindActions = {}

View File

@ -8,72 +8,8 @@
@opened="openedDialog"
>
<div class="bind-event-dialog-content">
<div class="dialog-content-left">
<div class="left-title">响应方法</div>
<div class="left-list-wrap">
<div class="left-action-list">
<tiny-search v-model="state.searchValue" placeholder="搜索"></tiny-search>
<ul class="action-list-wrap">
<li v-for="item in state.filterMethodList" :key="item.name" @click="selectMethod(item)">
<div :class="['action-name', { active: item.name === state.bindMethodInfo.name }]">
{{ item.title || item.name }}
<icon-yes v-if="item.name === state.bindMethodInfo.name" class="action-selected-icon"></icon-yes>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="content-right">
<div :class="['content-right-top', { 'tip-error': state.tipError }]">
<div class="content-right-title">方法名称</div>
<tiny-input
v-model="state.bindMethodInfo.name"
:disabled="state.bindMethodInfo.type !== NEW_METHOD_TYPE"
:class="[{ 'status-error': state.tipError }]"
placeholder="请从左侧选择一个方法进行绑定,或者选择添加新方法,输入自定义方法名称。"
@update:modelValue="change"
></tiny-input>
<div class="new-action-tip">{{ state.tip }}</div>
</div>
<div :class="['content-right-bottom', { 'tip-error': !state.isValidParams }]">
<div class="content-right-title">
<span class="set-params-tip">扩展参数设置</span>
<tiny-popover placement="top-start" width="350" trigger="hover">
<template #reference>
<icon-help-query></icon-help-query>
</template>
<p>
扩展参数调用当前事件传入的真实参数数组格式追加在原有事件参数之后<br />
:
{{ state.bindMethodInfo.name }}(eventArgs, extParam1, extParam2, ...)
</p>
</tiny-popover>
<tiny-switch v-model="state.enableExtraParams" class="set-switch" :show-text="true">
<template #open>
<span>开启</span>
</template>
<template #close>
<span>关闭</span>
</template>
</tiny-switch>
</div>
<div class="content-right-monaco">
<monaco-editor
v-if="dialogVisible"
ref="editor"
:value="state.editorContent"
:options="editorOptions"
class="monaco-editor"
/>
<div v-if="!state.enableExtraParams" class="mark"></div>
</div>
<div v-if="!state.isValidParams && state.enableExtraParams" class="params-tip">
请输入数组格式的参数参数可以为表达式例如["extParam1", "item.status", 1, "getNames()"]
</div>
</div>
</div>
<component :is="BindEventsDialogSidebar" :eventBinding="eventBinding"></component>
<component :is="BindEventsDialogContent" :dialogVisible="dialogVisible"></component>
</div>
<template #footer>
<div class="bind-dialog-footer">
@ -85,12 +21,12 @@
</template>
<script>
import { reactive, ref, watchEffect, nextTick } from 'vue'
import { VueMonaco } from '@opentiny/tiny-engine-common'
import { Button, DialogBox, Input, Search, Popover, Switch } from '@opentiny/vue'
import { useCanvas, useHistory, useLayout } from '@opentiny/tiny-engine-controller'
import { string2Ast, ast2String } from '@opentiny/tiny-engine-controller/js/ast'
import { iconYes, iconHelpQuery } from '@opentiny/vue-icon'
import { ast2String, string2Ast } from '@opentiny/tiny-engine-controller/js/ast'
import { getMergeMeta, useCanvas, useHistory, useLayout } from '@opentiny/tiny-engine-entry'
import { Button, DialogBox } from '@opentiny/vue'
import { nextTick, provide, reactive, ref } from 'vue'
import { METHOD_TIPS_MAP } from './constants'
import meta from '../../meta'
const dialogVisible = ref(false)
@ -102,28 +38,10 @@ export const close = () => {
dialogVisible.value = false
}
const NEW_METHOD_TYPE = 'newMethod'
const invalidVarNameCharRE = /[^0-9a-zA-Z_$]/
const validVarNameRE = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/
const METHOD_TIPS_MAP = {
default: '选择已有方法或者添加新方法(点击 确定 之后将在JS面板中创建一个该名称的新方法)',
exist: '方法名称已存在',
ruleInvalid: '请输入有效的方法名,可以由字母、数字、下划线、$ 符号组成,不能以数字开头',
empty: '方法名称不能为空'
}
export default {
components: {
MonacoEditor: VueMonaco,
TinyInput: Input,
TinyButton: Button,
TinySearch: Search,
TinyPopover: Popover,
TinyDialogBox: DialogBox,
IconYes: iconYes(),
IconHelpQuery: iconHelpQuery(),
TinySwitch: Switch
TinyDialogBox: DialogBox
},
inheritAttrs: false,
props: {
@ -133,73 +51,22 @@ export default {
}
},
setup(props) {
const { BindEventsDialogSidebar, BindEventsDialogContent } = getMergeMeta(meta.id).components
const { PLUGIN_NAME, getPluginApi, activePlugin } = useLayout()
const { pageState } = useCanvas()
const { getMethodNameList, getMethods, saveMethod, highlightMethod } = getPluginApi(PLUGIN_NAME.PageController)
const editor = ref(null)
const { getMethods, saveMethod, highlightMethod } = getPluginApi(PLUGIN_NAME.PageController)
const state = reactive({
searchValue: '',
editorContent: '',
bindMethodInfo: {},
filterMethodList: [],
tip: METHOD_TIPS_MAP.default,
tipError: false,
enableExtraParams: false,
isValidParams: true
})
const editorOptions = {
language: 'json',
lineNumbers: false,
minimap: {
enabled: false
}
}
const generateMethodName = (nameList, eventName) => {
const max = nameList
.map((name) => Number.parseInt(name.match(/\d+$/)?.[0]) || 0)
.sort((a, b) => a - b)
.pop()
const functionName = eventName?.replace(invalidVarNameCharRE, '_') || ''
let name = `${functionName}New`
if (max > -1) {
name += `${max + 1}`
}
return name
}
watchEffect(() => {
const eventName = props.eventBinding?.eventName
const nameList = getMethodNameList?.().filter((action) => action.indexOf(eventName) > -1) || []
const newMethodName = generateMethodName(nameList, eventName)
const newMethod = {
title: '添加新方法',
name: newMethodName,
type: NEW_METHOD_TYPE
}
if (props.eventBinding?.ref) {
state.bindMethodInfo = {
name: props.eventBinding.ref
}
} else {
state.bindMethodInfo = newMethod
}
const methodList =
getMethodNameList?.()
.filter((item) => item.indexOf(state.searchValue) > -1)
.map((name) => ({ name })) || []
state.filterMethodList = [newMethod, ...methodList]
})
provide('context', state)
const selectMethod = (data) => {
state.bindMethodInfo = data
@ -243,40 +110,14 @@ export default {
const resetTipError = () => {
state.tipError = false
state.tip = METHOD_TIPS_MAP.default
}
const validMethodNameEmpty = (name) => !name
const validMethodNameExist = (name) => getMethodNameList?.().includes(name)
const invalidMethodName = (name) => !validVarNameRE.test(name)
const change = (value) => {
const validRules = [
{ validator: validMethodNameEmpty, tip: METHOD_TIPS_MAP.empty },
{ validator: validMethodNameExist, tip: METHOD_TIPS_MAP.exist },
{ validator: invalidMethodName, tip: METHOD_TIPS_MAP.ruleInvalid }
]
for (let i = 0; i < validRules.length; i++) {
const rule = validRules[i]
if (rule.validator(value)) {
state.tipError = true
state.tip = rule.tip
//
return
}
}
state.tipError = false
state.tip = METHOD_TIPS_MAP.default
state.isValidParams = true
}
const getExtraParams = () => {
let extraParams = ''
if (state.enableExtraParams) {
try {
const inputParams = editor.value?.getEditor().getValue()
extraParams = JSON.parse(inputParams)
extraParams = JSON.parse(state.editorContent)
state.isValidParams = Array.isArray(extraParams)
} catch (error) {
state.isValidParams = false
@ -365,12 +206,10 @@ export default {
}
return {
NEW_METHOD_TYPE,
BindEventsDialogSidebar,
BindEventsDialogContent,
state,
editor,
editorOptions,
dialogVisible,
change,
confirm,
closeDialog,
openedDialog,
@ -384,111 +223,5 @@ export default {
.bind-event-dialog-content {
display: flex;
min-width: 700px;
.dialog-content-left {
margin-right: 30px;
width: 30%;
display: flex;
flex-direction: column;
.left-title {
font-weight: 600;
}
.left-list-wrap {
border: 1px solid var(--ti-lowcode-bind-event-dialog-content-left-border-color);
border-radius: 4px;
height: 300px;
margin-top: 12px;
display: flex;
flex: 1;
.left-action-list {
flex: 1;
padding: 12px;
.action-list-wrap {
height: 250px;
margin-top: 8px;
overflow: auto;
}
.action-name {
display: flex;
justify-content: space-between;
padding: 8px 12px;
cursor: pointer;
&.active {
background: var(--ti-lowcode-bind-event-dialog-content-left-list-item-active-bg-color);
}
.action-selected-icon {
font-size: 14px;
color: var(--ti-lowcode-bind-event-dialog-action-selected-icon-color);
}
}
}
}
}
.content-right {
width: 68%;
.content-right-top {
.new-action-tip {
margin: 8px 0;
color: var(--ti-lowcode-bind-event-dialog-new-action-tip-color);
}
}
.content-right-bottom {
.content-right-monaco {
border: 1px solid var(--ti-lowcode-bind-event-dialog-content-right-monaco-border-color);
overflow: hidden;
position: relative;
.monaco-editor {
width: 100%;
height: 216px;
padding: 12px 8px;
color: var(--ti-lowcode-toolbar-breadcrumb-color);
}
.mark {
width: 100%;
height: 216px;
position: absolute;
z-index: 1;
top: 0;
background-color: var(--ti-lowcode-bind-event-dialog-mark-bg-color);
}
}
.params-tip {
margin: 8px 0;
color: var(--ti-lowcode-error-tip-color);
}
}
.content-right-top .content-right-title,
.content-right-bottom .content-right-title {
font-weight: 600;
margin-bottom: 12px;
.set-params-tip {
margin-right: 3px;
}
.set-switch {
width: 60px;
margin-left: 10px;
}
}
.tip-error {
.content-right-monaco {
border: 1px solid var(--ti-lowcode-error-tip-color);
}
.params-tip,
.new-action-tip {
color: var(--ti-lowcode-error-tip-color);
}
}
}
}
</style>

View File

@ -0,0 +1,195 @@
<template>
<div class="content-right">
<div :class="['content-right-top', { 'tip-error': context.tipError }]">
<div class="content-right-title">方法名称</div>
<tiny-input
v-model="context.bindMethodInfo.name"
:disabled="context.bindMethodInfo.type !== NEW_METHOD_TYPE"
:class="[{ 'status-error': context.tipError }]"
placeholder="请从左侧选择一个方法进行绑定,或者选择添加新方法,输入自定义方法名称。"
@update:modelValue="change"
></tiny-input>
<div class="new-action-tip">{{ context.tip }}</div>
</div>
<div :class="['content-right-bottom', { 'tip-error': !context.isValidParams }]">
<div class="content-right-title">
<span class="set-params-tip">扩展参数设置</span>
<tiny-popover placement="top-start" width="350" trigger="hover">
<template #reference>
<icon-help-query></icon-help-query>
</template>
<p>
扩展参数调用当前事件传入的真实参数数组格式追加在原有事件参数之后<br />
:
{{ context.bindMethodInfo.name }}(eventArgs, extParam1, extParam2, ...)
</p>
</tiny-popover>
<tiny-switch v-model="context.enableExtraParams" class="set-switch" :show-text="true">
<template #open>
<span>开启</span>
</template>
<template #close>
<span>关闭</span>
</template>
</tiny-switch>
</div>
<div class="content-right-monaco">
<monaco-editor
v-if="dialogVisible"
:value="context.editorContent"
:options="editorOptions"
@change="editorContentChange"
class="monaco-editor"
/>
<div v-if="!context.enableExtraParams" class="mark"></div>
</div>
<div v-if="!context.isValidParams && context.enableExtraParams" class="params-tip">
请输入数组格式的参数参数可以为表达式例如["extParam1", "item.status", 1, "getNames()"]
</div>
</div>
</div>
</template>
<script>
import { VueMonaco } from '@opentiny/tiny-engine-common'
import { theme } from '@opentiny/tiny-engine-controller/js/monaco'
import { useLayout } from '@opentiny/tiny-engine-entry'
import { Input, Popover, Switch } from '@opentiny/vue'
import { iconHelpQuery } from '@opentiny/vue-icon'
import { inject } from 'vue'
import { METHOD_TIPS_MAP, NEW_METHOD_TYPE, VALID_VARNAME_RE } from './constants'
export default {
components: {
MonacoEditor: VueMonaco,
TinyInput: Input,
TinyPopover: Popover,
IconHelpQuery: iconHelpQuery(),
TinySwitch: Switch
},
props: {
dialogVisible: Boolean
},
setup() {
const { PLUGIN_NAME, getPluginApi } = useLayout()
const { getMethodNameList } = getPluginApi(PLUGIN_NAME.PageController)
const context = inject('context')
const editorOptions = {
roundedSelection: true,
automaticLayout: true,
autoIndent: true,
language: 'json',
formatOnPaste: true,
tabSize: 2,
theme: theme(),
lineNumbers: false,
minimap: {
enabled: false
}
}
const editorContentChange = (content) => {
context.editorContent = content
}
const validMethodNameEmpty = (name) => !name
const validMethodNameExist = (name) => getMethodNameList?.().includes(name)
const invalidMethodName = (name) => !VALID_VARNAME_RE.test(name)
const change = (value) => {
const validRules = [
{ validator: validMethodNameEmpty, tip: METHOD_TIPS_MAP.empty },
{ validator: validMethodNameExist, tip: METHOD_TIPS_MAP.exist },
{ validator: invalidMethodName, tip: METHOD_TIPS_MAP.ruleInvalid }
]
for (let i = 0; i < validRules.length; i++) {
const rule = validRules[i]
if (rule.validator(value)) {
context.tipError = true
context.tip = rule.tip
//
return
}
}
context.tipError = false
context.tip = METHOD_TIPS_MAP.default
}
return {
NEW_METHOD_TYPE,
context,
editorOptions,
change,
editorContentChange
}
}
}
</script>
<style lang="less" scoped>
.content-right {
width: 68%;
.content-right-top {
.new-action-tip {
margin: 8px 0;
color: var(--ti-lowcode-bind-event-dialog-new-action-tip-color);
}
}
.content-right-bottom {
.content-right-monaco {
border: 1px solid var(--ti-lowcode-bind-event-dialog-content-right-monaco-border-color);
overflow: hidden;
position: relative;
.monaco-editor {
width: 100%;
height: 216px;
padding: 12px 8px;
color: var(--ti-lowcode-toolbar-breadcrumb-color);
}
.mark {
width: 100%;
height: 216px;
position: absolute;
z-index: 1;
top: 0;
background-color: var(--ti-lowcode-bind-event-dialog-mark-bg-color);
}
}
.params-tip {
margin: 8px 0;
color: var(--ti-lowcode-error-tip-color);
}
}
.content-right-top .content-right-title,
.content-right-bottom .content-right-title {
font-weight: 600;
margin-bottom: 12px;
.set-params-tip {
margin-right: 3px;
}
.set-switch {
width: 60px;
margin-left: 10px;
}
}
.tip-error {
.content-right-monaco {
border: 1px solid var(--ti-lowcode-error-tip-color);
}
.params-tip,
.new-action-tip {
color: var(--ti-lowcode-error-tip-color);
}
}
}
</style>

View File

@ -0,0 +1,148 @@
<template>
<div class="dialog-content-left">
<div class="left-title">响应方法</div>
<div class="left-list-wrap">
<div class="left-action-list">
<tiny-search v-model="searchValue" placeholder="搜索"></tiny-search>
<ul class="action-list-wrap">
<li v-for="item in filteredMethodList" :key="item.name" @click="selectMethod(item)">
<div :class="['action-name', { active: item.name === context.bindMethodInfo.name }]">
{{ item.title || item.name }}
<icon-yes v-if="item.name === context.bindMethodInfo.name" class="action-selected-icon"></icon-yes>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import { useLayout } from '@opentiny/tiny-engine-entry'
import { Search } from '@opentiny/vue'
import { iconYes } from '@opentiny/vue-icon'
import { inject, ref, watchEffect } from 'vue'
import { INVALID_VARNAME_CHAR_RE, NEW_METHOD_TYPE } from './constants'
export default {
components: {
TinySearch: Search,
IconYes: iconYes()
},
props: {
eventBinding: {
type: Object,
default: () => ({})
}
},
setup(props) {
const { PLUGIN_NAME, getPluginApi } = useLayout()
const { getMethodNameList } = getPluginApi(PLUGIN_NAME.PageController)
const searchValue = ref('')
const filteredMethodList = ref([])
const context = inject('context')
const generateMethodName = (nameList, eventName) => {
const max = nameList
.map((name) => Number.parseInt(name.match(/\d+$/)?.[0]) || 0)
.sort((a, b) => a - b)
.pop()
const functionName = eventName?.replace(INVALID_VARNAME_CHAR_RE, '_') || ''
let name = `${functionName}New`
if (max > -1) {
name += `${max + 1}`
}
return name
}
const selectMethod = (data) => {
context.bindMethodInfo = data
}
watchEffect(() => {
const eventName = props.eventBinding?.eventName
const nameList = getMethodNameList?.().filter((action) => action.includes(eventName)) || []
const newMethodName = generateMethodName(nameList, eventName)
const newMethod = {
title: '添加新方法',
name: newMethodName,
type: NEW_METHOD_TYPE
}
if (props.eventBinding?.ref) {
selectMethod({ name: props.eventBinding.ref })
} else {
selectMethod(newMethod)
}
const methodList =
getMethodNameList?.()
.filter((item) => item.includes(searchValue.value))
.map((name) => ({ name })) || []
filteredMethodList.value = [newMethod, ...methodList]
})
return {
context,
searchValue,
filteredMethodList,
selectMethod
}
}
}
</script>
<style lang="less" scoped>
.dialog-content-left {
margin-right: 30px;
width: 30%;
display: flex;
flex-direction: column;
.left-title {
font-weight: 600;
}
.left-list-wrap {
border: 1px solid var(--ti-lowcode-bind-event-dialog-content-left-border-color);
border-radius: 4px;
height: 300px;
margin-top: 12px;
display: flex;
flex: 1;
.left-action-list {
flex: 1;
padding: 12px;
.action-list-wrap {
height: 250px;
margin-top: 8px;
overflow: auto;
}
.action-name {
display: flex;
justify-content: space-between;
padding: 8px 12px;
cursor: pointer;
&.active {
background: var(--ti-lowcode-bind-event-dialog-content-left-list-item-active-bg-color);
}
.action-selected-icon {
font-size: 14px;
color: var(--ti-lowcode-bind-event-dialog-action-selected-icon-color);
}
}
}
}
}
</style>

View File

@ -0,0 +1,10 @@
export const NEW_METHOD_TYPE = 'newMethod'
export const INVALID_VARNAME_CHAR_RE = /[^0-9a-zA-Z_$]/
export const VALID_VARNAME_RE = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/
export const METHOD_TIPS_MAP = {
default: '选择已有方法或者添加新方法(点击 确定 之后将在JS面板中创建一个该名称的新方法)',
exist: '方法名称已存在',
ruleInvalid: '请输入有效的方法名,可以由字母、数字、下划线、$ 符号组成,不能以数字开头',
empty: '方法名称不能为空'
}