Skip to content

Commit 068a6f3

Browse files
authored
feat(Block): implement property selector with two-way binding (opentiny#1453)
1 parent 54e0b62 commit 068a6f3

1 file changed

Lines changed: 122 additions & 7 deletions

File tree

packages/plugins/block/src/BlockEventForm.vue

Lines changed: 122 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
<template>
22
<tiny-form label-position="left" label-width="60px" show-message :model="formData" :rules="rules">
33
<tiny-form-item label="事件名" prop="eventName">
4-
<tiny-input v-model="formData.eventName" :placeholder="eventNameTip" @blur="changeEventName"></tiny-input>
4+
<tiny-input class="event-name" v-model="formData.eventName" :placeholder="eventNameTip" @blur="changeEventName">
5+
<template #suffix>
6+
<tiny-popover placement="bottom-end" trigger="hover" :close-delay="0" @show="state.showPopover = true">
7+
<template #reference>
8+
<tiny-icon-rich-text-link :class="{ 'bind-propertys': isUpdateEvent }" />
9+
</template>
10+
<div class="property-list" v-show="state.showPopover">
11+
<div class="property-list-title">
12+
<tiny-icon-rich-text-link />
13+
双向绑定属性
14+
</div>
15+
<ul class="property-list-content">
16+
<li
17+
v-for="(item, index) in propertys"
18+
:key="index"
19+
:class="{ existed: eventNameList.has(`onUpdate:${item.property}`) }"
20+
@click="usePropertysToBeEvent(item)"
21+
>
22+
<div>{{ item.property }}</div>
23+
</li>
24+
</ul>
25+
</div>
26+
</tiny-popover>
27+
</template>
28+
</tiny-input>
529
</tiny-form-item>
630
<tiny-form-item label="标签名">
731
<tiny-input v-model="label"></tiny-input>
@@ -16,19 +40,34 @@
1640
<script lang="ts">
1741
/* metaService: engine.plugins.blockmanage.BlockEventForm */
1842
import { computed, reactive, watch } from 'vue'
19-
import { Input as TinyInput, Form as TinyForm, FormItem as TinyFormItem } from '@opentiny/vue'
43+
import { Input as TinyInput, Form as TinyForm, FormItem as TinyFormItem, TinyPopover } from '@opentiny/vue'
2044
import { REGEXP_EVENT_NAME, verifyEventName } from '@opentiny/tiny-engine-common/js/verification'
21-
import { getEditEvent, getEditEventName, renameBlockEventName } from './js/blockSetting'
45+
import {
46+
getEditBlockPropertyList,
47+
getEditBlockEvents,
48+
getEditEvent,
49+
getEditEventName,
50+
renameBlockEventName
51+
} from './js/blockSetting'
52+
import { IconRichTextLink } from '@opentiny/vue-icon'
2253
2354
export default {
2455
components: {
2556
TinyForm,
2657
TinyInput,
27-
TinyFormItem
58+
TinyFormItem,
59+
TinyPopover,
60+
TinyIconRichTextLink: IconRichTextLink()
2861
},
2962
setup() {
30-
const eventNameTip = '事件名为小写字符开头的驼峰形式,例:customEvent'
63+
const propertys = computed(() => getEditBlockPropertyList())
64+
const events = computed(() => getEditBlockEvents())
65+
const state = reactive({
66+
showPopover: false
67+
})
68+
const eventNameTip = '事件名为小写字符或onUpdate:开头的驼峰形式,例:customEvent, onUpdate:propsName'
3169
const linked = computed(() => (getEditEvent() || {}).linked)
70+
const eventNameList = computed(() => new Set(Object.keys(events.value)))
3271
3372
const label = computed({
3473
get: () => getEditEvent()?.label?.zh_CN || '',
@@ -56,8 +95,35 @@ export default {
5695
eventName: getEditEventName() || ''
5796
})
5897
98+
const isUpdateEvent = computed(() => formData.eventName.startsWith('onUpdate:'))
99+
100+
const usePropertysToBeEvent = (item: any) => {
101+
if (item.label?.text?.zh_CN) label.value = item.label.text.zh_CN
102+
renameBlockEventName(`onUpdate:${item.property}`, getEditEventName())
103+
state.showPopover = false
104+
}
105+
59106
const rules = {
60-
eventName: [{ pattern: REGEXP_EVENT_NAME, message: eventNameTip, trigger: 'change' }]
107+
eventName: [
108+
{
109+
pattern: REGEXP_EVENT_NAME,
110+
validator: (rule: any /* IFormInnerRule */, value: string, callback: (e?: Error) => void) => {
111+
if (isUpdateEvent.value) {
112+
const matched = /^onUpdate:[a-zA-Z_$][\w$]*$/.test(value)
113+
const propertyMatched = propertys.value.some((item) => item.property === value.replace('onUpdate:', ''))
114+
return matched && propertyMatched
115+
? callback()
116+
: callback(new Error(`${value} 需要有对应的 ${value.replace('onUpdate:', '')} 在属性中定义`))
117+
}
118+
if (!rule.pattern.test(value)) {
119+
callback(new Error(eventNameTip))
120+
} else {
121+
callback()
122+
}
123+
},
124+
trigger: 'change'
125+
}
126+
]
61127
}
62128
63129
watch(
@@ -74,13 +140,19 @@ export default {
74140
}
75141
76142
return {
143+
state,
144+
propertys,
145+
events,
77146
rules,
78147
label,
79148
linked,
80149
formData,
81150
description,
82151
eventNameTip,
83-
changeEventName
152+
eventNameList,
153+
isUpdateEvent,
154+
changeEventName,
155+
usePropertysToBeEvent
84156
}
85157
}
86158
}
@@ -92,4 +164,47 @@ export default {
92164
padding: 15px 0px;
93165
border-top: 1px solid var(--te-block-event-link-border-color);
94166
}
167+
168+
.event-name {
169+
:deep(.tiny-input__inner.tiny-input__inner) {
170+
padding-right: var(--tv-Input-suffix-padding-right);
171+
padding-left: var(--tv-Input-suffix-padding-left);
172+
}
173+
.bind-propertys {
174+
fill: var(--te-component-common-block-add-text-color);
175+
}
176+
}
177+
178+
.property-list {
179+
.property-list-title {
180+
display: flex;
181+
align-items: center;
182+
font-weight: bold;
183+
text-align: left;
184+
gap: 6px;
185+
margin-bottom: 6px;
186+
}
187+
.property-list-content {
188+
max-height: 200px;
189+
overflow-y: scroll;
190+
overflow-x: hidden;
191+
padding-inline-start: 10px;
192+
padding-inline-end: 10px;
193+
}
194+
li {
195+
padding: 0 12px;
196+
margin: 0 -16px;
197+
line-height: 24px;
198+
cursor: pointer;
199+
&:hover {
200+
background: var(--te-component-common-bg-color-hover);
201+
}
202+
}
203+
204+
.existed {
205+
cursor: not-allowed;
206+
pointer-events: none;
207+
color: var(--te-component-common-text-color-disabled);
208+
}
209+
}
95210
</style>

0 commit comments

Comments
 (0)