fix: 物模型属性组件开发
This commit is contained in:
parent
7bd5c034e5
commit
c201ebdbf4
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<a-modal :mask-closable="false" visible width="70vw" title="设置属性规则" @cancel="handleCancel" @ok="handleOk">
|
||||
<div class="advance-box">
|
||||
<div class="left">
|
||||
<Editor
|
||||
mode="advance"
|
||||
key="advance"
|
||||
v-model:value="_value"
|
||||
/>
|
||||
<Debug
|
||||
:virtualRule="{
|
||||
...virtualRule,
|
||||
script: _value,
|
||||
}"
|
||||
:id="id"
|
||||
/>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Operator :id="id" />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Advance">
|
||||
import Editor from '../Editor/index.vue'
|
||||
import Debug from '../Debug/index.vue'
|
||||
import Operator from '../Operator/index.vue'
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: string | undefined): void;
|
||||
(e: 'change', data: string): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
id: String,
|
||||
virtualRule: Object
|
||||
})
|
||||
|
||||
const _value = ref<string | undefined>(props.value)
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('change', 'simple')
|
||||
}
|
||||
const handleOk = () => {
|
||||
emit('update:value', _value.value)
|
||||
emit('change', 'simple')
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.advance-box {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
|
||||
.left {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 30%;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid lightgray;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,266 @@
|
|||
<template>
|
||||
<div class="debug-container">
|
||||
<div class="left">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">
|
||||
属性赋值
|
||||
<div class="description">请对上方规则使用的属性进行赋值</div>
|
||||
</div>
|
||||
<div v-if="!isBeginning && virtualRule?.type === 'window'" class="action" @click="runScriptAgain">
|
||||
<a style="margin-left: 75px;">发送数据</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="property" :pagination="false" bordered size="small">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-input v-model:value="record.id" size="small"></a-input>
|
||||
</template>
|
||||
<template v-if="column.key === 'current'">
|
||||
<a-input v-model:value="record.current" size="small"></a-input>
|
||||
</template>
|
||||
<template v-if="column.key === 'last'">
|
||||
<a-input v-model:value="record.last" size="small"></a-input>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<delete-outlined @click="deleteItem(index)" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-button type="dashed" block style="margin-top: 5px" @click="addItem">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
添加条目
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<div>运行结果</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<div>
|
||||
<a v-if="isBeginning" @click="beginAction">
|
||||
开始运行
|
||||
</a>
|
||||
<a v-else @click="stopAction">
|
||||
停止运行
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a @click="clearAction">
|
||||
清空
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log">
|
||||
<a-descriptions>
|
||||
<a-descriptions-item v-for="item in ruleEditorStore.state.log" :label="moment(item.time).format('HH:mm:ss')" :key="item.time"
|
||||
:span="3">
|
||||
<a-tooltip placement="top" :title="item.content">
|
||||
{{ item.content }}
|
||||
</a-tooltip>
|
||||
</a-descriptions-item>
|
||||
))}
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="Debug">
|
||||
import { PropType } from 'vue';
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { useProductStore } from '@/store/product';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useRuleEditorStore } from '@/store/ruleEditor';
|
||||
import moment from 'moment';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
virtualRule: Object as PropType<Record<any, any>>,
|
||||
id: String,
|
||||
})
|
||||
|
||||
const isBeginning = ref(true)
|
||||
|
||||
const runScriptAgain = () => { }
|
||||
|
||||
type propertyType = {
|
||||
id?: string,
|
||||
current?: string,
|
||||
last?: string
|
||||
}
|
||||
const property = ref<propertyType[]>([])
|
||||
|
||||
const columns = [{
|
||||
title: '属性ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id'
|
||||
}, {
|
||||
title: '当前值',
|
||||
dataIndex: 'current',
|
||||
key: 'current'
|
||||
}, {
|
||||
title: '上一值',
|
||||
dataIndex: 'last',
|
||||
key: 'last'
|
||||
}, {
|
||||
title: '',
|
||||
key: 'action'
|
||||
}]
|
||||
|
||||
const addItem = () => {
|
||||
property.value.push({})
|
||||
}
|
||||
const deleteItem = (index: number) => {
|
||||
property.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const ws = ref()
|
||||
|
||||
const virtualIdRef = ref(new Date().getTime());
|
||||
|
||||
const productStore = useProductStore()
|
||||
const ruleEditorStore = useRuleEditorStore()
|
||||
const runScript = () => {
|
||||
const metadata = productStore.current.metadata || '{}';
|
||||
const propertiesList = JSON.parse(metadata).properties || [];
|
||||
const _properties = property.value.map((item: any) => {
|
||||
const _item = propertiesList.find((i: any) => i.id === item.id);
|
||||
return { ...item, type: _item?.valueType?.type };
|
||||
});
|
||||
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
}
|
||||
if (!props.virtualRule?.script) {
|
||||
isBeginning.value = true;
|
||||
message.warning('请编辑规则');
|
||||
return;
|
||||
}
|
||||
ws.value = getWebSocket(`virtual-property-debug-${ruleEditorStore.state.property}-${new Date().getTime()}`,
|
||||
'/virtual-property-debug',
|
||||
{
|
||||
virtualId: `${virtualIdRef.value}-virtual-id`,
|
||||
property: ruleEditorStore.state.property,
|
||||
virtualRule: {
|
||||
...props.virtualRule,
|
||||
},
|
||||
properties: _properties || [],
|
||||
})
|
||||
ws.value.subscribe((data: any) => {
|
||||
ruleEditorStore.state.log.push({ time: new Date().getTime(), content: JSON.stringify(data.payload) });
|
||||
})
|
||||
}
|
||||
const beginAction = () => {
|
||||
isBeginning.value = false;
|
||||
runScript();
|
||||
}
|
||||
const stopAction = () => {
|
||||
isBeginning.value = true;
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
}
|
||||
}
|
||||
const clearAction = () => {
|
||||
ruleEditorStore.set('log', []);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ws.value) {
|
||||
ws.value.unsubscribe();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.debug-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 340px;
|
||||
margin-top: 20px;
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: 550px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid lightgray;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid lightgray;
|
||||
//justify-content: space-around;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
//width: 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
margin: 0 10px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 10px;
|
||||
color: lightgray;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
width: 150px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border: 1px solid lightgray;
|
||||
border-left: none;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid lightgray;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log {
|
||||
height: 290px;
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div class="editor-box">
|
||||
<div class="top">
|
||||
<div class="left">
|
||||
<span v-for="item in symbolList.filter((t: SymbolType, i: number) => i <= 3)" :key="item.key"
|
||||
@click="handleInsertCode(item.value)">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
<span>
|
||||
<a-dropdown>
|
||||
<more-outlined />
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-for="item in symbolList.filter((t: SymbolType, i: number) => i > 6)" :key="item.key"
|
||||
@click="handleInsertCode(item.value)">
|
||||
{{ item.value }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span v-if="mode !== 'advance'">
|
||||
<a-tooltip :title="!id ? '请先输入标识' : '设置属性规则'">
|
||||
<fullscreen-outlined :class="!id ? 'disabled' : ''" @click="fullscreenClick" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor">
|
||||
<MonacoEditor v-if="loading" v-model:model-value="_value" theme="vs" ref="editor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="Editor">
|
||||
import { FullscreenOutlined, MoreOutlined } from '@ant-design/icons-vue';
|
||||
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||
|
||||
interface Props {
|
||||
mode?: 'advance' | 'simple';
|
||||
id?: string;
|
||||
value?: string;
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
interface Emits {
|
||||
(e: 'change', data: string): void;
|
||||
(e: 'update:value', data: string): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
type editorType = {
|
||||
insert(val: string): void
|
||||
}
|
||||
const editor = ref<editorType>()
|
||||
|
||||
type SymbolType = {
|
||||
key: string,
|
||||
value: string
|
||||
}
|
||||
const symbolList = [
|
||||
{
|
||||
key: 'add',
|
||||
value: '+',
|
||||
},
|
||||
{
|
||||
key: 'subtract',
|
||||
value: '-',
|
||||
},
|
||||
{
|
||||
key: 'multiply',
|
||||
value: '*',
|
||||
},
|
||||
{
|
||||
key: 'divide',
|
||||
value: '/',
|
||||
},
|
||||
{
|
||||
key: 'parentheses',
|
||||
value: '()',
|
||||
},
|
||||
{
|
||||
key: 'cubic',
|
||||
value: '^',
|
||||
},
|
||||
{
|
||||
key: 'dayu',
|
||||
value: '>',
|
||||
},
|
||||
{
|
||||
key: 'dayudengyu',
|
||||
value: '>=',
|
||||
},
|
||||
{
|
||||
key: 'dengyudengyu',
|
||||
value: '==',
|
||||
},
|
||||
{
|
||||
key: 'xiaoyudengyu',
|
||||
value: '<=',
|
||||
},
|
||||
{
|
||||
key: 'xiaoyu',
|
||||
value: '<',
|
||||
},
|
||||
{
|
||||
key: 'jiankuohao',
|
||||
value: '<>',
|
||||
},
|
||||
{
|
||||
key: 'andand',
|
||||
value: '&&',
|
||||
},
|
||||
{
|
||||
key: 'huohuo',
|
||||
value: '||',
|
||||
},
|
||||
{
|
||||
key: 'fei',
|
||||
value: '!',
|
||||
},
|
||||
{
|
||||
key: 'and',
|
||||
value: '&',
|
||||
},
|
||||
{
|
||||
key: 'huo',
|
||||
value: '|',
|
||||
},
|
||||
{
|
||||
key: 'bolang',
|
||||
value: '~',
|
||||
},
|
||||
] as SymbolType[];
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value || '',
|
||||
set: (data: string) => {
|
||||
emit('update:value', data);
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
loading.value = true;
|
||||
}, 100);
|
||||
})
|
||||
|
||||
const handleInsertCode = (val: string) => {
|
||||
editor.value?.insert(val)
|
||||
}
|
||||
|
||||
const fullscreenClick = () => {
|
||||
if (props.id) {
|
||||
emit('change', 'advance');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.editor-box {
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid lightgray;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid lightgray;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 60%;
|
||||
margin: 0 5px;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
margin: 0 10px;
|
||||
line-height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 10%;
|
||||
margin: 0 5px;
|
||||
|
||||
span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: rgba(#000, 0.5);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div class="operator-box">
|
||||
<a-input-search @search="search" allow-clear placeholder="搜索关键字" />
|
||||
<a-tree class="tree" @select="selectTree" :field-names="{ title: 'name', key: 'id', }" auto-expand-parent
|
||||
:tree-data="data">
|
||||
<template #title="node">
|
||||
<div class="node">
|
||||
<div>{{ node.name }}</div>
|
||||
<div :class="node.children?.length > 0 ? 'parent' : 'add'">
|
||||
<a-popover v-if="node.type === 'property'" placement="right" title="请选择使用值" @visibleChange="setVisible">
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<a-tooltip placement="right" title="实时值为空时获取上一有效值补齐,实时值不为空则使用实时值">
|
||||
<a-button type="text" @click="recentClick(node)">
|
||||
$recent实时值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="right" title="实时值的上一有效值">
|
||||
<a-button @click="lastClick(node)" type="text">
|
||||
上一值
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a @click="setVisible(true)">添加</a>
|
||||
</a-popover>
|
||||
|
||||
<a v-else @click="addClick(node)">
|
||||
添加
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
<div class="explain">
|
||||
<ReactMarkdown>{{ item?.description || '' }}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="Operator">
|
||||
import type { OperatorItem } from './typings';
|
||||
import { treeFilter } from '@/utils/tree'
|
||||
import { Store } from 'jetlinks-store';
|
||||
|
||||
const item = ref<Partial<OperatorItem>>()
|
||||
const data = ref<OperatorItem[]>([])
|
||||
const dataRef = ref<OperatorItem[]>([])
|
||||
const visible = ref(false)
|
||||
|
||||
const search = (value: string) => {
|
||||
if (value) {
|
||||
const nodes = treeFilter(dataRef.value, value, 'name') as OperatorItem[];
|
||||
data.value = nodes;
|
||||
} else {
|
||||
data.value = dataRef.value;
|
||||
}
|
||||
};
|
||||
|
||||
const selectTree = (k: any, info: any) => {
|
||||
item.value = info.node as unknown as OperatorItem;
|
||||
}
|
||||
|
||||
const setVisible = (_visible: boolean) => {
|
||||
visible.value = !!visible
|
||||
}
|
||||
|
||||
const recentClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', `$recent("${node.id}")`);
|
||||
setVisible(!visible.value);
|
||||
}
|
||||
const lastClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', `$lastState("${node.id}")`);
|
||||
setVisible(!visible.value);
|
||||
}
|
||||
const addClick = (node: OperatorItem) => {
|
||||
Store.set('add-operator-value', node.code);
|
||||
setVisible(true);
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.border {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border-top: 1px solid lightgray;
|
||||
}
|
||||
|
||||
.operator-box {
|
||||
width: 100%;
|
||||
|
||||
.explain {
|
||||
.border;
|
||||
}
|
||||
|
||||
.tree {
|
||||
.border;
|
||||
|
||||
height: 350px;
|
||||
overflow-y: auto;
|
||||
|
||||
.node {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 220px;
|
||||
|
||||
//.add {
|
||||
// display: none;
|
||||
//}
|
||||
//
|
||||
//&:hover .add {
|
||||
// display: block;
|
||||
//}
|
||||
|
||||
.parent {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,10 @@
|
|||
import type { TreeNode } from '@/utils/tree';
|
||||
|
||||
interface OperatorItem extends TreeNode {
|
||||
id: string;
|
||||
name: string;
|
||||
key: string;
|
||||
description: string;
|
||||
code: string;
|
||||
children: OperatorItem[];
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<Editor key="simple" @change="change" v-model:value="_value" :id="id" />
|
||||
{{ ruleEditorStore.state.model }}
|
||||
<Advance v-if="ruleEditorStore.state.model === 'advance'" :model="ruleEditorStore.state.model"
|
||||
:virtualRule="virtualRule" :id="id" @change="change" />
|
||||
</template>
|
||||
<script setup lang="ts" name="FRuleEditor">
|
||||
import { useRuleEditorStore } from '@/store/ruleEditor'
|
||||
import Editor from './Editor/index.vue'
|
||||
import Advance from './Advance/index.vue'
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
property?: string;
|
||||
virtualRule?: any;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: string): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: (val: string) => {
|
||||
emit('update:value', val)
|
||||
}
|
||||
})
|
||||
|
||||
const ruleEditorStore = useRuleEditorStore()
|
||||
|
||||
const change = (v: string) => {
|
||||
ruleEditorStore.set('model', v);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
ruleEditorStore.set('property', props.property)
|
||||
ruleEditorStore.set('code', props.value);
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<a-form-item :name="name.concat(['script'])">
|
||||
<f-rule-editor v-model:value="value.script" :id="id" ></f-rule-editor>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<script setup lang="ts" name="VirtualRuleParam">
|
||||
import { PropType } from 'vue';
|
||||
import FRuleEditor from '@/components/FRuleEditor/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
type: 'script',
|
||||
})
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ([])
|
||||
},
|
||||
id: String
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: Record<any, any>): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
onMounted(() => {
|
||||
emit('update:value', {
|
||||
...props.value,
|
||||
type: 'script'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -73,6 +73,27 @@ watchEffect(() => {
|
|||
editorFormat();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const insert = (val) => {
|
||||
if (!instance) return
|
||||
const position = instance.getPosition();
|
||||
instance.executeEdits(instance.getValue(), [
|
||||
{
|
||||
range: new monaco.Range(
|
||||
position?.lineNumber,
|
||||
position?.column,
|
||||
position?.lineNumber,
|
||||
position?.column,
|
||||
),
|
||||
text: val,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
editorFormat,
|
||||
insert,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
type RuleEditorType = {
|
||||
model: 'simple' | 'advance';
|
||||
code: string;
|
||||
property?: string;
|
||||
log: {
|
||||
content: string;
|
||||
time: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const useRuleEditorStore = defineStore({
|
||||
id: 'ruleEditor',
|
||||
state: () => ({
|
||||
state: {
|
||||
model: 'simple',
|
||||
code: '',
|
||||
log: [],
|
||||
} as RuleEditorType
|
||||
}),
|
||||
actions: {
|
||||
set(key: string, value: any) {
|
||||
this.state[key] = value
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
场景
|
||||
树形数据过滤, 并保留原有树形结构不变, 即如果有子集被选中,父级同样保留。
|
||||
思路
|
||||
对数据进行处理,根据过滤标识对匹配的数据添加标识。如visible:true
|
||||
对有标识的子集的父级添加标识visible:true
|
||||
根据visible标识对数据进行递归过滤,得到最后的数据
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export type TreeNode = {
|
||||
id: string;
|
||||
name: string;
|
||||
children: TreeNode[];
|
||||
visible?: boolean;
|
||||
} & Record<string, any>;
|
||||
|
||||
/*
|
||||
* 对表格数据进行处理
|
||||
* data 树形数据数组
|
||||
* filter 过滤参数值
|
||||
* filterType 过滤参数名
|
||||
*/
|
||||
export function treeFilter(data: TreeNode[], filter: string, filterType: string): TreeNode[] {
|
||||
const _data = _.cloneDeep(data);
|
||||
const traverse = (item: TreeNode[]) => {
|
||||
item.forEach((child) => {
|
||||
child.visible = filterMethod(filter, child, filterType);
|
||||
if (child.children) traverse(child.children);
|
||||
if (!child.visible && child.children?.length) {
|
||||
const visible = !child.children.some((c) => c.visible);
|
||||
child.visible = !visible;
|
||||
}
|
||||
});
|
||||
};
|
||||
traverse(_data);
|
||||
return filterDataByVisible(_data);
|
||||
}
|
||||
|
||||
// 根据传入的值进行数据匹配, 并返回匹配结果
|
||||
function filterMethod(val: string, data: TreeNode, filterType: string | number) {
|
||||
return data[filterType].includes(val);
|
||||
}
|
||||
|
||||
// 递归过滤符合条件的数据
|
||||
function filterDataByVisible(data: TreeNode[]) {
|
||||
return data.filter((item) => {
|
||||
if (item.children) {
|
||||
item.children = filterDataByVisible(item.children);
|
||||
}
|
||||
return item.visible;
|
||||
});
|
||||
}
|
||||
|
||||
const mockData = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [],
|
||||
name: '加',
|
||||
id: 'operator-1',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: '减',
|
||||
id: 'operator-2',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: '乘',
|
||||
id: 'operator-3',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: '除',
|
||||
id: 'operator-4',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: '括号',
|
||||
id: 'operator-5',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: '按位异或',
|
||||
id: 'operator-6',
|
||||
},
|
||||
],
|
||||
name: '操作符',
|
||||
id: 'operator',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: [],
|
||||
name: 'if',
|
||||
id: 'if',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: 'for',
|
||||
id: 'for',
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
name: 'while',
|
||||
id: 'while',
|
||||
},
|
||||
],
|
||||
name: '控制语句',
|
||||
id: 'control',
|
||||
},
|
||||
];
|
||||
const myTree = treeFilter(mockData, '操作', 'name');
|
||||
|
||||
console.log(JSON.stringify(myTree), 'mytree');
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<a-form-item label="来源" :name="name.concat(['source'])" v-if="type === 'product'" :rules="[
|
||||
{ required: true, message: '请选择来源' },
|
||||
]">
|
||||
<a-select v-model:value="_value.source" :options="PropertySource" size="small" :disabled="metadataStore.model.action === 'edit'"></a-select>
|
||||
</a-form-item>
|
||||
<virtual-rule-param v-if="_value.source === 'rule'" v-model:value="_value.virtualRule" :name="name.concat(['virtualRule'])" :id="id"></virtual-rule-param>
|
||||
<a-form-item label="读写类型" :name="name.concat(['type'])" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<a-select v-model:value="_value.type" :options="options" mode="multiple" size="small"></a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<script setup lang="ts" name="ExpandsForm">
|
||||
import { useMetadataStore } from '@/store/metadata';
|
||||
import { PropertySource } from '@/views/device/data';
|
||||
import { PropType } from 'vue';
|
||||
import VirtualRuleParam from '@/components/Metadata/VirtualRuleParam/index.vue';
|
||||
|
||||
type ValueType = Record<any, any>;
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: Object as PropType<ValueType>,
|
||||
default: () => ({})
|
||||
},
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
name: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ([]),
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: String
|
||||
},
|
||||
})
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', data: ValueType): void;
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const _value = computed({
|
||||
get: () => props.value,
|
||||
set: val => {
|
||||
emit('update:value', val)
|
||||
}
|
||||
})
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '读',
|
||||
value: 'read',
|
||||
},
|
||||
{
|
||||
label: '写',
|
||||
value: 'write',
|
||||
},
|
||||
{
|
||||
label: '上报',
|
||||
value: 'report',
|
||||
},
|
||||
]
|
||||
|
||||
const metadataStore = useMetadataStore()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.type === 'product' || !props.value.source) {
|
||||
emit('update:value', { ...props.value, source: 'device' })
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
|
@ -16,12 +16,10 @@
|
|||
]">
|
||||
<a-input v-model:value="form.model.name" size="small"></a-input>
|
||||
</a-form-item>
|
||||
<ValueTypeForm :name="['valueType']" v-model:value="form.model.valueType" key="property"></ValueTypeForm>
|
||||
<a-form-item label="读写类型" :name="['expands', 'type']" :rules="[
|
||||
{ required: true, message: '请选择读写类型' },
|
||||
]">
|
||||
<a-select v-model:value="form.model.expands.type" :options="form.expandsType" mode="multiple" size="small"></a-select>
|
||||
</a-form-item>
|
||||
<value-type-form :name="['valueType']" v-model:value="form.model.valueType" key="property"></value-type-form>
|
||||
|
||||
<expands-form :name="['expands']" v-model:value="form.model.expands" :type="type" :id="form.model.id"></expands-form>
|
||||
|
||||
<a-form-item label="说明" name="description" :rules="[
|
||||
{ max: 200, message: '最多可输入200个字符' },
|
||||
]">
|
||||
|
@ -30,8 +28,18 @@
|
|||
</a-form>
|
||||
</template>
|
||||
<script setup lang="ts" name="PropertyForm">
|
||||
import { PropType } from 'vue';
|
||||
import ExpandsForm from './ExpandsForm.vue';
|
||||
import ValueTypeForm from './ValueTypeForm.vue'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<'product' | 'device'>,
|
||||
required: true,
|
||||
default: 'product'
|
||||
}
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
model: {
|
||||
valueType: {
|
||||
|
@ -39,20 +47,6 @@ const form = reactive({
|
|||
},
|
||||
expands: {}
|
||||
} as any,
|
||||
expandsType: [
|
||||
{
|
||||
label: '读',
|
||||
value: 'read',
|
||||
},
|
||||
{
|
||||
label: '写',
|
||||
value: 'write',
|
||||
},
|
||||
{
|
||||
label: '上报',
|
||||
value: 'report',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<template #extra>
|
||||
<a-button :loading="save.loading" type="primary" @click="save.saveMetadata">保存</a-button>
|
||||
</template>
|
||||
<PropertyForm v-if="metadataStore.model.type === 'properties'"></PropertyForm>
|
||||
<PropertyForm v-if="metadataStore.model.type === 'properties'" :type="type"></PropertyForm>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup name="Edit">
|
||||
|
@ -20,12 +20,18 @@ import { SystemConst } from '@/utils/consts';
|
|||
import { detail } from '@/api/device/instance';
|
||||
import { DeviceInstance } from '@/views/device/Instance/typings';
|
||||
import PropertyForm from './PropertyForm.vue';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
interface Props {
|
||||
type: 'product' | 'device';
|
||||
tabs?: string;
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<'product' | 'device'>,
|
||||
required: true,
|
||||
default: 'product'
|
||||
},
|
||||
tabs: {
|
||||
type: String
|
||||
}
|
||||
})
|
||||
const route = useRoute()
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<script setup lang="ts" name="BaseMetadata">
|
||||
import type { MetadataItem, MetadataType } from '@/views/device/Product/typings'
|
||||
import MetadataMapping from './columns'
|
||||
import JTable, { JColumnProps } from '@/components/Table'
|
||||
import JTable from '@/components/Table'
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { useProductStore } from '@/store/product'
|
||||
import { useMetadataStore } from '@/store/metadata'
|
||||
|
@ -97,7 +97,7 @@ const expandsType = ref({
|
|||
write: '写',
|
||||
report: '上报',
|
||||
});
|
||||
const actions: JColumnProps[] = [
|
||||
const actions = [
|
||||
{
|
||||
title: '操作',
|
||||
align: 'left',
|
||||
|
|
Loading…
Reference in New Issue