forked from opentiny/tiny-engine
715 lines
22 KiB
Vue
715 lines
22 KiB
Vue
<template>
|
||
<slot>
|
||
<span :class="['icon', { 'icon-bind': modelValue?.variable }]" @click="open">
|
||
<svg-button name="cloud-shell" placement="top" tips="变量绑定"></svg-button>
|
||
</span>
|
||
</slot>
|
||
|
||
<tiny-dialog-box
|
||
v-if="dialogShouldInitialize"
|
||
:visible="state.isVisible"
|
||
title="变量绑定"
|
||
width="48%"
|
||
:append-to-body="true"
|
||
class="meta-bind-variable-dialog-box"
|
||
@update:visible="state.isVisible = $event"
|
||
@close="cancel"
|
||
>
|
||
<div class="bind-dialog-container">
|
||
<div class="bind-dialog-container-header">
|
||
<tiny-alert
|
||
type="info"
|
||
description="你可以通过点击左侧区域变量列表绑定变量或处理函数,也可以在右边输入模式输入复杂的表达式。"
|
||
class="header-alert"
|
||
></tiny-alert>
|
||
</div>
|
||
<div class="bind-dialog-content">
|
||
<div class="content-left">
|
||
<span class="content-left__title">变量列表</span>
|
||
<div class="list-wrap">
|
||
<ul class="content-left__list">
|
||
<li
|
||
v-for="item in state.variableList"
|
||
:key="item.id"
|
||
:class="{ 'content-left__list-item': true, active: item.id === state.active }"
|
||
@click="selectItem(item)"
|
||
>
|
||
{{ item.content }}
|
||
</li>
|
||
</ul>
|
||
<div class="item-content">
|
||
<tiny-search v-model="state.value" placeholder="搜索"></tiny-search>
|
||
<div class="item-content-list lowcode-scrollbar-thin">
|
||
<ul>
|
||
<li
|
||
v-for="(item, key) in state.variables"
|
||
v-show="key.includes(state.value)"
|
||
:key="key"
|
||
:class="{ 'item-selected': state.variableName === key }"
|
||
@click="variableClick(key, item)"
|
||
>
|
||
<div class="item-text" :title="state.bindPrefix + key">{{ `${state.bindPrefix}${key}` }}</div>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="content-right">
|
||
<div class="content-detail-header">
|
||
<div class="header-name">
|
||
<span class="content-right__title">变量</span>
|
||
</div>
|
||
</div>
|
||
<div class="content-wrap">
|
||
<div v-if="!state.isEditorEditMode" class="top">{{ state.variable }}</div>
|
||
<div v-else :class="['top', { 'poll-top': isDataSource }]">
|
||
<monaco-editor
|
||
ref="editor"
|
||
:value="state.variable"
|
||
:options="editorOptions"
|
||
@editorDidMount="editorDidMount"
|
||
></monaco-editor>
|
||
<div v-if="isDataSource" class="datasource-poll-wrap">
|
||
<tiny-tooltip
|
||
placement="top"
|
||
content="定时更新开启后,页面运行时将会定期请求远程数据源,实现数据定时更新。"
|
||
><span>定时更新:</span></tiny-tooltip
|
||
>
|
||
<tiny-switch v-model="state.isPoll"></tiny-switch>
|
||
<div v-if="state.isPoll" class="datasource-poll-interval">
|
||
<span>更新时间:</span>
|
||
<tiny-input type="number" v-model="state.pollInterval"></tiny-input>
|
||
<span>ms</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="bottom lowcode-scrollbar-thin">
|
||
<h3>用法</h3>
|
||
<div class="bottom-demo">
|
||
<p>
|
||
你可以通过点击左侧区域绑定变量或处理函数,或者点击右边的铅笔按钮切换到输入模式,输入复杂的表达式。
|
||
</p>
|
||
<p>输入框内默认支持变量,写法和 JS 写法完全一致。</p>
|
||
<div>页面状态: this.state.xxx</div>
|
||
<div>字符串: "string"</div>
|
||
<div>数字: 123</div>
|
||
<div>布尔值: true / false</div>
|
||
<div>对象: { name: "张三" }</div>
|
||
<div>数组: ["1", "2"]</div>
|
||
<div>空值: null</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<template #footer>
|
||
<div class="bind-dialog-footer">
|
||
<div class="left">
|
||
<tiny-button type="danger" plain @click="remove">移除绑定</tiny-button>
|
||
</div>
|
||
<div class="right">
|
||
<tiny-button @click="cancel">取 消</tiny-button>
|
||
<tiny-button type="info" @click="confirm">确 定</tiny-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</tiny-dialog-box>
|
||
</template>
|
||
|
||
<script>
|
||
import { reactive, ref, computed, nextTick, watch } from 'vue'
|
||
import { camelize, capitalize } from '@vue/shared'
|
||
import { Button, DialogBox, Search, Switch, Input, Tooltip, Alert } from '@opentiny/vue'
|
||
import { useHttp } from '@opentiny/tiny-engine-http'
|
||
import { getSchema, getGlobalState, setState, getNode, getCurrent } from '@opentiny/tiny-engine-canvas'
|
||
import { useCanvas, useResource, useLayout, useApp, useProperties, useData } from '@opentiny/tiny-engine-controller'
|
||
import { theme } from '@opentiny/tiny-engine-controller/adapter'
|
||
import { constants } from '@opentiny/tiny-engine-utils'
|
||
import SvgButton from './SvgButton.vue'
|
||
import { parse, traverse, generate } from '@opentiny/tiny-engine-controller/js/ast'
|
||
import { DEFAULT_LOOP_NAME } from '@opentiny/tiny-engine-controller/js/constants'
|
||
import MonacoEditor from './VueMonaco.vue'
|
||
import { formatString } from '@opentiny/tiny-engine-controller/js/ast'
|
||
|
||
const { EXPRESSION_TYPE } = constants
|
||
|
||
const CONSTANTS = {
|
||
THIS: 'this.',
|
||
STATE: 'this.state.',
|
||
STORE: 'this.stores.',
|
||
PROPS: 'this.props.',
|
||
COLLECTION: 'Collection',
|
||
ITEM: 'item',
|
||
DATASOUCE: 'datasource',
|
||
DATASOUCEPREFIX: '数据源: ',
|
||
DATASOURCEMAP: 'this.dataSourceMap.',
|
||
INTERVALID: 'intervalId'
|
||
}
|
||
|
||
const getJsSlot = () => {
|
||
const jsSlot = getNode(getCurrent()?.parent?.id, true)?.parent
|
||
|
||
return [jsSlot?.type === 'JSSlot', jsSlot]
|
||
}
|
||
|
||
const getJsSlotParams = () => {
|
||
const [isJsSlot, jsSlot] = getJsSlot()
|
||
return isJsSlot ? jsSlot?.params || [] : []
|
||
}
|
||
|
||
export default {
|
||
name: 'MetaBindVariable',
|
||
components: {
|
||
MonacoEditor,
|
||
TinyDialogBox: DialogBox,
|
||
TinyButton: Button,
|
||
TinySearch: Search,
|
||
TinySwitch: Switch,
|
||
TinyInput: Input,
|
||
TinyTooltip: Tooltip,
|
||
SvgButton,
|
||
TinyAlert: Alert
|
||
},
|
||
inheritAttrs: false,
|
||
props: {
|
||
name: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
modelValue: {
|
||
type: [String, Number, Boolean, Array, Object, Date],
|
||
default: ''
|
||
},
|
||
lazyLoad: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
setup(props, { emit }) {
|
||
const editor = ref(null)
|
||
const http = useHttp()
|
||
let oldValue = ''
|
||
|
||
const list = [
|
||
{ id: 'state', content: 'State 属性' },
|
||
{ id: 'store', content: '应用状态' },
|
||
{ id: 'function', content: '自定义处理函数' },
|
||
{ id: 'utils', content: '工具类' },
|
||
{ id: 'bridge', content: '桥接源' },
|
||
{ id: 'datasource', content: '数据源' }
|
||
]
|
||
|
||
const state = reactive({
|
||
isBlock: computed(() => useCanvas().isBlock()),
|
||
variables: {},
|
||
// 控制变量列表显示/隐藏
|
||
isVisible: false,
|
||
// 搜索框value
|
||
value: '',
|
||
active: 'state',
|
||
// 某一类型下的变量列表
|
||
variableList: computed(() => {
|
||
const extendedVars = []
|
||
const [isInJsSlot] = getJsSlot()
|
||
|
||
if (state.isBlock) {
|
||
extendedVars.push({ id: 'props', content: 'props' })
|
||
}
|
||
|
||
if (state.loopData) {
|
||
extendedVars.push({ id: 'loop', content: '循环变量' })
|
||
}
|
||
|
||
if (isInJsSlot) {
|
||
extendedVars.push({ id: 'slotScope', content: '暴露给插槽使用的变量' })
|
||
}
|
||
|
||
return [...list, ...extendedVars]
|
||
}),
|
||
// 绑定的变量名/变量表达式
|
||
variable: '',
|
||
// 绑定的变量指向的值内容
|
||
variableContent: null,
|
||
// 引用的state变量名
|
||
variableName: '',
|
||
// 编辑器状态:只读状态(false)、编辑状态(true)
|
||
isEditorEditMode: true,
|
||
dataSouce: null,
|
||
// 静态值
|
||
mock: props.modelValue?.value || props.modelValue,
|
||
bindPrefix: '',
|
||
loopData: null,
|
||
loopArgs: '',
|
||
isPoll: false,
|
||
pollInterval: 5000
|
||
})
|
||
|
||
const isDataSource = computed(() => state.active === CONSTANTS.DATASOUCE)
|
||
|
||
// 每次弹窗打开时都记录下绑定变量的旧值,用来判断保存按钮状态
|
||
watch(
|
||
() => state.isVisible,
|
||
(value) => {
|
||
if (value) {
|
||
oldValue = state.variable
|
||
state.loopData = useProperties().getSchema()?.loop
|
||
}
|
||
}
|
||
)
|
||
|
||
const bindKey = computed(() => props.modelValue?.value?.replace?.('this.state.', '') || '')
|
||
|
||
const editorOptions = {
|
||
theme: theme(),
|
||
tabSize: 2,
|
||
language: 'javascript',
|
||
autoIndent: true,
|
||
formatOnPaste: true,
|
||
automaticLayout: true,
|
||
roundedSelection: true,
|
||
lineNumbers: false,
|
||
minimap: {
|
||
enabled: false
|
||
}
|
||
}
|
||
|
||
const editorDidMount = () => {
|
||
if (!editor.value) {
|
||
return
|
||
}
|
||
|
||
// 支持对象类型数据或表达式,不显示语法校验报错
|
||
const diagnosticsOptions = editor.value
|
||
.getMonaco()
|
||
.languages.typescript.javascriptDefaults.getDiagnosticsOptions()
|
||
editor.value.getMonaco().languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||
...diagnosticsOptions,
|
||
noSyntaxValidation: true,
|
||
noSemanticValidation: true
|
||
})
|
||
}
|
||
|
||
const removeInterval = (start, end, intervalId, pageSchema) => {
|
||
const unmountedFn = pageSchema.lifeCycles?.onUnmounted?.value
|
||
const fetchBody = `
|
||
/** ${start} */
|
||
clearInterval(state.${intervalId});
|
||
/** ${end} */`
|
||
|
||
if (!unmountedFn) {
|
||
pageSchema.lifeCycles = pageSchema.lifeCycles || {}
|
||
pageSchema.lifeCycles.onUnmounted = {
|
||
type: 'JSFunction',
|
||
value: `function onUnmounted() {${fetchBody}}`
|
||
}
|
||
} else {
|
||
if (!unmountedFn.includes(`${intervalId}`)) {
|
||
pageSchema.lifeCycles.onUnmounted.value = unmountedFn.trim().replace(/\}$/, fetchBody + '}')
|
||
}
|
||
}
|
||
}
|
||
|
||
const genRemoteMethodToLifeSetup = (variableName, sourceRef, pageSchema) => {
|
||
if (sourceRef?.data?.data) {
|
||
const setupFn = pageSchema.lifeCycles?.setup?.value
|
||
const { getCommentByKey } = useData()
|
||
const { start, end } = getCommentByKey(variableName)
|
||
const intervalId = `${CONSTANTS.INTERVALID}${capitalize(camelize(sourceRef.name))}`
|
||
const isPoll = state.isPoll && state.pollInterval !== undefined
|
||
|
||
let fetchBodyFn = `${CONSTANTS.DATASOURCEMAP}${sourceRef.name}.load().then(res => {
|
||
state.${variableName} = res?.data?.items || res?.data || res
|
||
})`
|
||
|
||
if (isPoll) {
|
||
fetchBodyFn = `state.${intervalId} = setInterval(() => {${CONSTANTS.DATASOURCEMAP}${sourceRef.name}.load().then(res => {
|
||
state.${variableName} = res?.data?.items || res?.data || res
|
||
})}, ${state.pollInterval})`
|
||
}
|
||
|
||
const fetchBody = `
|
||
/** ${start} */
|
||
${fetchBodyFn};
|
||
/** ${end} */`
|
||
|
||
if (!setupFn) {
|
||
pageSchema.lifeCycles = pageSchema.lifeCycles || {}
|
||
pageSchema.lifeCycles.setup = {
|
||
type: 'JSFunction',
|
||
value: `function setup({ props, state, watch, onMounted }) {${fetchBody}}`
|
||
}
|
||
} else {
|
||
if (!setupFn.includes(`${CONSTANTS.DATASOURCEMAP}${sourceRef.name}`)) {
|
||
pageSchema.lifeCycles.setup.value = setupFn.trim().replace(/\}$/, fetchBody + '}')
|
||
} else {
|
||
const ast = parse(setupFn)
|
||
traverse(ast, {
|
||
ExpressionStatement(path) {
|
||
if (path.toString().includes(sourceRef.name)) {
|
||
path.replaceWithSourceString(fetchBodyFn)
|
||
path.stop()
|
||
}
|
||
}
|
||
})
|
||
|
||
pageSchema.lifeCycles.setup.value = generate(ast).code
|
||
}
|
||
}
|
||
|
||
if (isPoll) {
|
||
removeInterval(start, end, intervalId, pageSchema)
|
||
}
|
||
}
|
||
}
|
||
|
||
const variableClick = (key, item) => {
|
||
if (state.bindPrefix === CONSTANTS.DATASOUCEPREFIX) {
|
||
// 当选中数据源时,直接生成对应state变量并绑定数据源的静态数据
|
||
const stateName = `${CONSTANTS.DATASOUCE}${capitalize(camelize(key))}`
|
||
const staticData = item?.data?.data || []
|
||
|
||
// 处理数据数据回显
|
||
state.dataSouce = item
|
||
state.variable = `${CONSTANTS.STATE}${stateName}`
|
||
editor.value.getEditor().setValue(state.variable)
|
||
state.variableName = key
|
||
state.variableContent = staticData
|
||
} else {
|
||
state.variable = !state.bindPrefix ? `${state.loopArgs}${key}` : `${state.bindPrefix}${key}`
|
||
editor.value.getEditor().setValue(state.variable)
|
||
state.variableName = key
|
||
state.variableContent = state.variables[key]
|
||
}
|
||
}
|
||
|
||
const cancel = () => {
|
||
state.variable = ''
|
||
state.isVisible = false
|
||
}
|
||
|
||
const remove = () => {
|
||
emit('update:modelValue', '')
|
||
cancel()
|
||
}
|
||
|
||
const confirm = () => {
|
||
let variableContent = state.isEditorEditMode ? editor.value?.getEditor().getValue() : state.variable
|
||
|
||
// 如果新旧值不一样就显示未保存状态
|
||
if (oldValue !== variableContent) {
|
||
const { setSaved } = useCanvas()
|
||
|
||
setSaved(false)
|
||
variableContent = formatString(variableContent, 'javascript')
|
||
}
|
||
|
||
const pattern = /^[\s]*{[\s]*api[\s]*:[\s\w.]*}$/
|
||
const needFetchDataFormat = props.name === 'fetchData' && !pattern.test(variableContent)
|
||
|
||
if (variableContent) {
|
||
if (state.bindPrefix === CONSTANTS.DATASOUCEPREFIX) {
|
||
const pageSchema = getSchema()
|
||
const stateName = state.variable.replace(`${CONSTANTS.STATE}`, '')
|
||
const staticData = state.variableContent.map(({ _id, ...other }) => other)
|
||
|
||
pageSchema.state[stateName] = staticData
|
||
|
||
// 设置画布上下文环境,让画布触发更新渲染
|
||
setState({ [stateName]: staticData })
|
||
|
||
// 这里在setup生命周期函数内部处理用户真实环境中的数据源请求
|
||
genRemoteMethodToLifeSetup(stateName, state.dataSouce, pageSchema)
|
||
}
|
||
|
||
emit('update:modelValue', {
|
||
type: 'JSExpression',
|
||
value: needFetchDataFormat ? `{api:${variableContent}}` : variableContent
|
||
})
|
||
} else {
|
||
emit('update:modelValue', '')
|
||
}
|
||
|
||
cancel()
|
||
}
|
||
|
||
const getInitVariable = () => {
|
||
if (
|
||
props.modelValue?.value &&
|
||
props.modelValue?.type === EXPRESSION_TYPE.JS_EXPRESSION &&
|
||
Object.keys(props.modelValue || {}).length === 2
|
||
) {
|
||
return String(props.modelValue?.value)
|
||
}
|
||
|
||
return ''
|
||
}
|
||
|
||
const dialogShouldInitialize = ref(!props.lazyLoad)
|
||
const open = () => {
|
||
dialogShouldInitialize.value = true
|
||
state.isVisible = true
|
||
state.variableName = bindKey.value
|
||
state.variable = getInitVariable()
|
||
state.variables = getSchema()?.state || {}
|
||
state.bindPrefix = CONSTANTS.STATE
|
||
state.variableContent = state.variables[bindKey.value]
|
||
nextTick(() => window.dispatchEvent(new Event('resize')))
|
||
}
|
||
|
||
const selectItem = (item) => {
|
||
state.active = item.id
|
||
if (item.id === 'function') {
|
||
state.bindPrefix = CONSTANTS.THIS
|
||
const { PLUGIN_NAME, getPluginApi } = useLayout()
|
||
const { getMethods } = getPluginApi(PLUGIN_NAME.PageController)
|
||
state.variables = { ...getMethods?.() }
|
||
} else if (item.id === 'bridge' || item.id === 'utils') {
|
||
state.bindPrefix = `${CONSTANTS.THIS}${item.id}.`
|
||
const bridge = {}
|
||
useResource().resState[item.id]?.forEach((res) => {
|
||
bridge[res.name] = `${item.id}.${res.content.exportName}`
|
||
})
|
||
|
||
state.variables = bridge
|
||
} else if (item.id === 'props') {
|
||
state.bindPrefix = CONSTANTS.PROPS
|
||
const properties = getSchema()?.schema?.properties
|
||
const bindProperties = {}
|
||
properties?.forEach(({ content }) => {
|
||
content.forEach(({ property }) => {
|
||
bindProperties[property] = property
|
||
})
|
||
})
|
||
state.variables = bindProperties
|
||
} else if (item.id === 'datasource') {
|
||
state.bindPrefix = CONSTANTS.DATASOUCEPREFIX
|
||
const { appInfoState } = useApp()
|
||
const url = new URLSearchParams(location.search)
|
||
const selectedId = appInfoState.selectedId || url.get('id')
|
||
|
||
// 实时请求数据源列表数据,保证数据源获取最新的数据源数据
|
||
http.get(`/app-center/api/sources/list/${selectedId}`).then((data) => {
|
||
const sourceData = {}
|
||
data.forEach((res) => {
|
||
sourceData[res.name] = res
|
||
})
|
||
state.variables = sourceData
|
||
})
|
||
} else if (item.id === 'store') {
|
||
state.bindPrefix = CONSTANTS.STORE
|
||
state.variables = {}
|
||
|
||
const stores = getGlobalState()
|
||
stores.forEach(({ id, state: storeState = {}, getters = {} }) => {
|
||
const loadProp = (prop) => {
|
||
const propBinding = `${id}.${prop}`
|
||
state.variables[propBinding] = propBinding
|
||
}
|
||
|
||
Object.keys(storeState).forEach(loadProp)
|
||
Object.keys(getters).forEach(loadProp)
|
||
})
|
||
} else if (item.id === 'loop') {
|
||
state.bindPrefix = ''
|
||
const [loopItem = DEFAULT_LOOP_NAME.ITEM, loopIndex = DEFAULT_LOOP_NAME.INDEX] =
|
||
useProperties().getSchema()?.loopArgs || []
|
||
state.variables = [loopItem, loopIndex].reduce((variables, param) => ({ ...variables, [param]: param }), {})
|
||
} else if (item.id === 'slotScope') {
|
||
state.bindPrefix = ''
|
||
const params = getJsSlotParams()
|
||
state.variables = params.reduce((variables, param) => ({ ...variables, [param]: param }), {})
|
||
} else {
|
||
state.bindPrefix = CONSTANTS.STATE
|
||
state.variables = getSchema()?.[item.id]
|
||
}
|
||
}
|
||
|
||
return {
|
||
editorDidMount,
|
||
editorOptions,
|
||
variableClick,
|
||
remove,
|
||
cancel,
|
||
confirm,
|
||
dialogShouldInitialize,
|
||
open,
|
||
selectItem,
|
||
state,
|
||
editor,
|
||
isDataSource
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.icon {
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.meta-bind-variable-dialog-box {
|
||
.header-alert {
|
||
margin-top: 0;
|
||
margin-bottom: 12px;
|
||
color: var(--ti-lowcode-meta-bind-variable-header-alert-color);
|
||
}
|
||
.bind-dialog-content {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.content-left {
|
||
margin-right: 12px;
|
||
width: 38%;
|
||
|
||
.content-left__title {
|
||
color: var(--ti-lowcode-meta-bind-variable-content-left-title-color);
|
||
}
|
||
|
||
.list-wrap {
|
||
border: 1px solid var(--ti-lowcode-meta-bind-variable-list-wrap-border-color);
|
||
border-radius: 4px;
|
||
height: 300px;
|
||
margin-top: 8px;
|
||
display: flex;
|
||
}
|
||
|
||
.content-left__list {
|
||
width: 120px;
|
||
color: var(--ti-lowcode-meta-bind-variable-content-left-list-color);
|
||
border-right: 1px solid var(--ti-lowcode-meta-bind-variable-content-left-list-border-right-color);
|
||
}
|
||
|
||
.content-left__list-item {
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
&.active,
|
||
&:hover {
|
||
background: var(--ti-lowcode-meta-bind-variable-list-item-hover-bg-color);
|
||
}
|
||
}
|
||
|
||
.item-selected {
|
||
background-color: var(--ti-lowcode-meta-bind-variable-item-selected-bg-color);
|
||
}
|
||
|
||
.item-text {
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
color: var(--ti-lowcode-meta-bind-variable-item-text-color);
|
||
|
||
&:hover {
|
||
background-color: var(--ti-lowcode-meta-bind-variable-item-hover-bg-color);
|
||
}
|
||
}
|
||
|
||
.content-left__title {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.item-content {
|
||
padding: 12px;
|
||
width: calc(100% - 140px);
|
||
|
||
.item-content-list {
|
||
height: calc(100% - 42px);
|
||
overflow-y: auto;
|
||
}
|
||
}
|
||
}
|
||
|
||
.content-right {
|
||
flex: 1 1 0;
|
||
width: 60%;
|
||
.content-detail-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
.header-name {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.content-right__title {
|
||
color: var(--ti-lowcode-meta-bind-variable-content-right-title-color);
|
||
font-weight: 600;
|
||
margin-right: 5px;
|
||
}
|
||
|
||
.state-preview {
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.content-wrap {
|
||
height: 300px;
|
||
margin-top: 8px;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
|
||
.top {
|
||
width: 100%;
|
||
height: 54%;
|
||
border-radius: 4px;
|
||
padding: 12px 8px;
|
||
color: var(--ti-lowcode-meta-bind-variable-top-color);
|
||
border: 1px solid var(--ti-lowcode-meta-bind-variable-top-border-color);
|
||
box-sizing: border-box;
|
||
& > div {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
.datasource-poll-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 24px;
|
||
margin-top: 12px;
|
||
.datasource-poll-interval {
|
||
margin-left: 16px;
|
||
.tiny-input {
|
||
width: 120px;
|
||
height: 20px;
|
||
margin: 0 8px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.poll-top {
|
||
& > div {
|
||
height: calc(100% - 36px);
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.bottom {
|
||
width: 100%;
|
||
height: 40%;
|
||
padding: 8px 12px;
|
||
border-radius: 4px;
|
||
box-sizing: border-box;
|
||
overflow: auto;
|
||
color: var(--ti-lowcode-meta-bind-variable-bottom-color);
|
||
border: 1px solid var(--ti-lowcode-meta-bind-variable-bottom-border-color);
|
||
pre {
|
||
font-family: consolas;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bind-dialog-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
}
|
||
</style>
|